@openreachtech/mentsu-npm-audit-gate 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.ja.md +124 -0
- package/README.md +123 -2
- package/lib/AuditFetcher.js +134 -0
- package/lib/AuditReportResponse.js +343 -0
- package/lib/EXIT_CODE.js +14 -0
- package/lib/IgnoredAdvisoriesLoader.js +96 -0
- package/lib/NpmAuditGate.js +179 -0
- package/lib/ProcessArgumentsResolver.js +67 -0
- package/lib/ProcessLogger.js +97 -0
- package/lib/globals/root-path.js +7 -0
- package/lib/index.js +8 -0
- package/package.json +55 -4
- package/scripts/npm-audit-gate.js +31 -0
- package/types/index.d.ts +49 -0
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Judge an `npm audit` report, deliberately letting through the advisories
|
|
3
|
+
* the caller lists, as a stopgap until a release fixes them.
|
|
4
|
+
*
|
|
5
|
+
* It fails on an advisory the list does not name, at any severity,
|
|
6
|
+
* on an advisory the list names that is no longer reported,
|
|
7
|
+
* and on an `npm audit` that answers with an error instead of a report.
|
|
8
|
+
*/
|
|
9
|
+
export default class AuditReportResponse {
|
|
10
|
+
/**
|
|
11
|
+
* @param {{
|
|
12
|
+
* ignoredAdvisories: Array<NpmAuditType.IgnoredAdvisory>
|
|
13
|
+
* report: AuditReport | null
|
|
14
|
+
* }} params - Parameters of this constructor.
|
|
15
|
+
*/
|
|
16
|
+
constructor ({
|
|
17
|
+
ignoredAdvisories,
|
|
18
|
+
report,
|
|
19
|
+
}) {
|
|
20
|
+
this.ignoredAdvisories = ignoredAdvisories
|
|
21
|
+
this.report = report
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Factory method.
|
|
26
|
+
*
|
|
27
|
+
* @template {X extends typeof AuditReportResponse ? X : never} T, X
|
|
28
|
+
* @param {{
|
|
29
|
+
* ignoredAdvisories: Array<NpmAuditType.IgnoredAdvisory>
|
|
30
|
+
* report: AuditReport | null
|
|
31
|
+
* }} params - Parameters for the factory method.
|
|
32
|
+
* @returns {InstanceType<T>} Instance of this class.
|
|
33
|
+
* @this {T}
|
|
34
|
+
* @public
|
|
35
|
+
*/
|
|
36
|
+
static create ({
|
|
37
|
+
ignoredAdvisories,
|
|
38
|
+
report,
|
|
39
|
+
}) {
|
|
40
|
+
return /** @type {InstanceType<T>} */ (
|
|
41
|
+
new this({
|
|
42
|
+
ignoredAdvisories,
|
|
43
|
+
report,
|
|
44
|
+
})
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* get: Name of the file listing the advisories to let through.
|
|
50
|
+
*
|
|
51
|
+
* @returns {string} File name.
|
|
52
|
+
*/
|
|
53
|
+
static get ignoredAdvisoriesFileName () {
|
|
54
|
+
return 'IGNORED_ADVISORIES.js'
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* get: Pattern picking a GHSA id off the end of an advisory url.
|
|
59
|
+
*
|
|
60
|
+
* @returns {RegExp} Pattern with the named group `id`.
|
|
61
|
+
*/
|
|
62
|
+
static get advisoryIdPattern () {
|
|
63
|
+
return /(?<id>GHSA(?:-[0-9a-z]{4}){3})$/u
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Extract the GHSA id of an advisory.
|
|
68
|
+
*
|
|
69
|
+
* @param {{
|
|
70
|
+
* advisory: AuditAdvisory
|
|
71
|
+
* }} params - Parameters.
|
|
72
|
+
* @returns {string} GHSA id, or the whole url where it carries none.
|
|
73
|
+
*/
|
|
74
|
+
static extractAdvisoryId ({
|
|
75
|
+
advisory,
|
|
76
|
+
}) {
|
|
77
|
+
const matched = advisory.url.match(this.advisoryIdPattern)
|
|
78
|
+
|
|
79
|
+
return matched?.groups
|
|
80
|
+
?.id
|
|
81
|
+
?? advisory.url
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* get: Constructor of this class.
|
|
86
|
+
*
|
|
87
|
+
* @returns {typeof AuditReportResponse} Constructor of this class.
|
|
88
|
+
*/
|
|
89
|
+
get Ctor () {
|
|
90
|
+
return /** @type {typeof AuditReportResponse} */ (this.constructor)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Check whether the report can be judged.
|
|
95
|
+
*
|
|
96
|
+
* @returns {BooleanLike} Whether the report carries vulnerabilities and no error.
|
|
97
|
+
* @public
|
|
98
|
+
*/
|
|
99
|
+
hasUsableReport () {
|
|
100
|
+
if (
|
|
101
|
+
!this.report
|
|
102
|
+
|| this.report.error
|
|
103
|
+
) {
|
|
104
|
+
return false
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return this.report.vulnerabilities
|
|
108
|
+
?? false
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Build the line saying no report came back.
|
|
113
|
+
*
|
|
114
|
+
* @returns {string} Line to print.
|
|
115
|
+
* @public
|
|
116
|
+
*/
|
|
117
|
+
buildNoReportMessage () {
|
|
118
|
+
const error = this.report?.error
|
|
119
|
+
?? null
|
|
120
|
+
|
|
121
|
+
return `🤔 npm audit answered with no report, so nothing was checked: ${JSON.stringify(error)}`
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Build the lines naming each listed advisory that is still reported.
|
|
126
|
+
*
|
|
127
|
+
* @returns {Array<string>} Lines to print.
|
|
128
|
+
* @public
|
|
129
|
+
*/
|
|
130
|
+
buildIgnoredLines () {
|
|
131
|
+
return this.collectMetIgnores()
|
|
132
|
+
.flatMap(it => [
|
|
133
|
+
`⚠️ ignored: ${it.id} (${it.package}) via ${it.path}`,
|
|
134
|
+
` reason: ${it.reason}`,
|
|
135
|
+
` remove when: ${it.removeWhen}`,
|
|
136
|
+
])
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Collect the listed advisories that are still reported.
|
|
141
|
+
*
|
|
142
|
+
* @returns {Array<NpmAuditType.IgnoredAdvisory>} Advisories of the list.
|
|
143
|
+
*/
|
|
144
|
+
collectMetIgnores () {
|
|
145
|
+
return this.ignoredAdvisories
|
|
146
|
+
.filter(it => this.isReported({
|
|
147
|
+
id: it.id,
|
|
148
|
+
}))
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Check whether the report names an advisory.
|
|
153
|
+
*
|
|
154
|
+
* @param {{
|
|
155
|
+
* id: string
|
|
156
|
+
* }} params - Parameters.
|
|
157
|
+
* @returns {BooleanLike} Whether the advisory is reported.
|
|
158
|
+
*/
|
|
159
|
+
isReported ({
|
|
160
|
+
id,
|
|
161
|
+
}) {
|
|
162
|
+
return this.collectAdvisoryIds()
|
|
163
|
+
.includes(id)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Collect the ids of the advisories the report names, each once.
|
|
168
|
+
*
|
|
169
|
+
* @returns {Array<string>} GHSA ids in the order they first appear.
|
|
170
|
+
*/
|
|
171
|
+
collectAdvisoryIds () {
|
|
172
|
+
const advisoryIds = this.collectAdvisories()
|
|
173
|
+
.map(it => this.Ctor.extractAdvisoryId({
|
|
174
|
+
advisory: it,
|
|
175
|
+
}))
|
|
176
|
+
|
|
177
|
+
return [
|
|
178
|
+
...new Set(advisoryIds),
|
|
179
|
+
]
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Collect the advisories the report names, as often as it names them.
|
|
184
|
+
*
|
|
185
|
+
* @returns {Array<AuditAdvisory>} Advisories of the report.
|
|
186
|
+
*/
|
|
187
|
+
collectAdvisories () {
|
|
188
|
+
const vulnerabilities = this.report?.vulnerabilities
|
|
189
|
+
?? {}
|
|
190
|
+
|
|
191
|
+
return Object.values(vulnerabilities)
|
|
192
|
+
.flatMap(it => it.via)
|
|
193
|
+
.filter(it => typeof it === 'object')
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Build the lines naming each failure.
|
|
198
|
+
*
|
|
199
|
+
* @returns {Array<string>} Lines to print.
|
|
200
|
+
* @public
|
|
201
|
+
*/
|
|
202
|
+
buildFailureLines () {
|
|
203
|
+
return [
|
|
204
|
+
...this.buildUnignoredLines(),
|
|
205
|
+
...this.buildStaleLines(),
|
|
206
|
+
]
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Build the lines naming each reported advisory the list does not name.
|
|
211
|
+
*
|
|
212
|
+
* @returns {Array<string>} Lines to print.
|
|
213
|
+
*/
|
|
214
|
+
buildUnignoredLines () {
|
|
215
|
+
return this.collectAdvisoryIds()
|
|
216
|
+
.filter(it => !this.isIgnored({
|
|
217
|
+
id: it,
|
|
218
|
+
}))
|
|
219
|
+
.map(it => this.buildUnignoredLine({
|
|
220
|
+
id: it,
|
|
221
|
+
}))
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Check whether the list names an advisory.
|
|
226
|
+
*
|
|
227
|
+
* @param {{
|
|
228
|
+
* id: string
|
|
229
|
+
* }} params - Parameters.
|
|
230
|
+
* @returns {BooleanLike} Whether the advisory is listed.
|
|
231
|
+
*/
|
|
232
|
+
isIgnored ({
|
|
233
|
+
id,
|
|
234
|
+
}) {
|
|
235
|
+
return this.ignoredAdvisories
|
|
236
|
+
.some(it => it.id === id)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Build the line naming a reported advisory the list does not name.
|
|
241
|
+
*
|
|
242
|
+
* @param {{
|
|
243
|
+
* id: string
|
|
244
|
+
* }} params - Parameters.
|
|
245
|
+
* @returns {string} Line to print.
|
|
246
|
+
*/
|
|
247
|
+
buildUnignoredLine ({
|
|
248
|
+
id,
|
|
249
|
+
}) {
|
|
250
|
+
// The id comes from the report itself, so the advisory is always found.
|
|
251
|
+
const advisory = /** @type {AuditAdvisory} */ (
|
|
252
|
+
this.findAdvisory({
|
|
253
|
+
id,
|
|
254
|
+
})
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
return `🚨 not ignored: ${id} ${advisory.severity} ${advisory.name} ${advisory.range} — ${advisory.title}`
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Find the advisory of an id, taking the last one the report names under it.
|
|
262
|
+
*
|
|
263
|
+
* @param {{
|
|
264
|
+
* id: string
|
|
265
|
+
* }} params - Parameters.
|
|
266
|
+
* @returns {AuditAdvisory | null} Advisory, or null where the report names none.
|
|
267
|
+
*/
|
|
268
|
+
findAdvisory ({
|
|
269
|
+
id,
|
|
270
|
+
}) {
|
|
271
|
+
return this.collectAdvisories()
|
|
272
|
+
.findLast(it => this.Ctor.extractAdvisoryId({
|
|
273
|
+
advisory: it,
|
|
274
|
+
}) === id)
|
|
275
|
+
?? null
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Build the lines naming each listed advisory that is no longer reported.
|
|
280
|
+
*
|
|
281
|
+
* @returns {Array<string>} Lines to print.
|
|
282
|
+
*/
|
|
283
|
+
buildStaleLines () {
|
|
284
|
+
return this.ignoredAdvisories
|
|
285
|
+
.filter(it => !this.isReported({
|
|
286
|
+
id: it.id,
|
|
287
|
+
}))
|
|
288
|
+
.map(it => `🗑️ stale ignore: ${it.id} (${it.package}) is no longer reported; remove it from ${this.Ctor.ignoredAdvisoriesFileName}`)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Build the line summing up the check.
|
|
293
|
+
*
|
|
294
|
+
* @returns {string} Line to print.
|
|
295
|
+
* @public
|
|
296
|
+
*/
|
|
297
|
+
buildSummaryLine () {
|
|
298
|
+
const advisoryCount = this.countAdvisories()
|
|
299
|
+
const metIgnoreCount = this.countMetIgnores()
|
|
300
|
+
const failureCount = this.countFailures()
|
|
301
|
+
|
|
302
|
+
return `checked ${advisoryCount} advisory(ies), ${metIgnoreCount} ignored, ${failureCount} failure(s)`
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Count the advisories the report names, each once.
|
|
307
|
+
*
|
|
308
|
+
* @returns {number} Number of advisories.
|
|
309
|
+
*/
|
|
310
|
+
countAdvisories () {
|
|
311
|
+
return this.collectAdvisoryIds().length
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Count the listed advisories that are still reported.
|
|
316
|
+
*
|
|
317
|
+
* @returns {number} Number of advisories.
|
|
318
|
+
*/
|
|
319
|
+
countMetIgnores () {
|
|
320
|
+
return this.collectMetIgnores().length
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Count the failures.
|
|
325
|
+
*
|
|
326
|
+
* @returns {number} Number of failures.
|
|
327
|
+
* @public
|
|
328
|
+
*/
|
|
329
|
+
countFailures () {
|
|
330
|
+
return this.buildFailureLines().length
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* @typedef {*} BooleanLike
|
|
336
|
+
*/
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* @import {
|
|
340
|
+
* AuditAdvisory,
|
|
341
|
+
* AuditReport,
|
|
342
|
+
* } from './AuditFetcher.js'
|
|
343
|
+
*/
|
package/lib/EXIT_CODE.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import nodeUrl from 'node:url'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Load the advisories a repository lets through, from the file it keeps them in.
|
|
5
|
+
*
|
|
6
|
+
* The file is the only thing an instance holds, and its path is absolute.
|
|
7
|
+
*/
|
|
8
|
+
export default class IgnoredAdvisoriesLoader {
|
|
9
|
+
/**
|
|
10
|
+
* @param {{
|
|
11
|
+
* ignoredAdvisoriesFilePath: string
|
|
12
|
+
* }} params - Parameters of this constructor.
|
|
13
|
+
*/
|
|
14
|
+
constructor ({
|
|
15
|
+
ignoredAdvisoriesFilePath,
|
|
16
|
+
}) {
|
|
17
|
+
this.ignoredAdvisoriesFilePath = ignoredAdvisoriesFilePath
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Factory method.
|
|
22
|
+
*
|
|
23
|
+
* @template {X extends typeof IgnoredAdvisoriesLoader ? X : never} T, X
|
|
24
|
+
* @param {{
|
|
25
|
+
* ignoredAdvisoriesFilePath: string
|
|
26
|
+
* }} params - Parameters for the factory method.
|
|
27
|
+
* @returns {InstanceType<T>} Instance of this class.
|
|
28
|
+
* @this {T}
|
|
29
|
+
* @public
|
|
30
|
+
*/
|
|
31
|
+
static create ({
|
|
32
|
+
ignoredAdvisoriesFilePath,
|
|
33
|
+
}) {
|
|
34
|
+
return /** @type {InstanceType<T>} */ (
|
|
35
|
+
new this({
|
|
36
|
+
ignoredAdvisoriesFilePath,
|
|
37
|
+
})
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* get: `node:url` module.
|
|
43
|
+
*
|
|
44
|
+
* @returns {typeof nodeUrl} `node:url` module.
|
|
45
|
+
*/
|
|
46
|
+
static get nodeUrl () {
|
|
47
|
+
return nodeUrl
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* get: Constructor of this class.
|
|
52
|
+
*
|
|
53
|
+
* @returns {typeof IgnoredAdvisoriesLoader} Constructor of this class.
|
|
54
|
+
*/
|
|
55
|
+
get Ctor () {
|
|
56
|
+
return /** @type {typeof IgnoredAdvisoriesLoader} */ (this.constructor)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Load the ignored advisories.
|
|
61
|
+
*
|
|
62
|
+
* @returns {Promise<Array<NpmAuditType.IgnoredAdvisory> | null>} Advisories, or null when the file cannot be loaded or its default export is not an array.
|
|
63
|
+
* @public
|
|
64
|
+
*/
|
|
65
|
+
async loadAdvisories () {
|
|
66
|
+
try {
|
|
67
|
+
const url = this.Ctor.nodeUrl.pathToFileURL(this.ignoredAdvisoriesFilePath)
|
|
68
|
+
|
|
69
|
+
const exported = await this.invokeImport({
|
|
70
|
+
href: url.href,
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
if (!Array.isArray(exported.default)) {
|
|
74
|
+
return null
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return exported.default
|
|
78
|
+
} catch (error) {
|
|
79
|
+
return null
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Import a module by the href of its URL.
|
|
85
|
+
*
|
|
86
|
+
* @param {{
|
|
87
|
+
* href: string
|
|
88
|
+
* }} params - Parameters.
|
|
89
|
+
* @returns {Promise<{ default?: * }>} Module namespace.
|
|
90
|
+
*/
|
|
91
|
+
async invokeImport ({
|
|
92
|
+
href,
|
|
93
|
+
}) {
|
|
94
|
+
return import(href)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import AuditFetcher from './AuditFetcher.js'
|
|
2
|
+
import AuditReportResponse from './AuditReportResponse.js'
|
|
3
|
+
import EXIT_CODE from './EXIT_CODE.js'
|
|
4
|
+
import ProcessLogger from './ProcessLogger.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Fetch an `npm audit` report, judge it against the advisories
|
|
8
|
+
* the caller lists, and print the outcome.
|
|
9
|
+
*/
|
|
10
|
+
export default class NpmAuditGate {
|
|
11
|
+
/**
|
|
12
|
+
* @param {{
|
|
13
|
+
* ignoredAdvisories: Array<NpmAuditType.IgnoredAdvisory>
|
|
14
|
+
* auditFetcher: AuditFetcher
|
|
15
|
+
* processLogger: ProcessLogger
|
|
16
|
+
* }} params - Parameters of this constructor.
|
|
17
|
+
*/
|
|
18
|
+
constructor ({
|
|
19
|
+
ignoredAdvisories,
|
|
20
|
+
auditFetcher,
|
|
21
|
+
processLogger,
|
|
22
|
+
}) {
|
|
23
|
+
this.ignoredAdvisories = ignoredAdvisories
|
|
24
|
+
this.auditFetcher = auditFetcher
|
|
25
|
+
this.processLogger = processLogger
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Factory method.
|
|
30
|
+
*
|
|
31
|
+
* @template {X extends typeof NpmAuditGate ? X : never} T, X
|
|
32
|
+
* @param {{
|
|
33
|
+
* ignoredAdvisories: Array<NpmAuditType.IgnoredAdvisory>
|
|
34
|
+
* auditFetcher?: AuditFetcher
|
|
35
|
+
* processLogger?: ProcessLogger
|
|
36
|
+
* }} params - Parameters for the factory method.
|
|
37
|
+
* @returns {InstanceType<T>} Instance of this class.
|
|
38
|
+
* @this {T}
|
|
39
|
+
* @public
|
|
40
|
+
*/
|
|
41
|
+
static create ({
|
|
42
|
+
ignoredAdvisories,
|
|
43
|
+
auditFetcher = this.createAuditFetcher(),
|
|
44
|
+
processLogger = this.createProcessLogger(),
|
|
45
|
+
}) {
|
|
46
|
+
return /** @type {InstanceType<T>} */ (
|
|
47
|
+
new this({
|
|
48
|
+
ignoredAdvisories,
|
|
49
|
+
auditFetcher,
|
|
50
|
+
processLogger,
|
|
51
|
+
})
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* get: Constructor of `AuditFetcher`.
|
|
57
|
+
*
|
|
58
|
+
* @returns {typeof AuditFetcher} Constructor of `AuditFetcher`.
|
|
59
|
+
*/
|
|
60
|
+
static get AuditFetcherCtor () {
|
|
61
|
+
return AuditFetcher
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* get: Constructor of `AuditReportResponse`.
|
|
66
|
+
*
|
|
67
|
+
* @returns {typeof AuditReportResponse} Constructor of `AuditReportResponse`.
|
|
68
|
+
*/
|
|
69
|
+
static get AuditReportResponseCtor () {
|
|
70
|
+
return AuditReportResponse
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* get: Constructor of `ProcessLogger`.
|
|
75
|
+
*
|
|
76
|
+
* @returns {typeof ProcessLogger} Constructor of `ProcessLogger`.
|
|
77
|
+
*/
|
|
78
|
+
static get ProcessLoggerCtor () {
|
|
79
|
+
return ProcessLogger
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Create an `AuditFetcher`.
|
|
84
|
+
*
|
|
85
|
+
* @returns {AuditFetcher} Instance of `AuditFetcher`.
|
|
86
|
+
*/
|
|
87
|
+
static createAuditFetcher () {
|
|
88
|
+
return this.AuditFetcherCtor.create()
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Create an `AuditReportResponse`.
|
|
93
|
+
*
|
|
94
|
+
* @param {{
|
|
95
|
+
* ignoredAdvisories: Array<NpmAuditType.IgnoredAdvisory>
|
|
96
|
+
* report: AuditReport | null
|
|
97
|
+
* }} params - Parameters.
|
|
98
|
+
* @returns {AuditReportResponse} Instance of `AuditReportResponse`.
|
|
99
|
+
*/
|
|
100
|
+
static createAuditReportResponse ({
|
|
101
|
+
ignoredAdvisories,
|
|
102
|
+
report,
|
|
103
|
+
}) {
|
|
104
|
+
return this.AuditReportResponseCtor.create({
|
|
105
|
+
ignoredAdvisories,
|
|
106
|
+
report,
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Create a `ProcessLogger`.
|
|
112
|
+
*
|
|
113
|
+
* @returns {ProcessLogger} Instance of `ProcessLogger`.
|
|
114
|
+
*/
|
|
115
|
+
static createProcessLogger () {
|
|
116
|
+
return this.ProcessLoggerCtor.create()
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* get: Constructor of this class.
|
|
121
|
+
*
|
|
122
|
+
* @returns {typeof NpmAuditGate} Constructor of this class.
|
|
123
|
+
*/
|
|
124
|
+
get Ctor () {
|
|
125
|
+
return /** @type {typeof NpmAuditGate} */ (this.constructor)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Fetch the report, judge it against the list, and print the outcome.
|
|
130
|
+
*
|
|
131
|
+
* @returns {number} Exit code: `EXIT_CODE.SUCCESS` when nothing fails, otherwise `EXIT_CODE.FAILURE`.
|
|
132
|
+
* @public
|
|
133
|
+
*/
|
|
134
|
+
auditAdvisories () {
|
|
135
|
+
const report = this.auditFetcher.fetchReport()
|
|
136
|
+
const auditReportResponse = this.Ctor.createAuditReportResponse({
|
|
137
|
+
ignoredAdvisories: this.ignoredAdvisories,
|
|
138
|
+
report,
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
if (!auditReportResponse.hasUsableReport()) {
|
|
142
|
+
this.processLogger.logError({
|
|
143
|
+
text: `${auditReportResponse.buildNoReportMessage()}\n`,
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
return EXIT_CODE.FAILURE
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
auditReportResponse.buildIgnoredLines()
|
|
150
|
+
.forEach(it => {
|
|
151
|
+
this.processLogger.logMessage({
|
|
152
|
+
text: `${it}\n`,
|
|
153
|
+
})
|
|
154
|
+
})
|
|
155
|
+
auditReportResponse.buildFailureLines()
|
|
156
|
+
.forEach(it => {
|
|
157
|
+
this.processLogger.logError({
|
|
158
|
+
text: `${it}\n`,
|
|
159
|
+
})
|
|
160
|
+
})
|
|
161
|
+
this.processLogger.logMessage({
|
|
162
|
+
text: `${auditReportResponse.buildSummaryLine()}\n`,
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
return auditReportResponse.countFailures() === 0
|
|
166
|
+
? EXIT_CODE.SUCCESS
|
|
167
|
+
: EXIT_CODE.FAILURE
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* @typedef {*} BooleanLike
|
|
173
|
+
*/
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* @import {
|
|
177
|
+
* AuditReport,
|
|
178
|
+
* } from './AuditFetcher.js'
|
|
179
|
+
*/
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the arguments the current process was started with.
|
|
3
|
+
*/
|
|
4
|
+
export default class ProcessArgumentsResolver {
|
|
5
|
+
/**
|
|
6
|
+
* @param {{
|
|
7
|
+
* actualArguments: Array<string>
|
|
8
|
+
* }} params - Parameters of this constructor.
|
|
9
|
+
*/
|
|
10
|
+
constructor ({
|
|
11
|
+
actualArguments,
|
|
12
|
+
}) {
|
|
13
|
+
this.actualArguments = actualArguments
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Factory method.
|
|
18
|
+
*
|
|
19
|
+
* The actual arguments are those that follow the script, so the executable and the script are left out.
|
|
20
|
+
*
|
|
21
|
+
* @template {X extends typeof ProcessArgumentsResolver ? X : never} T, X
|
|
22
|
+
* @param {{
|
|
23
|
+
* rawArguments: Array<string>
|
|
24
|
+
* }} params - Parameters for the factory method.
|
|
25
|
+
* @returns {InstanceType<T>} Instance of this class.
|
|
26
|
+
* @this {T}
|
|
27
|
+
* @public
|
|
28
|
+
*/
|
|
29
|
+
static create ({
|
|
30
|
+
rawArguments,
|
|
31
|
+
}) {
|
|
32
|
+
const actualArguments = rawArguments.slice(2)
|
|
33
|
+
|
|
34
|
+
return /** @type {InstanceType<T>} */ (
|
|
35
|
+
new this({
|
|
36
|
+
actualArguments,
|
|
37
|
+
})
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Extract the argument that names the file listing the ignored advisories.
|
|
43
|
+
*
|
|
44
|
+
* @returns {string | null} Argument, or null when it is not given.
|
|
45
|
+
* @public
|
|
46
|
+
*/
|
|
47
|
+
extractIgnoredAdvisoriesFilePathArgument () {
|
|
48
|
+
return this.extractArgumentByIndex({
|
|
49
|
+
index: 0,
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Extract the argument at an index.
|
|
55
|
+
*
|
|
56
|
+
* @param {{
|
|
57
|
+
* index: number
|
|
58
|
+
* }} params - Parameters.
|
|
59
|
+
* @returns {string | null} Argument, or null when there is none at the index.
|
|
60
|
+
*/
|
|
61
|
+
extractArgumentByIndex ({
|
|
62
|
+
index,
|
|
63
|
+
}) {
|
|
64
|
+
return this.actualArguments.at(index)
|
|
65
|
+
?? null
|
|
66
|
+
}
|
|
67
|
+
}
|