v11y-check 0.9.0 → 0.10.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/README.md +3 -9
- package/dist/esm/.cache +1 -1
- package/dist/esm/bin/route.js +105 -103
- package/dist/esm/bin/v11y.js +7 -7
- package/dist/esm/cli.d.ts +1 -1
- package/dist/esm/cli.js +209 -207
- package/dist/esm/config.d.ts +1 -1
- package/dist/esm/config.js +36 -39
- package/dist/esm/format-actions.d.ts +2 -2
- package/dist/esm/format-actions.js +28 -29
- package/dist/esm/format-check.d.ts +1 -1
- package/dist/esm/format-check.js +101 -100
- package/dist/esm/format-summary.d.ts +2 -2
- package/dist/esm/format-summary.js +11 -11
- package/dist/esm/format-time.js +34 -29
- package/dist/esm/format-verbose.d.ts +2 -2
- package/dist/esm/format-verbose.js +119 -111
- package/dist/esm/generate.d.ts +1 -1
- package/dist/esm/generate.js +23 -26
- package/dist/esm/index.d.ts +5 -5
- package/dist/esm/index.js +3 -20
- package/dist/esm/init/initCommand.js +25 -27
- package/dist/esm/init/scaffold.js +36 -40
- package/dist/esm/init/templates.js +9 -16
- package/dist/esm/run-audit.d.ts +1 -1
- package/dist/esm/run-audit.js +145 -145
- package/dist/esm/sync.d.ts +1 -1
- package/dist/esm/sync.js +77 -83
- package/dist/esm/tmp.js +11 -13
- package/dist/esm/types.js +34 -37
- package/package.json +2 -2
package/dist/esm/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import process from
|
|
2
|
-
import { reportError } from
|
|
1
|
+
import process from 'node:process';
|
|
2
|
+
import { reportError } from '@williamthorsen/nmr-core';
|
|
3
3
|
import { loadConfig } from "./config.js";
|
|
4
4
|
import { formatCheckJson, formatCheckText } from "./format-check.js";
|
|
5
5
|
import { formatCheckVerboseText } from "./format-verbose.js";
|
|
@@ -9,217 +9,219 @@ import { parseAuditCiOutput, runAudit, runReport } from "./run-audit.js";
|
|
|
9
9
|
import { syncAllowlist } from "./sync.js";
|
|
10
10
|
import { withTempDir } from "./tmp.js";
|
|
11
11
|
import { AUDIT_SCOPES, isSeverityAtOrAbove } from "./types.js";
|
|
12
|
-
function extractMessage(error) {
|
|
13
|
-
|
|
12
|
+
export function extractMessage(error) {
|
|
13
|
+
return error instanceof Error ? error.message : String(error);
|
|
14
14
|
}
|
|
15
|
-
async function auditCommand(options) {
|
|
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
|
-
|
|
15
|
+
export async function auditCommand(options) {
|
|
16
|
+
let loaded;
|
|
17
|
+
try {
|
|
18
|
+
loaded = await loadConfig(options.configPath);
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
reportError(extractMessage(error));
|
|
22
|
+
return 1;
|
|
23
|
+
}
|
|
24
|
+
const { config } = loaded;
|
|
25
|
+
const scopes = options.scopes.length > 0 ? options.scopes : [...AUDIT_SCOPES];
|
|
26
|
+
try {
|
|
27
|
+
return await withTempDir(async (tempDir) => {
|
|
28
|
+
let exitCode = 0;
|
|
29
|
+
const allResults = [];
|
|
30
|
+
for (const scope of scopes) {
|
|
31
|
+
const configPath = await generateAuditCiConfig(config[scope], scope, tempDir);
|
|
32
|
+
const result = runAudit({ configPath, json: options.json });
|
|
33
|
+
for (const warning of result.warnings) {
|
|
34
|
+
process.stderr.write(`warning: ${warning}\n`);
|
|
35
|
+
}
|
|
36
|
+
if (result.staleEntries.length > 0) {
|
|
37
|
+
process.stderr.write(`warning: stale allowlist entries in ${scope}: ${result.staleEntries.join(', ')}\n`);
|
|
38
|
+
}
|
|
39
|
+
if (options.json) {
|
|
40
|
+
const parsed = parseAuditCiOutput(result.stdout);
|
|
41
|
+
for (const warning of parsed.warnings) {
|
|
42
|
+
process.stderr.write(`warning: ${warning}\n`);
|
|
43
|
+
}
|
|
44
|
+
for (const r of parsed.results) {
|
|
45
|
+
if (!allResults.some((existing) => existing.id === r.id)) {
|
|
46
|
+
allResults.push(r);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
if (result.stdout.length > 0) {
|
|
52
|
+
process.stdout.write(result.stdout);
|
|
53
|
+
}
|
|
54
|
+
if (result.stderr.length > 0) {
|
|
55
|
+
process.stderr.write(result.stderr);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (result.exitCode !== 0) {
|
|
59
|
+
exitCode = result.exitCode;
|
|
60
|
+
}
|
|
49
61
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
exitCode = result.exitCode;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
if (options.json) {
|
|
64
|
-
process.stdout.write(JSON.stringify(allResults, null, 2) + "\n");
|
|
65
|
-
}
|
|
66
|
-
return exitCode;
|
|
67
|
-
});
|
|
68
|
-
} catch (error) {
|
|
69
|
-
reportError(extractMessage(error));
|
|
70
|
-
return 1;
|
|
71
|
-
}
|
|
62
|
+
if (options.json) {
|
|
63
|
+
process.stdout.write(JSON.stringify(allResults, null, 2) + '\n');
|
|
64
|
+
}
|
|
65
|
+
return exitCode;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
reportError(extractMessage(error));
|
|
70
|
+
return 1;
|
|
71
|
+
}
|
|
72
72
|
}
|
|
73
|
-
const CHECK_SCOPE_ORDER = [
|
|
74
|
-
async function checkCommand(options) {
|
|
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
|
-
|
|
73
|
+
const CHECK_SCOPE_ORDER = ['prod', 'dev'];
|
|
74
|
+
export async function checkCommand(options) {
|
|
75
|
+
let loaded;
|
|
76
|
+
try {
|
|
77
|
+
loaded = await loadConfig(options.configPath);
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
reportError(extractMessage(error));
|
|
81
|
+
return 1;
|
|
82
|
+
}
|
|
83
|
+
const { config } = loaded;
|
|
84
|
+
const requestedScopes = options.scopes.length > 0 ? options.scopes : [...CHECK_SCOPE_ORDER];
|
|
85
|
+
const scopes = CHECK_SCOPE_ORDER.filter((s) => requestedScopes.includes(s));
|
|
86
|
+
try {
|
|
87
|
+
return await withTempDir(async (tempDir) => {
|
|
88
|
+
const checkResult = {
|
|
89
|
+
dev: { allowed: [], belowThreshold: [], stale: [], unallowed: [] },
|
|
90
|
+
prod: { allowed: [], belowThreshold: [], stale: [], unallowed: [] },
|
|
91
|
+
};
|
|
92
|
+
const thresholds = {};
|
|
93
|
+
for (const scope of scopes) {
|
|
94
|
+
const scopeConfig = config[scope];
|
|
95
|
+
const effectiveThreshold = scopeConfig.severityThreshold ?? 'low';
|
|
96
|
+
thresholds[scope] = effectiveThreshold;
|
|
97
|
+
const strippedScope = { ...scopeConfig, allowlist: [], severityThreshold: 'low' };
|
|
98
|
+
const strippedConfigPath = await generateAuditCiConfig(strippedScope, scope, tempDir);
|
|
99
|
+
const reportOptions = { configPath: strippedConfigPath };
|
|
100
|
+
if (options.verbose) {
|
|
101
|
+
reportOptions.reportType = 'full';
|
|
102
|
+
}
|
|
103
|
+
const report = runReport(reportOptions);
|
|
104
|
+
for (const warning of report.warnings) {
|
|
105
|
+
process.stderr.write(`warning: ${warning}\n`);
|
|
106
|
+
}
|
|
107
|
+
if (report.stderr.length > 0) {
|
|
108
|
+
process.stderr.write(report.stderr);
|
|
109
|
+
}
|
|
110
|
+
const allowedIds = new Set(scopeConfig.allowlist.map((entry) => entry.id));
|
|
111
|
+
const foundIds = new Set(report.results.map((r) => r.id));
|
|
112
|
+
const allowlistById = new Map(scopeConfig.allowlist.map((entry) => [entry.id, entry]));
|
|
113
|
+
const scopeResult = { allowed: [], belowThreshold: [], stale: [], unallowed: [] };
|
|
114
|
+
for (const result of report.results) {
|
|
115
|
+
if (!isSeverityAtOrAbove(result.severity, effectiveThreshold)) {
|
|
116
|
+
scopeResult.belowThreshold.push(result);
|
|
117
|
+
}
|
|
118
|
+
else if (allowedIds.has(result.id)) {
|
|
119
|
+
const entry = allowlistById.get(result.id);
|
|
120
|
+
scopeResult.allowed.push(buildAllowedVuln(result, entry));
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
scopeResult.unallowed.push(result);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
for (const entry of scopeConfig.allowlist) {
|
|
127
|
+
if (!foundIds.has(entry.id)) {
|
|
128
|
+
scopeResult.stale.push({ id: entry.id });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
checkResult[scope] = scopeResult;
|
|
132
|
+
}
|
|
133
|
+
if (options.verbose && !options.json) {
|
|
134
|
+
process.stdout.write(formatCheckVerboseText(checkResult, scopes, undefined, thresholds));
|
|
135
|
+
}
|
|
136
|
+
else if (options.json) {
|
|
137
|
+
process.stdout.write(formatCheckJson(checkResult, scopes));
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
process.stdout.write(formatCheckText(checkResult, scopes, undefined, thresholds));
|
|
141
|
+
}
|
|
142
|
+
const hasUnallowed = scopes.some((s) => checkResult[s].unallowed.length > 0);
|
|
143
|
+
return hasUnallowed ? 1 : 0;
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
reportError(extractMessage(error));
|
|
148
|
+
return 1;
|
|
149
|
+
}
|
|
145
150
|
}
|
|
146
|
-
async function syncCommand(options) {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
} catch (error) {
|
|
151
|
-
reportError(extractMessage(error));
|
|
152
|
-
return 1;
|
|
153
|
-
}
|
|
154
|
-
const { configSource } = loaded;
|
|
155
|
-
if (configSource === "defaults") {
|
|
156
|
-
const { configResult } = scaffoldConfig({ dryRun: false, force: false });
|
|
157
|
-
if (configResult.outcome === "failed") {
|
|
158
|
-
reportError("Failed to create config file");
|
|
159
|
-
return 1;
|
|
151
|
+
export async function syncCommand(options) {
|
|
152
|
+
let loaded;
|
|
153
|
+
try {
|
|
154
|
+
loaded = await loadConfig(options.configPath);
|
|
160
155
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
const report = runReport({ configPath: strippedConfigPath });
|
|
172
|
-
for (const warning of report.warnings) {
|
|
173
|
-
process.stderr.write(`warning: ${warning}
|
|
174
|
-
`);
|
|
175
|
-
}
|
|
176
|
-
const { syncResult, updatedConfig } = await syncAllowlist(config, scope, report.results, configFilePath);
|
|
177
|
-
config = updatedConfig;
|
|
178
|
-
if (options.json) {
|
|
179
|
-
process.stdout.write(JSON.stringify(syncResult, null, 2) + "\n");
|
|
180
|
-
} else {
|
|
181
|
-
process.stdout.write(`
|
|
182
|
-
--- ${scope} ---
|
|
183
|
-
`);
|
|
184
|
-
process.stdout.write(` added: ${syncResult.added.length}
|
|
185
|
-
`);
|
|
186
|
-
process.stdout.write(` kept: ${syncResult.kept.length}
|
|
187
|
-
`);
|
|
188
|
-
process.stdout.write(` removed: ${syncResult.removed.length}
|
|
189
|
-
`);
|
|
156
|
+
catch (error) {
|
|
157
|
+
reportError(extractMessage(error));
|
|
158
|
+
return 1;
|
|
159
|
+
}
|
|
160
|
+
const { configSource } = loaded;
|
|
161
|
+
if (configSource === 'defaults') {
|
|
162
|
+
const { configResult } = scaffoldConfig({ dryRun: false, force: false });
|
|
163
|
+
if (configResult.outcome === 'failed') {
|
|
164
|
+
reportError('Failed to create config file');
|
|
165
|
+
return 1;
|
|
190
166
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
167
|
+
loaded = await loadConfig(options.configPath);
|
|
168
|
+
}
|
|
169
|
+
let { config } = loaded;
|
|
170
|
+
const { configFilePath } = loaded;
|
|
171
|
+
const scopes = options.scopes.length > 0 ? options.scopes : [...AUDIT_SCOPES];
|
|
172
|
+
try {
|
|
173
|
+
return await withTempDir(async (tempDir) => {
|
|
174
|
+
for (const scope of scopes) {
|
|
175
|
+
const strippedScope = { ...config[scope], allowlist: [] };
|
|
176
|
+
const strippedConfigPath = await generateAuditCiConfig(strippedScope, scope, tempDir);
|
|
177
|
+
const report = runReport({ configPath: strippedConfigPath });
|
|
178
|
+
for (const warning of report.warnings) {
|
|
179
|
+
process.stderr.write(`warning: ${warning}\n`);
|
|
180
|
+
}
|
|
181
|
+
const { syncResult, updatedConfig } = await syncAllowlist(config, scope, report.results, configFilePath);
|
|
182
|
+
config = updatedConfig;
|
|
183
|
+
if (options.json) {
|
|
184
|
+
process.stdout.write(JSON.stringify(syncResult, null, 2) + '\n');
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
process.stdout.write(`\n--- ${scope} ---\n`);
|
|
188
|
+
process.stdout.write(` added: ${syncResult.added.length}\n`);
|
|
189
|
+
process.stdout.write(` kept: ${syncResult.kept.length}\n`);
|
|
190
|
+
process.stdout.write(` removed: ${syncResult.removed.length}\n`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (configSource === 'defaults') {
|
|
194
|
+
const totalEntries = config.dev.allowlist.length + config.prod.allowlist.length;
|
|
195
|
+
process.stdout.write(`Created config at ${configFilePath} with ${totalEntries} allowed entries.\n`);
|
|
196
|
+
}
|
|
197
|
+
return 0;
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
reportError(extractMessage(error));
|
|
202
|
+
return 1;
|
|
203
|
+
}
|
|
203
204
|
}
|
|
204
205
|
function buildAllowedVuln(result, entry) {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
206
|
+
const allowed = {
|
|
207
|
+
id: result.id,
|
|
208
|
+
path: result.path,
|
|
209
|
+
paths: result.paths,
|
|
210
|
+
url: result.url,
|
|
211
|
+
};
|
|
212
|
+
if (result.ghsaId !== undefined)
|
|
213
|
+
allowed.ghsaId = result.ghsaId;
|
|
214
|
+
if (result.severity !== undefined)
|
|
215
|
+
allowed.severity = result.severity;
|
|
216
|
+
if (result.title !== undefined)
|
|
217
|
+
allowed.title = result.title;
|
|
218
|
+
if (result.description !== undefined)
|
|
219
|
+
allowed.description = result.description;
|
|
220
|
+
if (result.cvss !== undefined)
|
|
221
|
+
allowed.cvss = result.cvss;
|
|
222
|
+
if (entry?.reason !== undefined)
|
|
223
|
+
allowed.reason = entry.reason;
|
|
224
|
+
if (entry?.addedAt !== undefined)
|
|
225
|
+
allowed.addedAt = entry.addedAt;
|
|
226
|
+
return allowed;
|
|
219
227
|
}
|
|
220
|
-
export {
|
|
221
|
-
auditCommand,
|
|
222
|
-
checkCommand,
|
|
223
|
-
extractMessage,
|
|
224
|
-
syncCommand
|
|
225
|
-
};
|
package/dist/esm/config.d.ts
CHANGED
package/dist/esm/config.js
CHANGED
|
@@ -1,41 +1,38 @@
|
|
|
1
|
-
import { existsSync } from
|
|
2
|
-
import { readFile } from
|
|
3
|
-
import path from
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
4
|
import { DEFAULT_CONFIG, v11yCheckConfigSchema } from "./types.js";
|
|
5
|
-
const DEFAULT_CONFIG_PATH =
|
|
6
|
-
async function loadConfig(configPath, cwd) {
|
|
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
|
-
const
|
|
33
|
-
|
|
34
|
-
${
|
|
35
|
-
|
|
36
|
-
|
|
5
|
+
export const DEFAULT_CONFIG_PATH = '.config/v11y-check.config.json';
|
|
6
|
+
export async function loadConfig(configPath, cwd) {
|
|
7
|
+
const resolvedCwd = cwd ?? process.cwd();
|
|
8
|
+
const filePath = path.resolve(resolvedCwd, configPath ?? DEFAULT_CONFIG_PATH);
|
|
9
|
+
const configDir = path.dirname(filePath);
|
|
10
|
+
if (configPath === undefined && !existsSync(filePath)) {
|
|
11
|
+
return {
|
|
12
|
+
config: DEFAULT_CONFIG,
|
|
13
|
+
configDir,
|
|
14
|
+
configFilePath: filePath,
|
|
15
|
+
configSource: 'defaults',
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
let raw;
|
|
19
|
+
try {
|
|
20
|
+
raw = await readFile(filePath, 'utf8');
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
throw new Error(`Config file not found: ${filePath}`);
|
|
24
|
+
}
|
|
25
|
+
let parsed;
|
|
26
|
+
try {
|
|
27
|
+
parsed = JSON.parse(raw);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
throw new Error(`Config file is not valid JSON: ${filePath}`);
|
|
31
|
+
}
|
|
32
|
+
const result = v11yCheckConfigSchema.safeParse(parsed);
|
|
33
|
+
if (!result.success) {
|
|
34
|
+
const issues = result.error.issues.map((issue) => ` - ${issue.path.join('.')}: ${issue.message}`).join('\n');
|
|
35
|
+
throw new Error(`Invalid config in ${filePath}:\n${issues}`);
|
|
36
|
+
}
|
|
37
|
+
return { config: result.data, configDir, configFilePath: filePath, configSource: 'file' };
|
|
37
38
|
}
|
|
38
|
-
export {
|
|
39
|
-
DEFAULT_CONFIG_PATH,
|
|
40
|
-
loadConfig
|
|
41
|
-
};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type { CheckResult } from './format-check.
|
|
2
|
-
import type { AuditScope } from './types.
|
|
1
|
+
import type { CheckResult } from './format-check.js';
|
|
2
|
+
import type { AuditScope } from './types.js';
|
|
3
3
|
export declare function formatActionHints(result: CheckResult, scopes: AuditScope[]): string;
|
|
@@ -1,34 +1,33 @@
|
|
|
1
|
-
const HINT_ADD =
|
|
2
|
-
const HINT_REMOVE =
|
|
3
|
-
const HINT_BOTH =
|
|
1
|
+
const HINT_ADD = 'Run `v11y sync` to add the listed vulnerabilities to the allowlist.';
|
|
2
|
+
const HINT_REMOVE = 'Run `v11y sync` to remove stale allowlist entries.';
|
|
3
|
+
const HINT_BOTH = 'Run `v11y sync` to add the listed vulnerabilities to the allowlist and remove stale entries.';
|
|
4
4
|
function buildVerboseFlag(scopes) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
if (scopes.length === 1) {
|
|
6
|
+
return `--${scopes[0]} --verbose`;
|
|
7
|
+
}
|
|
8
|
+
return '--verbose';
|
|
9
9
|
}
|
|
10
10
|
function selectSyncHint(hasUnallowed, hasStale) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
if (hasUnallowed && hasStale)
|
|
12
|
+
return HINT_BOTH;
|
|
13
|
+
if (hasUnallowed)
|
|
14
|
+
return HINT_ADD;
|
|
15
|
+
return HINT_REMOVE;
|
|
14
16
|
}
|
|
15
|
-
function formatActionHints(result, scopes) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
${bullets.join(
|
|
17
|
+
export function formatActionHints(result, scopes) {
|
|
18
|
+
const hasUnallowed = scopes.some((scope) => result[scope].unallowed.length > 0);
|
|
19
|
+
const hasAllowed = scopes.some((scope) => result[scope].allowed.length > 0);
|
|
20
|
+
const hasStale = scopes.some((scope) => result[scope].stale.length > 0);
|
|
21
|
+
const hasVulns = hasUnallowed || hasAllowed;
|
|
22
|
+
const needsSync = hasUnallowed || hasStale;
|
|
23
|
+
if (!hasVulns && !needsSync)
|
|
24
|
+
return '';
|
|
25
|
+
const bullets = [];
|
|
26
|
+
if (hasVulns) {
|
|
27
|
+
bullets.push(` \u{2022} Run \`v11y check ${buildVerboseFlag(scopes)}\` for full report`);
|
|
28
|
+
}
|
|
29
|
+
if (needsSync) {
|
|
30
|
+
bullets.push(` \u{2022} ${selectSyncHint(hasUnallowed, hasStale)}`);
|
|
31
|
+
}
|
|
32
|
+
return ` Actions:\n${bullets.join('\n')}`;
|
|
31
33
|
}
|
|
32
|
-
export {
|
|
33
|
-
formatActionHints
|
|
34
|
-
};
|