-
Notifications
You must be signed in to change notification settings - Fork 407
Expand file tree
/
Copy pathci-discovery.js
More file actions
187 lines (160 loc) · 6.15 KB
/
Copy pathci-discovery.js
File metadata and controls
187 lines (160 loc) · 6.15 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
'use strict'
const path = require('node:path')
const DEFAULT_CI_SEARCHES = [
'.github/workflows/*.yml',
'.github/workflows/*.yaml',
'.gitlab-ci.yml',
'.gitlab-ci.yaml',
'.circleci/config.yml',
'.circleci/config.yaml',
'.buildkite/pipeline.yml',
'.buildkite/pipeline.yaml',
'bitbucket-pipelines.yml',
'bitbucket-pipelines.yaml',
'azure-pipelines.yml',
'azure-pipelines.yaml',
'.azure-pipelines/*.yml',
'.azure-pipelines/*.yaml',
'Jenkinsfile',
]
function annotateCiDiscovery ({ manifest, diagnosis }) {
manifest.ciDiscovery = buildCiDiscovery({ manifest, diagnosis })
if (manifest.ciDiscovery.contradictions.length === 0) return
const warnings = Array.isArray(manifest.warnings) ? manifest.warnings : []
for (const contradiction of manifest.ciDiscovery.contradictions) {
const warning = `CI discovery contradiction: ${contradiction}`
if (!warnings.includes(warning)) warnings.push(warning)
}
manifest.warnings = warnings
}
function buildCiDiscovery ({ manifest, diagnosis }) {
const declared = isObject(manifest.ciDiscovery) ? manifest.ciDiscovery : {}
const staticFound = getStaticWorkflowLocations(diagnosis)
const declaredFound = normalizeStringArray(declared.found)
const candidateFound = getManifestWorkflowLocations(manifest)
const manifestFound = uniqueStrings([...declaredFound, ...candidateFound])
const searched = normalizeStringArray(declared.searched)
let method = 'validator-static-diagnosis'
if (candidateFound.length > 0) method = 'framework-ci-command'
if (declaredFound.length > 0) method = 'manifest'
if (typeof declared.method === 'string' && declared.method) method = declared.method
const found = manifestFound.length > 0 ? manifestFound : staticFound
const contradictions = getCiDiscoveryContradictions({ manifest, declaredFound: manifestFound, staticFound })
return {
searched: searched.length > 0 ? searched : DEFAULT_CI_SEARCHES,
found,
staticFound,
method,
warnings: normalizeStringArray(declared.warnings),
notes: normalizeStringArray(declared.notes),
contradictions,
}
}
function getManifestWorkflowLocations (manifest) {
const root = manifest.repository?.root
const locations = []
if (manifest.frameworks) {
for (const framework of manifest.frameworks) {
const configFile = framework.ciWiring?.configFile
if (typeof configFile !== 'string') continue
if (!root || !path.isAbsolute(configFile)) {
locations.push(configFile)
continue
}
const relative = path.relative(root, configFile)
const isRelativePath = relative && relative !== '..' && !relative.startsWith(`..${path.sep}`) &&
!path.isAbsolute(relative)
locations.push(isRelativePath
? relative.split(path.sep).join('/')
: configFile)
}
}
return uniqueStrings(locations)
}
function getFrameworkCiDiscoveryContradiction (framework, manifest) {
const ciDiscovery = manifest?.ciDiscovery
if (!ciDiscovery || !Array.isArray(ciDiscovery.staticFound) || ciDiscovery.staticFound.length === 0) return null
if (!frameworkClaimsNoCi(framework)) return null
return {
reason: 'CI workflow files were found by validator static diagnosis, but this manifest entry says no CI ' +
'workflow was found. The manifest cannot support a "no CI workflow found" conclusion.',
recommendation: 'Inspect the discovered CI files with hidden-directory-aware discovery, then update ciWiring, ' +
'ciWiring configuration evidence, omittedTestCommands, notes, or unresolved blockers before validation.',
ciDiscovery,
}
}
function manifestHasCiDiscoveryContradiction (manifest) {
return Array.isArray(manifest?.ciDiscovery?.contradictions) &&
manifest.ciDiscovery.contradictions.length > 0
}
function getCiDiscoveryContradictions ({ manifest, declaredFound, staticFound }) {
if (staticFound.length === 0) return []
const contradictions = []
if (isObject(manifest.ciDiscovery) && declaredFound.length === 0) {
contradictions.push(
`manifest ciDiscovery.found is empty, but static diagnosis found ${formatList(staticFound)}`
)
}
if (manifest.frameworks) {
for (const framework of manifest.frameworks) {
if (!frameworkClaimsNoCi(framework)) continue
contradictions.push(
`framework ${framework.id || '<unknown>'} records no CI workflow, but static diagnosis found ` +
formatList(staticFound)
)
}
}
return contradictions
}
function frameworkClaimsNoCi (framework) {
const ciWiring = framework?.ciWiring
if (!isObject(ciWiring)) return false
if (ciWiring.provider === 'none') return true
return textClaimsNoCi([
ciWiring.diagnosis,
...(Array.isArray(ciWiring.unresolved) ? ciWiring.unresolved : []),
...(Array.isArray(framework.notes) ? framework.notes : []),
])
}
function textClaimsNoCi (values) {
return values.some(value => {
if (typeof value !== 'string') return false
return /no .*ci .*workflow/i.test(value) ||
/no .*ci .*configuration/i.test(value) ||
/no github actions.*gitlab.*circleci.*jenkins/i.test(value)
})
}
function getStaticWorkflowLocations (diagnosis) {
const results = Array.isArray(diagnosis?.results) ? diagnosis.results : []
const locations = []
const seen = new Set()
for (const result of results) {
if (result.title !== 'CI workflow files found' || !Array.isArray(result.locations)) continue
for (const location of result.locations) {
if (typeof location !== 'string' || seen.has(location)) continue
seen.add(location)
locations.push(location)
}
}
return locations
}
function normalizeStringArray (value) {
if (!Array.isArray(value)) return []
return value.filter(item => typeof item === 'string')
}
function uniqueStrings (values) {
return [...new Set(values.filter(value => typeof value === 'string' && value !== ''))]
}
function formatList (values) {
return values.map(value => `"${value}"`).join(', ')
}
function isObject (value) {
return value !== null && typeof value === 'object' && !Array.isArray(value)
}
module.exports = {
annotateCiDiscovery,
buildCiDiscovery,
getFrameworkCiDiscoveryContradiction,
getStaticWorkflowLocations,
manifestHasCiDiscoveryContradiction,
}