@zappie/check 0.2.0 → 0.2.1
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/CHANGELOG.md +8 -0
- package/README.md +22 -0
- package/dist/index.d.ts +42 -4
- package/dist/index.js +703 -149
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/schema/check-result.v1.json +30 -1
package/dist/index.js
CHANGED
|
@@ -42,7 +42,7 @@ function distance(left, right) {
|
|
|
42
42
|
}
|
|
43
43
|
return row[right.length] ?? Infinity;
|
|
44
44
|
}
|
|
45
|
-
function unknownKeys(value, allowed,
|
|
45
|
+
function unknownKeys(value, allowed, path15, issue) {
|
|
46
46
|
for (const key of Object.keys(value)) {
|
|
47
47
|
if (allowed.includes(key)) {
|
|
48
48
|
continue;
|
|
@@ -50,7 +50,7 @@ function unknownKeys(value, allowed, path14, issue) {
|
|
|
50
50
|
const closest = allowed.map((candidate) => ({ candidate, score: distance(key, candidate) })).sort((left, right) => left.score - right.score)[0];
|
|
51
51
|
const hint = closest && closest.score <= 2 ? closest.candidate : void 0;
|
|
52
52
|
issue({
|
|
53
|
-
path:
|
|
53
|
+
path: path15 ? `${path15}.${key}` : key,
|
|
54
54
|
code: "unknown_key",
|
|
55
55
|
message: hint ? `Unknown field "${key}". Rename it to "${hint}".` : `Unknown field "${key}". Remove it from the configuration.`,
|
|
56
56
|
severity: "error"
|
|
@@ -76,20 +76,20 @@ function hostname(value) {
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
// ../sdk/src/site-config/sections.ts
|
|
79
|
-
function nested(value,
|
|
79
|
+
function nested(value, path15, keys, issue, validate) {
|
|
80
80
|
if (value === void 0) {
|
|
81
81
|
return;
|
|
82
82
|
}
|
|
83
83
|
if (!record(value)) {
|
|
84
84
|
issue({
|
|
85
|
-
path:
|
|
85
|
+
path: path15,
|
|
86
86
|
code: "invalid_type",
|
|
87
|
-
message: `Expected an object. Set ${
|
|
87
|
+
message: `Expected an object. Set ${path15} to an object or remove it.`,
|
|
88
88
|
severity: "error"
|
|
89
89
|
});
|
|
90
90
|
return;
|
|
91
91
|
}
|
|
92
|
-
unknownKeys(value, keys,
|
|
92
|
+
unknownKeys(value, keys, path15, issue);
|
|
93
93
|
validate(value);
|
|
94
94
|
}
|
|
95
95
|
function budgets(value, issue, error) {
|
|
@@ -126,18 +126,18 @@ function budgets(value, issue, error) {
|
|
|
126
126
|
`Expected a positive finite number. Set budgets.${key} to a number greater than zero.`
|
|
127
127
|
);
|
|
128
128
|
} else if (amount > DEFAULT_BUDGETS[key]) {
|
|
129
|
-
const
|
|
129
|
+
const message6 = `The ${key} budget exceeds the ${DEFAULT_BUDGETS[key]} default.`;
|
|
130
130
|
if (budget.override === void 0) {
|
|
131
131
|
error(
|
|
132
132
|
`budgets.${key}`,
|
|
133
133
|
"budget_override",
|
|
134
|
-
`${
|
|
134
|
+
`${message6} Add budgets.override.reason to explain the increase.`
|
|
135
135
|
);
|
|
136
136
|
} else {
|
|
137
137
|
issue({
|
|
138
138
|
path: `budgets.${key}`,
|
|
139
139
|
code: "budget_override",
|
|
140
|
-
message: `${
|
|
140
|
+
message: `${message6} Lower it to the default when the exception is no longer needed.`,
|
|
141
141
|
severity: "warning"
|
|
142
142
|
});
|
|
143
143
|
}
|
|
@@ -181,6 +181,7 @@ var rootKeys = [
|
|
|
181
181
|
"prerender",
|
|
182
182
|
"menu",
|
|
183
183
|
"packages",
|
|
184
|
+
"audit",
|
|
184
185
|
"outbound",
|
|
185
186
|
"budgets",
|
|
186
187
|
"readiness",
|
|
@@ -189,7 +190,7 @@ var rootKeys = [
|
|
|
189
190
|
function validateConfig(input) {
|
|
190
191
|
const issues = [];
|
|
191
192
|
const issue = (value) => issues.push(value);
|
|
192
|
-
const error = (
|
|
193
|
+
const error = (path15, code, message6) => issue({ path: path15, code, message: message6, severity: "error" });
|
|
193
194
|
if (!record(input)) {
|
|
194
195
|
return {
|
|
195
196
|
valid: false,
|
|
@@ -284,12 +285,12 @@ function validateConfig(input) {
|
|
|
284
285
|
issues
|
|
285
286
|
};
|
|
286
287
|
}
|
|
287
|
-
function requiredSlug(value,
|
|
288
|
+
function requiredSlug(value, path15, error) {
|
|
288
289
|
if (!nonblank(value)) {
|
|
289
290
|
error(
|
|
290
|
-
|
|
291
|
+
path15,
|
|
291
292
|
value === void 0 ? "missing_required" : "invalid_type",
|
|
292
|
-
`Expected a non-empty storefront slug. Set ${
|
|
293
|
+
`Expected a non-empty storefront slug. Set ${path15} to the slug used by createZappie().`
|
|
293
294
|
);
|
|
294
295
|
}
|
|
295
296
|
}
|
|
@@ -308,29 +309,29 @@ function theme(value, error) {
|
|
|
308
309
|
);
|
|
309
310
|
}
|
|
310
311
|
}
|
|
311
|
-
function stringList(value,
|
|
312
|
+
function stringList(value, path15, error, options = {}) {
|
|
312
313
|
if (value === void 0) {
|
|
313
314
|
return void 0;
|
|
314
315
|
}
|
|
315
316
|
if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) {
|
|
316
317
|
error(
|
|
317
|
-
|
|
318
|
+
path15,
|
|
318
319
|
"invalid_type",
|
|
319
|
-
`Expected an array of strings. Set ${
|
|
320
|
+
`Expected an array of strings. Set ${path15} to string values.`
|
|
320
321
|
);
|
|
321
322
|
return void 0;
|
|
322
323
|
}
|
|
323
324
|
if (options.nonempty && value.length === 0) {
|
|
324
325
|
error(
|
|
325
|
-
|
|
326
|
+
path15,
|
|
326
327
|
"invalid_format",
|
|
327
|
-
`Expected at least one value. Add a value to ${
|
|
328
|
+
`Expected at least one value. Add a value to ${path15} or remove the field.`
|
|
328
329
|
);
|
|
329
330
|
}
|
|
330
331
|
value.forEach((entry, index) => {
|
|
331
332
|
if (options.slug && !nonblank(entry)) {
|
|
332
333
|
error(
|
|
333
|
-
`${
|
|
334
|
+
`${path15}[${index}]`,
|
|
334
335
|
"invalid_format",
|
|
335
336
|
"Expected a non-empty branch slug. Remove the empty value or use a branch slug."
|
|
336
337
|
);
|
|
@@ -338,17 +339,17 @@ function stringList(value, path14, error, options = {}) {
|
|
|
338
339
|
});
|
|
339
340
|
if (options.unique && new Set(value).size !== value.length) {
|
|
340
341
|
error(
|
|
341
|
-
|
|
342
|
+
path15,
|
|
342
343
|
"duplicate",
|
|
343
|
-
`Expected unique values. Remove duplicates from ${
|
|
344
|
+
`Expected unique values. Remove duplicates from ${path15}.`
|
|
344
345
|
);
|
|
345
346
|
}
|
|
346
347
|
return value;
|
|
347
348
|
}
|
|
348
349
|
function paths(value, error) {
|
|
349
350
|
const values = stringList(value, "prerender", error, { unique: true });
|
|
350
|
-
values?.forEach((
|
|
351
|
-
if (!
|
|
351
|
+
values?.forEach((path15, index) => {
|
|
352
|
+
if (!path15.startsWith("/")) {
|
|
352
353
|
error(
|
|
353
354
|
`prerender[${index}]`,
|
|
354
355
|
"invalid_format",
|
|
@@ -851,11 +852,11 @@ var record2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
|
851
852
|
var includes = (values, key) => values.includes(key);
|
|
852
853
|
function validateTheme(input) {
|
|
853
854
|
const issues = [];
|
|
854
|
-
const issue = (
|
|
855
|
-
const keys = (obj, allowed,
|
|
855
|
+
const issue = (path15, code, message6, severity = "error") => issues.push({ path: path15, code, message: message6, severity });
|
|
856
|
+
const keys = (obj, allowed, path15) => {
|
|
856
857
|
for (const key of Object.keys(obj)) {
|
|
857
858
|
if (!allowed.includes(key)) {
|
|
858
|
-
issue(
|
|
859
|
+
issue(path15 ? `${path15}.${key}` : key, "invalid_theme", "Unknown field");
|
|
859
860
|
}
|
|
860
861
|
}
|
|
861
862
|
};
|
|
@@ -916,9 +917,9 @@ function validateTheme(input) {
|
|
|
916
917
|
}
|
|
917
918
|
}
|
|
918
919
|
for (const [key, value] of Object.entries(values)) {
|
|
919
|
-
const
|
|
920
|
+
const path15 = `${group}.${key}`;
|
|
920
921
|
if (!includes(TOKEN_NAMES, key)) {
|
|
921
|
-
issue(
|
|
922
|
+
issue(path15, "unknown_token", "Unknown semantic token");
|
|
922
923
|
continue;
|
|
923
924
|
}
|
|
924
925
|
let code, valid = false;
|
|
@@ -936,7 +937,7 @@ function validateTheme(input) {
|
|
|
936
937
|
valid = typeof value === "string" && safeValue(value) && (includes(DURATION_TOKENS, key) ? isDuration(value) : isEasing(value));
|
|
937
938
|
}
|
|
938
939
|
if (!valid) {
|
|
939
|
-
issue(
|
|
940
|
+
issue(path15, code, "Invalid or unsafe token value");
|
|
940
941
|
}
|
|
941
942
|
}
|
|
942
943
|
}
|
|
@@ -946,30 +947,30 @@ function validateTheme(input) {
|
|
|
946
947
|
} else {
|
|
947
948
|
const roles = /* @__PURE__ */ new Set();
|
|
948
949
|
input.fonts.forEach((font, i) => {
|
|
949
|
-
const
|
|
950
|
+
const path15 = `fonts[${i}]`;
|
|
950
951
|
if (!record2(font)) {
|
|
951
|
-
issue(
|
|
952
|
+
issue(path15, "invalid_font", "Expected a font object");
|
|
952
953
|
return;
|
|
953
954
|
}
|
|
954
|
-
keys(font, ["family", "source", "url", "weights", "role"],
|
|
955
|
+
keys(font, ["family", "source", "url", "weights", "role"], path15);
|
|
955
956
|
if (typeof font.family !== "string" || !isFontFamily(font.family) || font.family.includes(",")) {
|
|
956
957
|
issue(
|
|
957
|
-
`${
|
|
958
|
+
`${path15}.family`,
|
|
958
959
|
"invalid_font",
|
|
959
960
|
"Expected one unquoted font family"
|
|
960
961
|
);
|
|
961
962
|
}
|
|
962
963
|
if (typeof font.source !== "string" || !["google", "self", "system"].includes(font.source)) {
|
|
963
|
-
issue(`${
|
|
964
|
+
issue(`${path15}.source`, "invalid_font", "Unknown font source");
|
|
964
965
|
}
|
|
965
966
|
if (typeof font.role !== "string" || !["display", "body", "mono"].includes(font.role) || roles.has(font.role)) {
|
|
966
|
-
issue(`${
|
|
967
|
+
issue(`${path15}.role`, "invalid_font", "Expected a unique font role");
|
|
967
968
|
}
|
|
968
969
|
roles.add(String(font.role));
|
|
969
970
|
if (font.source === "self" || font.url !== void 0) {
|
|
970
971
|
if (typeof font.url !== "string" || !isAssetUrl(font.url, false) || !new URL(font.url).pathname.endsWith(".woff2")) {
|
|
971
972
|
issue(
|
|
972
|
-
`${
|
|
973
|
+
`${path15}.url`,
|
|
973
974
|
"invalid_font",
|
|
974
975
|
"Expected an HTTPS .woff2 URL"
|
|
975
976
|
);
|
|
@@ -979,7 +980,7 @@ function validateTheme(input) {
|
|
|
979
980
|
(w) => typeof w !== "number" || !Number.isInteger(w) || w < 1 || w > 1e3
|
|
980
981
|
))) {
|
|
981
982
|
issue(
|
|
982
|
-
`${
|
|
983
|
+
`${path15}.weights`,
|
|
983
984
|
"invalid_font",
|
|
984
985
|
"Expected 1\u20139 integer weights between 1 and 1000"
|
|
985
986
|
);
|
|
@@ -1089,13 +1090,13 @@ function escape(value) {
|
|
|
1089
1090
|
}
|
|
1090
1091
|
|
|
1091
1092
|
// src/result.ts
|
|
1092
|
-
function problem(
|
|
1093
|
-
return { ...details, message:
|
|
1093
|
+
function problem(message6, fix, details = {}) {
|
|
1094
|
+
return { ...details, message: message6, fix };
|
|
1094
1095
|
}
|
|
1095
1096
|
function step(name, result, problems = []) {
|
|
1096
1097
|
return { name, result, problems };
|
|
1097
1098
|
}
|
|
1098
|
-
var skipped = (name,
|
|
1099
|
+
var skipped = (name, message6) => step(name, "skipped", [problem(message6, null)]);
|
|
1099
1100
|
|
|
1100
1101
|
// src/config.ts
|
|
1101
1102
|
function supportsTypeScriptImport(version = process.versions.node) {
|
|
@@ -1318,22 +1319,500 @@ async function installedManifest(cwd, name) {
|
|
|
1318
1319
|
}
|
|
1319
1320
|
}
|
|
1320
1321
|
|
|
1322
|
+
// src/audit.ts
|
|
1323
|
+
import { access as access2, readFile as readFile4 } from "fs/promises";
|
|
1324
|
+
import path4 from "path";
|
|
1325
|
+
|
|
1326
|
+
// src/command.ts
|
|
1327
|
+
import { spawn } from "child_process";
|
|
1328
|
+
function command(executable, args, cwd, signal) {
|
|
1329
|
+
return new Promise((resolve, reject) => {
|
|
1330
|
+
const child = spawn(executable, args, {
|
|
1331
|
+
cwd,
|
|
1332
|
+
env: { ...process.env, NO_COLOR: "1", FORCE_COLOR: "0" },
|
|
1333
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1334
|
+
...signal ? { signal } : {}
|
|
1335
|
+
});
|
|
1336
|
+
let stdout = "";
|
|
1337
|
+
let stderr = "";
|
|
1338
|
+
child.stdout.on("data", (value) => {
|
|
1339
|
+
stdout += String(value);
|
|
1340
|
+
});
|
|
1341
|
+
child.stderr.on("data", (value) => {
|
|
1342
|
+
stderr += String(value);
|
|
1343
|
+
});
|
|
1344
|
+
child.on("error", reject);
|
|
1345
|
+
child.on("close", (code) => {
|
|
1346
|
+
resolve({ code: code ?? 1, stdout, stderr });
|
|
1347
|
+
});
|
|
1348
|
+
});
|
|
1349
|
+
}
|
|
1350
|
+
function execArgs(manager, binary, args) {
|
|
1351
|
+
if (manager === "npm") {
|
|
1352
|
+
return { executable: "npm", args: ["exec", "--no", "--", binary, ...args] };
|
|
1353
|
+
}
|
|
1354
|
+
if (manager === "yarn") {
|
|
1355
|
+
return { executable: "yarn", args: ["exec", binary, ...args] };
|
|
1356
|
+
}
|
|
1357
|
+
if (manager === "bun") {
|
|
1358
|
+
return { executable: "bun", args: ["x", "--no-install", binary, ...args] };
|
|
1359
|
+
}
|
|
1360
|
+
return { executable: "pnpm", args: ["exec", binary, ...args] };
|
|
1361
|
+
}
|
|
1362
|
+
function scriptArgs(manager, name) {
|
|
1363
|
+
if (manager === "bun") {
|
|
1364
|
+
return { executable: "bun", args: ["run", name] };
|
|
1365
|
+
}
|
|
1366
|
+
if (manager === "npm") {
|
|
1367
|
+
return { executable: "npm", args: ["run", name] };
|
|
1368
|
+
}
|
|
1369
|
+
return { executable: manager, args: [name] };
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
// src/audit.ts
|
|
1373
|
+
var AUDIT_TIMEOUT_MS = 6e4;
|
|
1374
|
+
async function checkAudit(options, dependencies = {}) {
|
|
1375
|
+
const mode = options.audit ?? "report";
|
|
1376
|
+
if (mode === "off") {
|
|
1377
|
+
return skipped("audit", "Dependency auditing is off.");
|
|
1378
|
+
}
|
|
1379
|
+
options.signal?.throwIfAborted();
|
|
1380
|
+
let invalidIgnores = [];
|
|
1381
|
+
let ignores = [];
|
|
1382
|
+
try {
|
|
1383
|
+
const parsed = await auditIgnores(options.cwd);
|
|
1384
|
+
ignores = parsed.ignores;
|
|
1385
|
+
invalidIgnores = parsed.problems;
|
|
1386
|
+
} catch (error) {
|
|
1387
|
+
invalidIgnores.push(
|
|
1388
|
+
problem(
|
|
1389
|
+
`Could not use audit.ignore: ${message2(error)}`,
|
|
1390
|
+
"Fix or remove the invalid acknowledgement. The advisory remains unacknowledged.",
|
|
1391
|
+
{ rule: "audit/invalid-ignore", file: "zappie.config.ts", line: 1 }
|
|
1392
|
+
)
|
|
1393
|
+
);
|
|
1394
|
+
}
|
|
1395
|
+
if (options.packageManager === "yarn" || options.packageManager === "bun") {
|
|
1396
|
+
return withProblems(
|
|
1397
|
+
unavailable(
|
|
1398
|
+
mode,
|
|
1399
|
+
`Could not audit dependencies: ${options.packageManager} is not supported.`,
|
|
1400
|
+
"Use npm or pnpm for dependency auditing, or run with --audit=off.",
|
|
1401
|
+
"package.json"
|
|
1402
|
+
),
|
|
1403
|
+
invalidIgnores,
|
|
1404
|
+
mode
|
|
1405
|
+
);
|
|
1406
|
+
}
|
|
1407
|
+
const lockfile2 = await findLockfile(options.cwd, options.packageManager);
|
|
1408
|
+
if (!lockfile2) {
|
|
1409
|
+
return withProblems(
|
|
1410
|
+
unavailable(
|
|
1411
|
+
mode,
|
|
1412
|
+
"Could not audit dependencies: no lockfile was found.",
|
|
1413
|
+
"Commit the package manager lockfile and run zappie check again.",
|
|
1414
|
+
"package.json"
|
|
1415
|
+
),
|
|
1416
|
+
invalidIgnores,
|
|
1417
|
+
mode
|
|
1418
|
+
);
|
|
1419
|
+
}
|
|
1420
|
+
const invocation = auditCommand(options.packageManager);
|
|
1421
|
+
const timeoutMs = dependencies.timeoutMs ?? AUDIT_TIMEOUT_MS;
|
|
1422
|
+
const timeout = AbortSignal.timeout(timeoutMs);
|
|
1423
|
+
const signal = options.signal ? AbortSignal.any([options.signal, timeout]) : timeout;
|
|
1424
|
+
let result;
|
|
1425
|
+
try {
|
|
1426
|
+
result = await (dependencies.run ?? command)(
|
|
1427
|
+
invocation.executable,
|
|
1428
|
+
invocation.args,
|
|
1429
|
+
options.cwd,
|
|
1430
|
+
signal
|
|
1431
|
+
);
|
|
1432
|
+
} catch (error) {
|
|
1433
|
+
options.signal?.throwIfAborted();
|
|
1434
|
+
const detail = timeout.aborted ? `the audit timed out after ${Math.ceil(timeoutMs / 1e3)} seconds` : message2(error);
|
|
1435
|
+
return withProblems(
|
|
1436
|
+
unavailable(
|
|
1437
|
+
mode,
|
|
1438
|
+
`Could not audit dependencies: ${detail}.`,
|
|
1439
|
+
"Check registry access and run zappie check again.",
|
|
1440
|
+
relativeLockfile(options.cwd, lockfile2)
|
|
1441
|
+
),
|
|
1442
|
+
invalidIgnores,
|
|
1443
|
+
mode
|
|
1444
|
+
);
|
|
1445
|
+
}
|
|
1446
|
+
options.signal?.throwIfAborted();
|
|
1447
|
+
let advisories;
|
|
1448
|
+
try {
|
|
1449
|
+
advisories = parseAuditOutput(options.packageManager, result.stdout);
|
|
1450
|
+
if (result.code > 1) {
|
|
1451
|
+
throw new Error(`the audit command exited with status ${result.code}`);
|
|
1452
|
+
}
|
|
1453
|
+
} catch (error) {
|
|
1454
|
+
return withProblems(
|
|
1455
|
+
unavailable(
|
|
1456
|
+
mode,
|
|
1457
|
+
`Could not audit dependencies: ${message2(error)}.`,
|
|
1458
|
+
"Check registry access and run zappie check again.",
|
|
1459
|
+
relativeLockfile(options.cwd, lockfile2)
|
|
1460
|
+
),
|
|
1461
|
+
invalidIgnores,
|
|
1462
|
+
mode
|
|
1463
|
+
);
|
|
1464
|
+
}
|
|
1465
|
+
return withProblems(
|
|
1466
|
+
evaluateAudit(
|
|
1467
|
+
advisories,
|
|
1468
|
+
mode,
|
|
1469
|
+
ignores,
|
|
1470
|
+
relativeLockfile(options.cwd, lockfile2)
|
|
1471
|
+
),
|
|
1472
|
+
invalidIgnores,
|
|
1473
|
+
mode
|
|
1474
|
+
);
|
|
1475
|
+
}
|
|
1476
|
+
function evaluateAudit(advisories, mode, ignores = [], file) {
|
|
1477
|
+
const problems = [];
|
|
1478
|
+
const applied = advisories.map((advisory) => {
|
|
1479
|
+
const acknowledged = ignores.find(
|
|
1480
|
+
({ id }) => normalizedId(id) === normalizedId(advisory.id)
|
|
1481
|
+
);
|
|
1482
|
+
if (acknowledged && malicious(advisory.id)) {
|
|
1483
|
+
problems.push(
|
|
1484
|
+
problem(
|
|
1485
|
+
`${advisory.id} is a malicious-package advisory and cannot be acknowledged.`,
|
|
1486
|
+
`Remove ${advisory.package} and replace it with a trusted package or release.`,
|
|
1487
|
+
{
|
|
1488
|
+
rule: "audit/invalid-ignore",
|
|
1489
|
+
advisory,
|
|
1490
|
+
...file ? { file, line: 1 } : {}
|
|
1491
|
+
}
|
|
1492
|
+
)
|
|
1493
|
+
);
|
|
1494
|
+
return advisory;
|
|
1495
|
+
}
|
|
1496
|
+
if (acknowledged && (advisory.fixedVersion || advisory.upgradeVersion || advisory.fixCommand)) {
|
|
1497
|
+
const upgradePackage = advisory.upgradePackage ?? advisory.package;
|
|
1498
|
+
const upgradeVersion = advisory.upgradeVersion ?? advisory.fixedVersion;
|
|
1499
|
+
const fix = advisory.fixCommand ? `Run ${advisory.fixCommand}.` : `Upgrade ${upgradePackage} to ${upgradeVersion} or later.`;
|
|
1500
|
+
const reason = advisory.fixCommand ? `${advisory.id} has an available fix and cannot be acknowledged.` : `${advisory.id} has a fixed version and cannot be acknowledged.`;
|
|
1501
|
+
problems.push(
|
|
1502
|
+
problem(reason, fix, {
|
|
1503
|
+
rule: "audit/invalid-ignore",
|
|
1504
|
+
advisory,
|
|
1505
|
+
...file ? { file, line: 1 } : {}
|
|
1506
|
+
})
|
|
1507
|
+
);
|
|
1508
|
+
return advisory;
|
|
1509
|
+
}
|
|
1510
|
+
return acknowledged ? { ...advisory, ignored: true, ignoredUntil: acknowledged.until } : advisory;
|
|
1511
|
+
});
|
|
1512
|
+
problems.push(...applied.map((advisory) => advisoryProblem(advisory, file)));
|
|
1513
|
+
const critical = applied.some(
|
|
1514
|
+
({ severity, ignored }) => severity === "critical" && !ignored
|
|
1515
|
+
);
|
|
1516
|
+
const invalidIgnore2 = problems.some(
|
|
1517
|
+
({ rule }) => rule === "audit/invalid-ignore"
|
|
1518
|
+
);
|
|
1519
|
+
return step(
|
|
1520
|
+
"audit",
|
|
1521
|
+
mode === "enforce" && (critical || invalidIgnore2) ? "failed" : "passed",
|
|
1522
|
+
problems
|
|
1523
|
+
);
|
|
1524
|
+
}
|
|
1525
|
+
function unavailable(mode, message6, fix, file) {
|
|
1526
|
+
return step("audit", mode === "enforce" ? "failed" : "passed", [
|
|
1527
|
+
problem(message6, fix, { rule: "audit/unavailable", file, line: 1 })
|
|
1528
|
+
]);
|
|
1529
|
+
}
|
|
1530
|
+
function withProblems(checked, extra, mode) {
|
|
1531
|
+
if (extra.length === 0) {
|
|
1532
|
+
return checked;
|
|
1533
|
+
}
|
|
1534
|
+
return step("audit", mode === "enforce" ? "failed" : checked.result, [
|
|
1535
|
+
...extra,
|
|
1536
|
+
...checked.problems
|
|
1537
|
+
]);
|
|
1538
|
+
}
|
|
1539
|
+
async function findLockfile(cwd, manager) {
|
|
1540
|
+
const names = manager === "npm" ? ["package-lock.json", "npm-shrinkwrap.json"] : ["pnpm-lock.yaml"];
|
|
1541
|
+
let directory = path4.resolve(cwd);
|
|
1542
|
+
for (; ; ) {
|
|
1543
|
+
if (directory !== path4.resolve(cwd) && manager === "pnpm" && await workspaceExcludes(directory, path4.resolve(cwd))) {
|
|
1544
|
+
return null;
|
|
1545
|
+
}
|
|
1546
|
+
for (const name of names) {
|
|
1547
|
+
const candidate = path4.join(directory, name);
|
|
1548
|
+
try {
|
|
1549
|
+
await access2(candidate);
|
|
1550
|
+
return candidate;
|
|
1551
|
+
} catch {
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
const parent = path4.dirname(directory);
|
|
1555
|
+
if (parent === directory) {
|
|
1556
|
+
return null;
|
|
1557
|
+
}
|
|
1558
|
+
directory = parent;
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
async function workspaceExcludes(directory, cwd) {
|
|
1562
|
+
try {
|
|
1563
|
+
const source = await readFile4(
|
|
1564
|
+
path4.join(directory, "pnpm-workspace.yaml"),
|
|
1565
|
+
"utf8"
|
|
1566
|
+
);
|
|
1567
|
+
const block = /^packages:\s*\n((?:[ \t]+.*(?:\n|$))*)/m.exec(source)?.[1];
|
|
1568
|
+
if (!block) {
|
|
1569
|
+
return false;
|
|
1570
|
+
}
|
|
1571
|
+
const relative2 = path4.relative(directory, cwd).replaceAll(path4.sep, "/");
|
|
1572
|
+
const patterns = [
|
|
1573
|
+
...block.matchAll(/^\s*-\s*["']?([^"'\s]+)["']?\s*$/gm)
|
|
1574
|
+
].map((match) => match[1] ?? "");
|
|
1575
|
+
return !patterns.some((pattern) => workspacePattern(pattern, relative2));
|
|
1576
|
+
} catch {
|
|
1577
|
+
return false;
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
function workspacePattern(pattern, relative2) {
|
|
1581
|
+
if (pattern === ".") {
|
|
1582
|
+
return relative2 === "";
|
|
1583
|
+
}
|
|
1584
|
+
const expression = pattern.split("*").map((part) => part.replace(/[.*+?^$()|[\]\\]/g, "\\$&")).join("[^/]+");
|
|
1585
|
+
return new RegExp(`^${expression}$`).test(relative2);
|
|
1586
|
+
}
|
|
1587
|
+
function relativeLockfile(cwd, lockfile2) {
|
|
1588
|
+
return path4.relative(cwd, lockfile2).replaceAll(path4.sep, "/");
|
|
1589
|
+
}
|
|
1590
|
+
function auditCommand(manager) {
|
|
1591
|
+
return manager === "pnpm" ? { executable: "pnpm", args: ["audit", "--json"] } : { executable: "npm", args: ["audit", "--json"] };
|
|
1592
|
+
}
|
|
1593
|
+
function parseAuditOutput(manager, source) {
|
|
1594
|
+
const value = JSON.parse(source);
|
|
1595
|
+
requireAuditReport(manager, value);
|
|
1596
|
+
const parsed = manager === "npm" ? npmAdvisories(value) : pnpmAdvisories(value);
|
|
1597
|
+
return parsed.filter(({ severity }) => severity === "high" || severity === "critical").sort((left, right) => left.id.localeCompare(right.id));
|
|
1598
|
+
}
|
|
1599
|
+
function requireAuditReport(manager, value) {
|
|
1600
|
+
if (record3(value.error)) {
|
|
1601
|
+
throw new Error(
|
|
1602
|
+
string(value.error.message) ?? string(value.error.code) ?? string(value.message) ?? "the package manager returned an error"
|
|
1603
|
+
);
|
|
1604
|
+
}
|
|
1605
|
+
if (manager === "npm" && !record3(value.vulnerabilities)) {
|
|
1606
|
+
throw new Error("npm returned no vulnerability report");
|
|
1607
|
+
}
|
|
1608
|
+
if (manager === "pnpm" && !record3(value.advisories) && !record3(value.metadata)) {
|
|
1609
|
+
throw new Error("pnpm returned no advisory report");
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
function pnpmAdvisories(value) {
|
|
1613
|
+
if (!record3(value.advisories)) {
|
|
1614
|
+
return [];
|
|
1615
|
+
}
|
|
1616
|
+
return Object.entries(value.advisories).flatMap(([key, raw]) => {
|
|
1617
|
+
if (!record3(raw)) {
|
|
1618
|
+
return [];
|
|
1619
|
+
}
|
|
1620
|
+
const findings = Array.isArray(raw.findings) ? raw.findings : [];
|
|
1621
|
+
const versions = findings.flatMap(
|
|
1622
|
+
(finding) => record3(finding) && typeof finding.version === "string" ? [finding.version] : []
|
|
1623
|
+
);
|
|
1624
|
+
const severity = normalizedSeverity(raw.severity);
|
|
1625
|
+
if (!severity) {
|
|
1626
|
+
return [];
|
|
1627
|
+
}
|
|
1628
|
+
return [
|
|
1629
|
+
{
|
|
1630
|
+
id: string(raw.github_advisory_id) ?? string(raw.id) ?? key,
|
|
1631
|
+
package: string(raw.module_name) ?? "unknown package",
|
|
1632
|
+
severity,
|
|
1633
|
+
vulnerableVersion: versions.join(", ") || string(raw.vulnerable_versions) || "unknown",
|
|
1634
|
+
fixedVersion: firstFixed(string(raw.patched_versions)),
|
|
1635
|
+
ignored: false
|
|
1636
|
+
}
|
|
1637
|
+
];
|
|
1638
|
+
});
|
|
1639
|
+
}
|
|
1640
|
+
function npmAdvisories(value) {
|
|
1641
|
+
if (!record3(value.vulnerabilities)) {
|
|
1642
|
+
return [];
|
|
1643
|
+
}
|
|
1644
|
+
const advisories = /* @__PURE__ */ new Map();
|
|
1645
|
+
for (const [packageName2, raw] of Object.entries(value.vulnerabilities)) {
|
|
1646
|
+
if (!record3(raw) || !Array.isArray(raw.via)) {
|
|
1647
|
+
continue;
|
|
1648
|
+
}
|
|
1649
|
+
for (const via of raw.via) {
|
|
1650
|
+
if (!record3(via)) {
|
|
1651
|
+
continue;
|
|
1652
|
+
}
|
|
1653
|
+
const severity = normalizedSeverity(via.severity);
|
|
1654
|
+
if (!severity) {
|
|
1655
|
+
continue;
|
|
1656
|
+
}
|
|
1657
|
+
const id = ghsaFrom(string(via.url)) ?? string(via.name) ?? String(via.source ?? packageName2);
|
|
1658
|
+
const upgrade = npmUpgrade(raw.fixAvailable);
|
|
1659
|
+
advisories.set(id, {
|
|
1660
|
+
id,
|
|
1661
|
+
package: packageName2,
|
|
1662
|
+
severity,
|
|
1663
|
+
vulnerableVersion: string(via.range) ?? string(raw.range) ?? "unknown",
|
|
1664
|
+
fixedVersion: upgrade.upgradePackage === packageName2 ? upgrade.upgradeVersion ?? null : null,
|
|
1665
|
+
...upgrade,
|
|
1666
|
+
ignored: false
|
|
1667
|
+
});
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
return [...advisories.values()];
|
|
1671
|
+
}
|
|
1672
|
+
function advisoryProblem(advisory, file) {
|
|
1673
|
+
const acknowledged = advisory.ignored ? ` Acknowledged until ${advisory.ignoredUntil}.` : "";
|
|
1674
|
+
const upgradePackage = advisory.upgradePackage ?? advisory.package;
|
|
1675
|
+
const upgradeVersion = advisory.upgradeVersion ?? advisory.fixedVersion;
|
|
1676
|
+
const fix = malicious(advisory.id) ? `Remove ${advisory.package} and replace it with a trusted package or release.` : advisory.fixCommand ? `Run ${advisory.fixCommand}.` : upgradeVersion ? `Upgrade ${upgradePackage} to ${upgradeVersion} or later.` : `Review ${advisory.id}. If no fix exists, add a reason and an expiry of at most 90 days to audit.ignore.`;
|
|
1677
|
+
return problem(
|
|
1678
|
+
`${advisory.id}: ${advisory.package} ${advisory.vulnerableVersion} has ${advisory.severity} severity.${acknowledged}`,
|
|
1679
|
+
fix,
|
|
1680
|
+
{
|
|
1681
|
+
rule: `audit/${advisory.id}`,
|
|
1682
|
+
advisory,
|
|
1683
|
+
...file ? { file, line: 1 } : {}
|
|
1684
|
+
}
|
|
1685
|
+
);
|
|
1686
|
+
}
|
|
1687
|
+
async function auditIgnores(cwd) {
|
|
1688
|
+
let loaded;
|
|
1689
|
+
try {
|
|
1690
|
+
loaded = await loadConfig(cwd);
|
|
1691
|
+
} catch (error) {
|
|
1692
|
+
if (missing(error)) {
|
|
1693
|
+
return { ignores: [], problems: [] };
|
|
1694
|
+
}
|
|
1695
|
+
throw error;
|
|
1696
|
+
}
|
|
1697
|
+
if (!record3(loaded.value) || !record3(loaded.value.audit)) {
|
|
1698
|
+
return { ignores: [], problems: [] };
|
|
1699
|
+
}
|
|
1700
|
+
const problems = [];
|
|
1701
|
+
const ignores = parseAuditIgnores(loaded.value.audit.ignore, problems);
|
|
1702
|
+
return { ignores, problems };
|
|
1703
|
+
}
|
|
1704
|
+
function parseAuditIgnores(value, problems = []) {
|
|
1705
|
+
if (value === void 0) {
|
|
1706
|
+
return [];
|
|
1707
|
+
}
|
|
1708
|
+
if (!Array.isArray(value)) {
|
|
1709
|
+
problems.push(invalidIgnore("audit.ignore must be an array."));
|
|
1710
|
+
return [];
|
|
1711
|
+
}
|
|
1712
|
+
const today = Date.parse(
|
|
1713
|
+
`${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}T00:00:00.000Z`
|
|
1714
|
+
);
|
|
1715
|
+
const ninetyDays = 90 * 24 * 60 * 60 * 1e3;
|
|
1716
|
+
const ids = /* @__PURE__ */ new Set();
|
|
1717
|
+
const ignores = [];
|
|
1718
|
+
value.forEach((entry, index) => {
|
|
1719
|
+
if (!ignoreEntry(entry) || !entry.id.trim() || !entry.reason.trim()) {
|
|
1720
|
+
problems.push(
|
|
1721
|
+
invalidIgnore(
|
|
1722
|
+
`audit.ignore[${index}] must have a non-blank id, reason and until.`
|
|
1723
|
+
)
|
|
1724
|
+
);
|
|
1725
|
+
return;
|
|
1726
|
+
}
|
|
1727
|
+
const id = normalizedId(entry.id);
|
|
1728
|
+
if (ids.has(id)) {
|
|
1729
|
+
problems.push(
|
|
1730
|
+
invalidIgnore(`audit.ignore contains duplicate id ${entry.id}.`)
|
|
1731
|
+
);
|
|
1732
|
+
return;
|
|
1733
|
+
}
|
|
1734
|
+
const expiry = Date.parse(`${entry.until}T00:00:00.000Z`);
|
|
1735
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(entry.until) || !Number.isFinite(expiry) || new Date(expiry).toISOString().slice(0, 10) !== entry.until || expiry < today || expiry - today > ninetyDays) {
|
|
1736
|
+
problems.push(
|
|
1737
|
+
invalidIgnore(
|
|
1738
|
+
`audit.ignore[${index}].until must be a real, unexpired date at most 90 days away.`
|
|
1739
|
+
)
|
|
1740
|
+
);
|
|
1741
|
+
return;
|
|
1742
|
+
}
|
|
1743
|
+
ids.add(id);
|
|
1744
|
+
ignores.push(entry);
|
|
1745
|
+
});
|
|
1746
|
+
return ignores;
|
|
1747
|
+
}
|
|
1748
|
+
function invalidIgnore(message6) {
|
|
1749
|
+
return problem(
|
|
1750
|
+
message6,
|
|
1751
|
+
"Fix or remove the invalid acknowledgement. The advisory remains unacknowledged.",
|
|
1752
|
+
{ rule: "audit/invalid-ignore", file: "zappie.config.ts", line: 1 }
|
|
1753
|
+
);
|
|
1754
|
+
}
|
|
1755
|
+
function ignoreEntry(value) {
|
|
1756
|
+
return record3(value) && typeof value.id === "string" && typeof value.reason === "string" && typeof value.until === "string";
|
|
1757
|
+
}
|
|
1758
|
+
function npmUpgrade(value) {
|
|
1759
|
+
if (record3(value) && typeof value.name === "string" && typeof value.version === "string") {
|
|
1760
|
+
return { upgradePackage: value.name, upgradeVersion: value.version };
|
|
1761
|
+
}
|
|
1762
|
+
if (value === true) {
|
|
1763
|
+
return { fixCommand: "npm audit fix" };
|
|
1764
|
+
}
|
|
1765
|
+
return {};
|
|
1766
|
+
}
|
|
1767
|
+
function firstFixed(value) {
|
|
1768
|
+
if (!value || value === "<0.0.0") {
|
|
1769
|
+
return null;
|
|
1770
|
+
}
|
|
1771
|
+
return /(?:^|\s)>=?\s*([^\s|]+)/.exec(value)?.[1] ?? null;
|
|
1772
|
+
}
|
|
1773
|
+
function ghsaFrom(value) {
|
|
1774
|
+
const id = value ? /GHSA-[\w-]+/i.exec(value)?.[0] : void 0;
|
|
1775
|
+
return id ? `GHSA-${id.slice(5).toLowerCase()}` : void 0;
|
|
1776
|
+
}
|
|
1777
|
+
function normalizedId(value) {
|
|
1778
|
+
return value.trim().toLowerCase();
|
|
1779
|
+
}
|
|
1780
|
+
function malicious(value) {
|
|
1781
|
+
return /^MAL-/i.test(value.trim());
|
|
1782
|
+
}
|
|
1783
|
+
function normalizedSeverity(value) {
|
|
1784
|
+
const severity = typeof value === "string" ? value.toLowerCase() : "";
|
|
1785
|
+
return severity === "high" || severity === "critical" ? severity : void 0;
|
|
1786
|
+
}
|
|
1787
|
+
function string(value) {
|
|
1788
|
+
return typeof value === "string" ? value : void 0;
|
|
1789
|
+
}
|
|
1790
|
+
function message2(error) {
|
|
1791
|
+
return error instanceof Error ? error.message : String(error);
|
|
1792
|
+
}
|
|
1793
|
+
function missing(error) {
|
|
1794
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
1795
|
+
}
|
|
1796
|
+
function record3(value) {
|
|
1797
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1321
1800
|
// src/budgets.ts
|
|
1322
|
-
import { access as
|
|
1323
|
-
import
|
|
1801
|
+
import { access as access3, readFile as readFile7 } from "fs/promises";
|
|
1802
|
+
import path7 from "path";
|
|
1324
1803
|
|
|
1325
1804
|
// src/budget-files.ts
|
|
1326
|
-
import { readFile as
|
|
1327
|
-
import
|
|
1805
|
+
import { readFile as readFile5, readdir } from "fs/promises";
|
|
1806
|
+
import path5 from "path";
|
|
1328
1807
|
import { gzipSync } from "zlib";
|
|
1329
1808
|
async function cssSizes(cwd) {
|
|
1330
|
-
const root =
|
|
1809
|
+
const root = path5.join(cwd, "build/client");
|
|
1331
1810
|
const files = [];
|
|
1332
1811
|
await collect(root, files);
|
|
1333
1812
|
return Promise.all(
|
|
1334
1813
|
files.filter((file) => file.endsWith(".css")).map(async (file) => ({
|
|
1335
|
-
file:
|
|
1336
|
-
bytes: gzipSync(await
|
|
1814
|
+
file: path5.relative(root, file).split(path5.sep).join("/"),
|
|
1815
|
+
bytes: gzipSync(await readFile5(file, "utf8")).byteLength
|
|
1337
1816
|
}))
|
|
1338
1817
|
);
|
|
1339
1818
|
}
|
|
@@ -1342,7 +1821,7 @@ async function collect(directory, files) {
|
|
|
1342
1821
|
if (entry.isSymbolicLink()) {
|
|
1343
1822
|
continue;
|
|
1344
1823
|
}
|
|
1345
|
-
const file =
|
|
1824
|
+
const file = path5.join(directory, entry.name);
|
|
1346
1825
|
if (entry.isDirectory()) {
|
|
1347
1826
|
await collect(file, files);
|
|
1348
1827
|
} else if (entry.isFile()) {
|
|
@@ -1352,8 +1831,8 @@ async function collect(directory, files) {
|
|
|
1352
1831
|
}
|
|
1353
1832
|
|
|
1354
1833
|
// src/budget-manifest.ts
|
|
1355
|
-
import { readFile as
|
|
1356
|
-
import
|
|
1834
|
+
import { readFile as readFile6 } from "fs/promises";
|
|
1835
|
+
import path6 from "path";
|
|
1357
1836
|
import { gzipSync as gzipSync2 } from "zlib";
|
|
1358
1837
|
function routeKeys(manifest) {
|
|
1359
1838
|
return Object.entries(manifest).filter(([key, entry]) => routeSource(entry.src ?? key)).map(([key]) => key).sort();
|
|
@@ -1367,7 +1846,7 @@ async function graphSizes(cwd, manifest, roots) {
|
|
|
1367
1846
|
[...keys].map((key) => manifest[key]).filter((entry) => Boolean(entry)).filter(({ file }) => file.endsWith(".js")).map(async ({ file }) => ({
|
|
1368
1847
|
file,
|
|
1369
1848
|
bytes: gzipSync2(
|
|
1370
|
-
await
|
|
1849
|
+
await readFile6(path6.join(cwd, "build/client", file), "utf8")
|
|
1371
1850
|
).byteLength
|
|
1372
1851
|
}))
|
|
1373
1852
|
);
|
|
@@ -1401,16 +1880,16 @@ async function checkBudgets({
|
|
|
1401
1880
|
signal
|
|
1402
1881
|
}) {
|
|
1403
1882
|
signal?.throwIfAborted();
|
|
1404
|
-
const manifestFile =
|
|
1883
|
+
const manifestFile = path7.join(cwd, "build/client/.vite/manifest.json");
|
|
1405
1884
|
try {
|
|
1406
|
-
await
|
|
1885
|
+
await access3(manifestFile);
|
|
1407
1886
|
} catch {
|
|
1408
1887
|
return skipped(
|
|
1409
1888
|
"budgets",
|
|
1410
1889
|
"No build/client/.vite/manifest.json is available."
|
|
1411
1890
|
);
|
|
1412
1891
|
}
|
|
1413
|
-
const manifest = JSON.parse(await
|
|
1892
|
+
const manifest = JSON.parse(await readFile7(manifestFile, "utf8"));
|
|
1414
1893
|
const config = (await loadConfig(cwd)).value;
|
|
1415
1894
|
const limits = { ...DEFAULT_BUDGETS, ...config.budgets };
|
|
1416
1895
|
const routes = routeKeys(manifest);
|
|
@@ -1465,56 +1944,8 @@ function cssProblem(sizes, limitKb, over) {
|
|
|
1465
1944
|
var total = (sizes) => sizes.reduce((sum, asset) => sum + asset.bytes, 0);
|
|
1466
1945
|
|
|
1467
1946
|
// src/build.ts
|
|
1468
|
-
import { readFile as
|
|
1469
|
-
import
|
|
1470
|
-
|
|
1471
|
-
// src/command.ts
|
|
1472
|
-
import { spawn } from "child_process";
|
|
1473
|
-
function command(executable, args, cwd, signal) {
|
|
1474
|
-
return new Promise((resolve, reject) => {
|
|
1475
|
-
const child = spawn(executable, args, {
|
|
1476
|
-
cwd,
|
|
1477
|
-
env: { ...process.env, NO_COLOR: "1", FORCE_COLOR: "0" },
|
|
1478
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
1479
|
-
...signal ? { signal } : {}
|
|
1480
|
-
});
|
|
1481
|
-
let stdout = "";
|
|
1482
|
-
let stderr = "";
|
|
1483
|
-
child.stdout.on("data", (value) => {
|
|
1484
|
-
stdout += String(value);
|
|
1485
|
-
});
|
|
1486
|
-
child.stderr.on("data", (value) => {
|
|
1487
|
-
stderr += String(value);
|
|
1488
|
-
});
|
|
1489
|
-
child.on("error", reject);
|
|
1490
|
-
child.on("close", (code) => {
|
|
1491
|
-
resolve({ code: code ?? 1, stdout, stderr });
|
|
1492
|
-
});
|
|
1493
|
-
});
|
|
1494
|
-
}
|
|
1495
|
-
function execArgs(manager, binary, args) {
|
|
1496
|
-
if (manager === "npm") {
|
|
1497
|
-
return { executable: "npm", args: ["exec", "--no", "--", binary, ...args] };
|
|
1498
|
-
}
|
|
1499
|
-
if (manager === "yarn") {
|
|
1500
|
-
return { executable: "yarn", args: ["exec", binary, ...args] };
|
|
1501
|
-
}
|
|
1502
|
-
if (manager === "bun") {
|
|
1503
|
-
return { executable: "bun", args: ["x", "--no-install", binary, ...args] };
|
|
1504
|
-
}
|
|
1505
|
-
return { executable: "pnpm", args: ["exec", binary, ...args] };
|
|
1506
|
-
}
|
|
1507
|
-
function scriptArgs(manager, name) {
|
|
1508
|
-
if (manager === "bun") {
|
|
1509
|
-
return { executable: "bun", args: ["run", name] };
|
|
1510
|
-
}
|
|
1511
|
-
if (manager === "npm") {
|
|
1512
|
-
return { executable: "npm", args: ["run", name] };
|
|
1513
|
-
}
|
|
1514
|
-
return { executable: manager, args: [name] };
|
|
1515
|
-
}
|
|
1516
|
-
|
|
1517
|
-
// src/build.ts
|
|
1947
|
+
import { readFile as readFile8 } from "fs/promises";
|
|
1948
|
+
import path8 from "path";
|
|
1518
1949
|
async function checkBuild({
|
|
1519
1950
|
cwd,
|
|
1520
1951
|
packageManager,
|
|
@@ -1522,7 +1953,7 @@ async function checkBuild({
|
|
|
1522
1953
|
}) {
|
|
1523
1954
|
signal?.throwIfAborted();
|
|
1524
1955
|
const manifest = JSON.parse(
|
|
1525
|
-
await
|
|
1956
|
+
await readFile8(path8.join(cwd, "package.json"), "utf8")
|
|
1526
1957
|
);
|
|
1527
1958
|
const invocation = manifest.dependencies?.["react-router"] ? execArgs(packageManager, "react-router", ["build"]) : manifest.scripts?.build ? scriptArgs(packageManager, "build") : void 0;
|
|
1528
1959
|
if (!invocation) {
|
|
@@ -1557,8 +1988,8 @@ ${stderr}`.split(/\r?\n/).map((line) => line.trimEnd()).filter(Boolean).slice(-1
|
|
|
1557
1988
|
}
|
|
1558
1989
|
|
|
1559
1990
|
// src/css-files.ts
|
|
1560
|
-
import { lstat, readFile as
|
|
1561
|
-
import
|
|
1991
|
+
import { lstat, readFile as readFile9, readdir as readdir2 } from "fs/promises";
|
|
1992
|
+
import path9 from "path";
|
|
1562
1993
|
var FILE_SIZE_CAP = 1024 * 1024;
|
|
1563
1994
|
var BUILD_FILE_SIZE_CAP = 8 * 1024 * 1024;
|
|
1564
1995
|
var ignoredDirectories = /* @__PURE__ */ new Set([
|
|
@@ -1582,7 +2013,7 @@ async function readCssInputs(cwd) {
|
|
|
1582
2013
|
return !name.startsWith(".") && !ignoredDirectories.has(name);
|
|
1583
2014
|
});
|
|
1584
2015
|
const built = [];
|
|
1585
|
-
await collect2(
|
|
2016
|
+
await collect2(path9.join(cwd, "build/client"), built, () => true);
|
|
1586
2017
|
const read = (files, cap) => texts(cwd, files, cap);
|
|
1587
2018
|
return {
|
|
1588
2019
|
sourceCss: await read(
|
|
@@ -1614,7 +2045,7 @@ async function texts(cwd, files, cap) {
|
|
|
1614
2045
|
}
|
|
1615
2046
|
result.push({
|
|
1616
2047
|
file: relative(cwd, file),
|
|
1617
|
-
text: await
|
|
2048
|
+
text: await readFile9(file, "utf8")
|
|
1618
2049
|
});
|
|
1619
2050
|
}
|
|
1620
2051
|
return result;
|
|
@@ -1633,7 +2064,7 @@ async function collect2(directory, files, enter) {
|
|
|
1633
2064
|
if (entry.isSymbolicLink()) {
|
|
1634
2065
|
continue;
|
|
1635
2066
|
}
|
|
1636
|
-
const file =
|
|
2067
|
+
const file = path9.join(directory, entry.name);
|
|
1637
2068
|
if (entry.isDirectory()) {
|
|
1638
2069
|
if (enter(entry.name)) {
|
|
1639
2070
|
await collect2(file, files, enter);
|
|
@@ -1747,9 +2178,9 @@ function important(css, code) {
|
|
|
1747
2178
|
}
|
|
1748
2179
|
return problems;
|
|
1749
2180
|
}
|
|
1750
|
-
function importantProblem(
|
|
2181
|
+
function importantProblem(message6, file, line) {
|
|
1751
2182
|
return problem(
|
|
1752
|
-
|
|
2183
|
+
message6,
|
|
1753
2184
|
"Raise the selector's specificity or reorder the rules instead. For a reviewed case, add /* zappie-allow-important: <reason> */ on the same line.",
|
|
1754
2185
|
{ rule: "css/important", file, ...line ? { line } : {} }
|
|
1755
2186
|
);
|
|
@@ -2333,7 +2764,7 @@ async function checkCss({
|
|
|
2333
2764
|
// src/lint.ts
|
|
2334
2765
|
import zappie from "eslint-plugin-zappie";
|
|
2335
2766
|
import { createRequire } from "module";
|
|
2336
|
-
import
|
|
2767
|
+
import path10 from "path";
|
|
2337
2768
|
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
2338
2769
|
async function checkLint({
|
|
2339
2770
|
cwd,
|
|
@@ -2342,12 +2773,12 @@ async function checkLint({
|
|
|
2342
2773
|
signal?.throwIfAborted();
|
|
2343
2774
|
let ESLint;
|
|
2344
2775
|
try {
|
|
2345
|
-
const require2 = createRequire(
|
|
2776
|
+
const require2 = createRequire(path10.join(cwd, "package.json"));
|
|
2346
2777
|
const loadedEslint = await import(pathToFileURL2(require2.resolve("eslint")).href);
|
|
2347
2778
|
ESLint = loadedEslint.ESLint;
|
|
2348
2779
|
} catch (error) {
|
|
2349
2780
|
return step("lint", "failed", [
|
|
2350
|
-
problem(
|
|
2781
|
+
problem(message3(error), "Install ESLint in the project.", {
|
|
2351
2782
|
rule: "lint/unavailable"
|
|
2352
2783
|
})
|
|
2353
2784
|
]);
|
|
@@ -2378,7 +2809,7 @@ async function checkLint({
|
|
|
2378
2809
|
} catch (error) {
|
|
2379
2810
|
return step("lint", "failed", [
|
|
2380
2811
|
problem(
|
|
2381
|
-
|
|
2812
|
+
message3(error),
|
|
2382
2813
|
"Fix the project ESLint configuration and run lint again.",
|
|
2383
2814
|
{
|
|
2384
2815
|
rule: "lint/config"
|
|
@@ -2433,7 +2864,7 @@ async function sourceFile(cwd) {
|
|
|
2433
2864
|
for (const candidate of candidates) {
|
|
2434
2865
|
try {
|
|
2435
2866
|
await import("fs/promises").then(
|
|
2436
|
-
({ access:
|
|
2867
|
+
({ access: access4 }) => access4(path10.join(cwd, candidate))
|
|
2437
2868
|
);
|
|
2438
2869
|
return candidate;
|
|
2439
2870
|
} catch {
|
|
@@ -2442,13 +2873,13 @@ async function sourceFile(cwd) {
|
|
|
2442
2873
|
}
|
|
2443
2874
|
return "eslint.config.mjs";
|
|
2444
2875
|
}
|
|
2445
|
-
function
|
|
2876
|
+
function message3(error) {
|
|
2446
2877
|
return error instanceof Error ? error.message : String(error);
|
|
2447
2878
|
}
|
|
2448
2879
|
|
|
2449
2880
|
// src/readiness.ts
|
|
2450
|
-
import { readFile as
|
|
2451
|
-
import
|
|
2881
|
+
import { readFile as readFile10 } from "fs/promises";
|
|
2882
|
+
import path11 from "path";
|
|
2452
2883
|
import { pathToFileURL as pathToFileURL3 } from "url";
|
|
2453
2884
|
async function checkReadiness({
|
|
2454
2885
|
cwd,
|
|
@@ -2466,9 +2897,9 @@ async function checkReadiness({
|
|
|
2466
2897
|
)
|
|
2467
2898
|
]);
|
|
2468
2899
|
}
|
|
2469
|
-
const file =
|
|
2900
|
+
const file = path11.join(cwd, "react-router.config.ts");
|
|
2470
2901
|
try {
|
|
2471
|
-
await
|
|
2902
|
+
await readFile10(file, "utf8");
|
|
2472
2903
|
const version = Date.now();
|
|
2473
2904
|
const config = await import(`${pathToFileURL3(file).href}?readiness=${version}`);
|
|
2474
2905
|
if (config.default?.routeDiscovery?.mode === "initial") {
|
|
@@ -2477,7 +2908,7 @@ async function checkReadiness({
|
|
|
2477
2908
|
} catch (error) {
|
|
2478
2909
|
return step("readiness", "failed", [
|
|
2479
2910
|
problem(
|
|
2480
|
-
|
|
2911
|
+
message4(error),
|
|
2481
2912
|
'Create react-router.config.ts and set routeDiscovery: { mode: "initial" }.',
|
|
2482
2913
|
{
|
|
2483
2914
|
rule: "readiness/route-discovery",
|
|
@@ -2494,13 +2925,15 @@ async function checkReadiness({
|
|
|
2494
2925
|
)
|
|
2495
2926
|
]);
|
|
2496
2927
|
}
|
|
2497
|
-
function
|
|
2928
|
+
function message4(error) {
|
|
2498
2929
|
return error instanceof Error ? error.message : String(error);
|
|
2499
2930
|
}
|
|
2500
2931
|
|
|
2501
2932
|
// src/secrets.ts
|
|
2502
|
-
import {
|
|
2503
|
-
import
|
|
2933
|
+
import { execFile } from "child_process";
|
|
2934
|
+
import { lstat as lstat2, readFile as readFile11, readdir as readdir3 } from "fs/promises";
|
|
2935
|
+
import path12 from "path";
|
|
2936
|
+
import { promisify } from "util";
|
|
2504
2937
|
|
|
2505
2938
|
// src/secret-patterns.ts
|
|
2506
2939
|
var FIX = "Move the value to a server environment variable and rotate it.";
|
|
@@ -2541,6 +2974,35 @@ function secretMatches(source, envFile) {
|
|
|
2541
2974
|
fix: FIX,
|
|
2542
2975
|
severity: "error"
|
|
2543
2976
|
}));
|
|
2977
|
+
const gitleaksDefaults = [
|
|
2978
|
+
[/\bghp_[A-Za-z0-9]{36}\b/g, "github-pat", "GitHub personal access token"],
|
|
2979
|
+
[
|
|
2980
|
+
/\bgithub_pat_[A-Za-z0-9_]{82}\b/g,
|
|
2981
|
+
"github-fine-grained-pat",
|
|
2982
|
+
"GitHub fine-grained access token"
|
|
2983
|
+
],
|
|
2984
|
+
[
|
|
2985
|
+
/\bglpat-[A-Za-z0-9_-]{20,}\b/g,
|
|
2986
|
+
"gitlab-pat",
|
|
2987
|
+
"GitLab personal access token"
|
|
2988
|
+
],
|
|
2989
|
+
[/\bnpm_[A-Za-z0-9]{36}\b/g, "npm-token", "npm access token"],
|
|
2990
|
+
[/\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g, "slack-token", "Slack token"],
|
|
2991
|
+
[/\bAIza[0-9A-Za-z_-]{35}\b/g, "google-api-key", "Google API key"],
|
|
2992
|
+
[
|
|
2993
|
+
/\bSG\.[A-Za-z0-9_-]{16,}\.[A-Za-z0-9_-]{16,}\b/g,
|
|
2994
|
+
"sendgrid-api-key",
|
|
2995
|
+
"SendGrid API key"
|
|
2996
|
+
]
|
|
2997
|
+
];
|
|
2998
|
+
for (const [pattern, rule, label] of gitleaksDefaults) {
|
|
2999
|
+
add(matches2, source, pattern, () => ({
|
|
3000
|
+
rule: `secrets/${rule}`,
|
|
3001
|
+
message: `Detected a ${label}.`,
|
|
3002
|
+
fix: FIX,
|
|
3003
|
+
severity: "error"
|
|
3004
|
+
}));
|
|
3005
|
+
}
|
|
2544
3006
|
const occupied = matches2.map(({ index }) => index);
|
|
2545
3007
|
const assignment = /\b([A-Za-z_$][\w$]*(?:secret|token|password|api[_-]?key)[\w$]*)\s*[:=]\s*(["'])([^"'\r\n]{24,})\2/gi;
|
|
2546
3008
|
for (const found of source.matchAll(assignment)) {
|
|
@@ -2578,21 +3040,31 @@ function entropy(value) {
|
|
|
2578
3040
|
|
|
2579
3041
|
// src/secrets.ts
|
|
2580
3042
|
var SECRET_FILE_SIZE_CAP = 1024 * 1024;
|
|
2581
|
-
|
|
3043
|
+
var execFileAsync = promisify(execFile);
|
|
3044
|
+
var lockfile = /(?:^|\/)(?:package-lock\.json|pnpm-lock\.yaml|yarn\.lock|bun\.lockb?)$/;
|
|
3045
|
+
async function scanSecrets(dir, options = {}) {
|
|
2582
3046
|
const findings = [];
|
|
2583
3047
|
for (const file of await filesToScan(dir)) {
|
|
2584
|
-
|
|
3048
|
+
let info;
|
|
3049
|
+
try {
|
|
3050
|
+
info = await lstat2(file);
|
|
3051
|
+
} catch (error) {
|
|
3052
|
+
if (missing2(error)) {
|
|
3053
|
+
continue;
|
|
3054
|
+
}
|
|
3055
|
+
throw error;
|
|
3056
|
+
}
|
|
2585
3057
|
if (!info.isFile() || info.size > SECRET_FILE_SIZE_CAP) {
|
|
2586
3058
|
continue;
|
|
2587
3059
|
}
|
|
2588
|
-
const contents = await
|
|
3060
|
+
const contents = await readFile11(file);
|
|
2589
3061
|
if (contents.includes(0)) {
|
|
2590
3062
|
continue;
|
|
2591
3063
|
}
|
|
2592
3064
|
const source = contents.toString("utf8");
|
|
2593
3065
|
for (const match of secretMatches(
|
|
2594
3066
|
source,
|
|
2595
|
-
|
|
3067
|
+
path12.basename(file).startsWith(".env")
|
|
2596
3068
|
)) {
|
|
2597
3069
|
findings.push({
|
|
2598
3070
|
rule: match.rule,
|
|
@@ -2604,14 +3076,21 @@ async function scanSecrets(dir) {
|
|
|
2604
3076
|
});
|
|
2605
3077
|
}
|
|
2606
3078
|
}
|
|
3079
|
+
if (options.history) {
|
|
3080
|
+
findings.push(...await scanHistory(dir));
|
|
3081
|
+
}
|
|
2607
3082
|
return findings;
|
|
2608
3083
|
}
|
|
2609
3084
|
async function checkSecrets({
|
|
2610
3085
|
cwd,
|
|
3086
|
+
history,
|
|
2611
3087
|
signal
|
|
2612
3088
|
}) {
|
|
2613
3089
|
signal?.throwIfAborted();
|
|
2614
|
-
const findings = await scanSecrets(
|
|
3090
|
+
const findings = await scanSecrets(
|
|
3091
|
+
cwd,
|
|
3092
|
+
history === void 0 ? {} : { history }
|
|
3093
|
+
);
|
|
2615
3094
|
signal?.throwIfAborted();
|
|
2616
3095
|
const failed = findings.some(({ severity }) => severity === "error");
|
|
2617
3096
|
return step(
|
|
@@ -2621,25 +3100,93 @@ async function checkSecrets({
|
|
|
2621
3100
|
);
|
|
2622
3101
|
}
|
|
2623
3102
|
async function filesToScan(cwd) {
|
|
3103
|
+
const files = await trackedFiles(cwd);
|
|
3104
|
+
for (const directory of ["build/client", "build/server", "workers"]) {
|
|
3105
|
+
await collect3(path12.join(cwd, directory), files);
|
|
3106
|
+
}
|
|
3107
|
+
return [...new Set(files)].filter((file) => !lockfile.test(relative(cwd, file))).sort();
|
|
3108
|
+
}
|
|
3109
|
+
async function trackedFiles(cwd) {
|
|
3110
|
+
try {
|
|
3111
|
+
const { stdout } = await execFileAsync("git", ["ls-files", "-z"], {
|
|
3112
|
+
cwd,
|
|
3113
|
+
encoding: "utf8",
|
|
3114
|
+
maxBuffer: 32 * 1024 * 1024
|
|
3115
|
+
});
|
|
3116
|
+
const files = stdout.split("\0").filter(Boolean).map((file) => path12.join(cwd, file));
|
|
3117
|
+
return files.length > 0 ? files : fallbackFiles(cwd);
|
|
3118
|
+
} catch {
|
|
3119
|
+
return fallbackFiles(cwd);
|
|
3120
|
+
}
|
|
3121
|
+
}
|
|
3122
|
+
async function fallbackFiles(cwd) {
|
|
2624
3123
|
const files = [];
|
|
2625
3124
|
for (const directory of ["app", "public"]) {
|
|
2626
|
-
await collect3(
|
|
3125
|
+
await collect3(path12.join(cwd, directory), files);
|
|
2627
3126
|
}
|
|
2628
|
-
await collect3(
|
|
2629
|
-
path11.join(cwd, "build/client"),
|
|
2630
|
-
files,
|
|
2631
|
-
(file) => file.endsWith(".js")
|
|
2632
|
-
);
|
|
2633
3127
|
try {
|
|
2634
3128
|
for (const entry of await readdir3(cwd, { withFileTypes: true })) {
|
|
2635
3129
|
if (entry.isFile() && rootConfig(entry.name)) {
|
|
2636
|
-
files.push(
|
|
3130
|
+
files.push(path12.join(cwd, entry.name));
|
|
2637
3131
|
}
|
|
2638
3132
|
}
|
|
2639
3133
|
} catch {
|
|
2640
|
-
return files
|
|
3134
|
+
return files;
|
|
2641
3135
|
}
|
|
2642
|
-
return
|
|
3136
|
+
return files;
|
|
3137
|
+
}
|
|
3138
|
+
async function scanHistory(cwd) {
|
|
3139
|
+
try {
|
|
3140
|
+
const { stdout } = await execFileAsync(
|
|
3141
|
+
"git",
|
|
3142
|
+
[
|
|
3143
|
+
"log",
|
|
3144
|
+
"--all",
|
|
3145
|
+
"--format=commit:%H",
|
|
3146
|
+
"-p",
|
|
3147
|
+
"--no-ext-diff",
|
|
3148
|
+
"--",
|
|
3149
|
+
".",
|
|
3150
|
+
":(exclude)package-lock.json",
|
|
3151
|
+
":(exclude)pnpm-lock.yaml",
|
|
3152
|
+
":(exclude)yarn.lock",
|
|
3153
|
+
":(exclude)bun.lock",
|
|
3154
|
+
":(exclude)bun.lockb"
|
|
3155
|
+
],
|
|
3156
|
+
{ cwd, encoding: "utf8", maxBuffer: 128 * 1024 * 1024 }
|
|
3157
|
+
);
|
|
3158
|
+
return secretMatches(stdout, false).map((match) => {
|
|
3159
|
+
const location2 = historyLocation(stdout, match.index);
|
|
3160
|
+
return {
|
|
3161
|
+
rule: match.rule,
|
|
3162
|
+
file: location2 ?? "git history",
|
|
3163
|
+
line: lineAt(stdout, match.index),
|
|
3164
|
+
message: match.message,
|
|
3165
|
+
fix: match.fix,
|
|
3166
|
+
severity: match.severity
|
|
3167
|
+
};
|
|
3168
|
+
});
|
|
3169
|
+
} catch {
|
|
3170
|
+
return [
|
|
3171
|
+
{
|
|
3172
|
+
rule: "secrets/history-unavailable",
|
|
3173
|
+
file: "git history",
|
|
3174
|
+
line: 1,
|
|
3175
|
+
message: "Could not scan Git history for secrets.",
|
|
3176
|
+
fix: "Check Git access and run zappie check --history again.",
|
|
3177
|
+
severity: "warning"
|
|
3178
|
+
}
|
|
3179
|
+
];
|
|
3180
|
+
}
|
|
3181
|
+
}
|
|
3182
|
+
function historyLocation(source, index) {
|
|
3183
|
+
const prefix = source.slice(0, index);
|
|
3184
|
+
const commit = [...prefix.matchAll(/^commit:([0-9a-f]+)$/gm)].at(-1)?.[1];
|
|
3185
|
+
const changedPaths = [
|
|
3186
|
+
...prefix.matchAll(/^(?:\+\+\+ b\/|--- a\/)(.+)$/gm)
|
|
3187
|
+
].map((match) => match[1]);
|
|
3188
|
+
const file = changedPaths.at(-1);
|
|
3189
|
+
return commit && file ? `${commit}:${file}` : void 0;
|
|
2643
3190
|
}
|
|
2644
3191
|
async function collect3(directory, files, accept = () => true) {
|
|
2645
3192
|
try {
|
|
@@ -2647,7 +3194,7 @@ async function collect3(directory, files, accept = () => true) {
|
|
|
2647
3194
|
if (entry.isSymbolicLink()) {
|
|
2648
3195
|
continue;
|
|
2649
3196
|
}
|
|
2650
|
-
const file =
|
|
3197
|
+
const file = path12.join(directory, entry.name);
|
|
2651
3198
|
if (entry.isDirectory()) {
|
|
2652
3199
|
await collect3(file, files, accept);
|
|
2653
3200
|
} else if (entry.isFile() && accept(file)) {
|
|
@@ -2655,11 +3202,14 @@ async function collect3(directory, files, accept = () => true) {
|
|
|
2655
3202
|
}
|
|
2656
3203
|
}
|
|
2657
3204
|
} catch (error) {
|
|
2658
|
-
if (!(error
|
|
3205
|
+
if (!missing2(error)) {
|
|
2659
3206
|
throw error;
|
|
2660
3207
|
}
|
|
2661
3208
|
}
|
|
2662
3209
|
}
|
|
3210
|
+
function missing2(error) {
|
|
3211
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
3212
|
+
}
|
|
2663
3213
|
function rootConfig(name) {
|
|
2664
3214
|
return name === "package.json" || name === "wrangler.json" || name === "wrangler.jsonc" || /(?:^|\.)config\.(?:[cm]?[jt]s|json)$/.test(name);
|
|
2665
3215
|
}
|
|
@@ -2668,13 +3218,13 @@ function lineAt(source, index) {
|
|
|
2668
3218
|
}
|
|
2669
3219
|
|
|
2670
3220
|
// src/test-step.ts
|
|
2671
|
-
import
|
|
3221
|
+
import path13 from "path";
|
|
2672
3222
|
async function checkTests({
|
|
2673
3223
|
cwd,
|
|
2674
3224
|
packageManager,
|
|
2675
3225
|
signal
|
|
2676
3226
|
}) {
|
|
2677
|
-
const manifest = await json(
|
|
3227
|
+
const manifest = await json(path13.join(cwd, "package.json"));
|
|
2678
3228
|
if (!manifest.scripts?.test) {
|
|
2679
3229
|
return skipped("test", "No test script is defined in package.json.");
|
|
2680
3230
|
}
|
|
@@ -2697,11 +3247,11 @@ async function checkTests({
|
|
|
2697
3247
|
}
|
|
2698
3248
|
|
|
2699
3249
|
// src/types-step.ts
|
|
2700
|
-
import
|
|
3250
|
+
import path14 from "path";
|
|
2701
3251
|
async function checkTypes(options) {
|
|
2702
3252
|
const { cwd, packageManager, signal } = options;
|
|
2703
3253
|
signal?.throwIfAborted();
|
|
2704
|
-
const manifest = await json(
|
|
3254
|
+
const manifest = await json(path14.join(cwd, "package.json"));
|
|
2705
3255
|
if (manifest.dependencies?.["react-router"]) {
|
|
2706
3256
|
const generated = execArgs(packageManager, "react-router", ["typegen"]);
|
|
2707
3257
|
const result2 = await command(
|
|
@@ -2755,6 +3305,7 @@ function outputProblems(output) {
|
|
|
2755
3305
|
var runners = [
|
|
2756
3306
|
{ name: "config", run: checkConfig },
|
|
2757
3307
|
{ name: "allowlist", run: checkAllowlist },
|
|
3308
|
+
{ name: "audit", run: checkAudit },
|
|
2758
3309
|
{ name: "lint", run: checkLint },
|
|
2759
3310
|
{ name: "types", run: checkTypes },
|
|
2760
3311
|
{ name: "test", run: checkTests },
|
|
@@ -2775,13 +3326,13 @@ async function runCheck(options) {
|
|
|
2775
3326
|
options.signal?.throwIfAborted();
|
|
2776
3327
|
checked = step(runner.name, "failed", [
|
|
2777
3328
|
problem(
|
|
2778
|
-
|
|
3329
|
+
message5(error),
|
|
2779
3330
|
"Fix the step setup and run zappie check again."
|
|
2780
3331
|
)
|
|
2781
3332
|
]);
|
|
2782
3333
|
}
|
|
2783
3334
|
steps.push(checked);
|
|
2784
|
-
if (checked.result === "failed" && !options.all) {
|
|
3335
|
+
if (checked.result === "failed" && !options.all && runner.name !== "audit") {
|
|
2785
3336
|
break;
|
|
2786
3337
|
}
|
|
2787
3338
|
}
|
|
@@ -2790,7 +3341,7 @@ async function runCheck(options) {
|
|
|
2790
3341
|
steps
|
|
2791
3342
|
};
|
|
2792
3343
|
}
|
|
2793
|
-
function
|
|
3344
|
+
function message5(error) {
|
|
2794
3345
|
return error instanceof Error ? error.message : String(error);
|
|
2795
3346
|
}
|
|
2796
3347
|
export {
|
|
@@ -2799,6 +3350,7 @@ export {
|
|
|
2799
3350
|
SECRET_FILE_SIZE_CAP,
|
|
2800
3351
|
analyseCss,
|
|
2801
3352
|
checkAllowlist,
|
|
3353
|
+
checkAudit,
|
|
2802
3354
|
checkBudgets,
|
|
2803
3355
|
checkBuild,
|
|
2804
3356
|
checkConfig,
|
|
@@ -2808,7 +3360,9 @@ export {
|
|
|
2808
3360
|
checkSecrets,
|
|
2809
3361
|
checkTests,
|
|
2810
3362
|
checkTypes,
|
|
3363
|
+
evaluateAudit,
|
|
2811
3364
|
loadConfig,
|
|
3365
|
+
parseAuditOutput,
|
|
2812
3366
|
runCheck,
|
|
2813
3367
|
scanSecrets,
|
|
2814
3368
|
supportsTypeScriptImport
|