mandrel-platform 1.12.0 → 1.13.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/README.md +6 -2
- package/package.json +4 -2
- package/scripts/audit-check.mjs +210 -47
- package/scripts/audit-check.test.mjs +479 -49
- package/scripts/check-action-download-retries.mjs +155 -11
- package/scripts/check-action-download-retries.test.mjs +168 -3
- package/scripts/check-advisory-scan-setup.test.mjs +186 -5
- package/scripts/check-coverage-threshold.mjs +112 -17
- package/scripts/check-coverage-threshold.test.mjs +335 -0
- package/scripts/check-husky-hook-modes.test.mjs +134 -0
- package/scripts/check-runner-runs-on.test.mjs +223 -2
- package/scripts/check-semgrep-lockfile.test.mjs +370 -1
- package/scripts/check-workflow-lint-tier.test.mjs +176 -0
- package/scripts/check-workflow-portability.mjs +6 -3
- package/scripts/check-workflow-portability.test.mjs +1 -1
- package/scripts/env-doctor.mjs +647 -90
- package/scripts/env-doctor.test.mjs +772 -3
- package/scripts/fixtures/npm-audit-v2.json +66 -0
- package/scripts/install-git-hooks.mjs +123 -0
- package/scripts/install-git-hooks.test.mjs +160 -0
- package/scripts/issue-intake.test.mjs +351 -21
- package/scripts/runner-toggle.test.mjs +407 -0
- package/scripts/select-semgrep-python.sh +215 -0
- package/scripts/select-semgrep-python.test.mjs +344 -0
- package/scripts/update-semgrep-rules.mjs +83 -5
- package/scripts/update-semgrep-rules.test.mjs +55 -1
- package/scripts/workflow-lint-gate.test.mjs +128 -0
|
@@ -11,6 +11,15 @@
|
|
|
11
11
|
// The other invariant these tests hold: a missing or malformed report is a
|
|
12
12
|
// TOOL FAILURE and exits non-zero even in advisory mode. A gate that reports
|
|
13
13
|
// "no findings" because the linter never ran is worse than no gate at all.
|
|
14
|
+
//
|
|
15
|
+
// Story #496 adds the third class and pins its split from the second: an
|
|
16
|
+
// INFRASTRUCTURE failure — a tool that never arrived at all, because the
|
|
17
|
+
// release CDN blipped, the checksum did not match, or the platform has no
|
|
18
|
+
// pinned entry — obeys the same dial findings do. It used to `exit 1` inside
|
|
19
|
+
// the composite's inline bash, so a CDN blip reddened every consumer's
|
|
20
|
+
// required check while this tier was documented as advisory. What it must
|
|
21
|
+
// never become is silent: an advisory infra failure still says, loudly, that
|
|
22
|
+
// nothing was linted.
|
|
14
23
|
|
|
15
24
|
import { test } from "node:test";
|
|
16
25
|
import assert from "node:assert/strict";
|
|
@@ -30,9 +39,21 @@ import {
|
|
|
30
39
|
loadReport,
|
|
31
40
|
severityRank,
|
|
32
41
|
resolveEnforcement,
|
|
42
|
+
classifyInfraFailure,
|
|
43
|
+
renderInfraSummary,
|
|
44
|
+
INFRA_FAILURE_ENV,
|
|
33
45
|
WorkflowLintGateError,
|
|
34
46
|
} from "../.github/actions/workflow-lint/workflow-lint-gate.mjs";
|
|
35
47
|
|
|
48
|
+
// A composite-supplied reason, in the shape action.yml actually emits. Kept
|
|
49
|
+
// free of any URL-shaped literal on purpose: asserting a substring against one
|
|
50
|
+
// is CodeQL js/incomplete-url-substring-sanitization, so the marker asserted
|
|
51
|
+
// on below is the slug prefix, never a host.
|
|
52
|
+
const INFRA_REASON =
|
|
53
|
+
"download-failed: curl exited 22 fetching actionlint_1.7.12_linux_amd64.tar.gz " +
|
|
54
|
+
"from the actionlint release assets.";
|
|
55
|
+
const INFRA_MARKER = "download-failed";
|
|
56
|
+
|
|
36
57
|
const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
37
58
|
const GATE = join(repoRoot, ".github/actions/workflow-lint/workflow-lint-gate.mjs");
|
|
38
59
|
|
|
@@ -423,3 +444,110 @@ test("CLI: only the literal string 'true' enables enforcement", () => {
|
|
|
423
444
|
assert.equal(res.status, 0, `enforce=${JSON.stringify(value)} must stay advisory`);
|
|
424
445
|
}
|
|
425
446
|
});
|
|
447
|
+
|
|
448
|
+
// ---------------------------------------------------------------------------
|
|
449
|
+
// Infrastructure failure — a tool that never arrived (Story #496, AC-1)
|
|
450
|
+
// ---------------------------------------------------------------------------
|
|
451
|
+
|
|
452
|
+
test("classifyInfraFailure: no reason means there is nothing to report", () => {
|
|
453
|
+
assert.equal(classifyInfraFailure(undefined, false), null);
|
|
454
|
+
assert.equal(classifyInfraFailure("", true), null);
|
|
455
|
+
assert.equal(classifyInfraFailure(" \n ", true), null, "whitespace is not a reason");
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
test("classifyInfraFailure: advisory tier warns and does NOT fail", () => {
|
|
459
|
+
const infra = classifyInfraFailure(INFRA_REASON, { actionlint: false, zizmor: false });
|
|
460
|
+
assert.equal(infra.enforced, false);
|
|
461
|
+
assert.equal(infra.level, "warning");
|
|
462
|
+
assert.equal(infra.exitCode, 0, "a CDN blip must not red an advisory tier");
|
|
463
|
+
assert.ok(infra.message.includes(INFRA_MARKER), infra.message);
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
test("classifyInfraFailure: an enforcing tier fails — tier-wide or per tool", () => {
|
|
467
|
+
assert.equal(classifyInfraFailure(INFRA_REASON, true).exitCode, 1);
|
|
468
|
+
assert.equal(classifyInfraFailure(INFRA_REASON, true).level, "error");
|
|
469
|
+
// A consumer who enforced ONE tool still asked this tier to block, so a
|
|
470
|
+
// gate that could not run is a failure for them.
|
|
471
|
+
const perTool = classifyInfraFailure(INFRA_REASON, { actionlint: true, zizmor: false });
|
|
472
|
+
assert.equal(perTool.enforced, true);
|
|
473
|
+
assert.equal(perTool.exitCode, 1);
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
test("renderInfraSummary states the posture and that nothing was linted", () => {
|
|
477
|
+
const advisory = renderInfraSummary(classifyInfraFailure(INFRA_REASON, false));
|
|
478
|
+
assert.match(advisory, /advisory/);
|
|
479
|
+
assert.match(advisory, /no workflows were linted/i);
|
|
480
|
+
assert.ok(advisory.includes(INFRA_MARKER), advisory);
|
|
481
|
+
const enforcing = renderInfraSummary(classifyInfraFailure(INFRA_REASON, true));
|
|
482
|
+
assert.match(enforcing, /enforcing/);
|
|
483
|
+
assert.doesNotMatch(enforcing, /does \*\*not\*\* fail/);
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
test("CLI: an infra failure exits 0 and warns while nothing is enforced", () => {
|
|
487
|
+
const res = runGate({
|
|
488
|
+
[INFRA_FAILURE_ENV]: INFRA_REASON,
|
|
489
|
+
WORKFLOW_LINT_ENFORCE: "false",
|
|
490
|
+
WORKFLOW_LINT_ENFORCE_ACTIONLINT: "false",
|
|
491
|
+
WORKFLOW_LINT_ENFORCE_ZIZMOR: "false",
|
|
492
|
+
});
|
|
493
|
+
assert.equal(res.status, 0, res.stderr);
|
|
494
|
+
assert.match(res.stdout, /::warning::/);
|
|
495
|
+
assert.doesNotMatch(res.stdout, /::error::/);
|
|
496
|
+
assert.ok(res.stdout.includes(INFRA_MARKER), res.stdout);
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
test("CLI: the same infra failure exits 1 and errors when the tier is enforced", () => {
|
|
500
|
+
const res = runGate({
|
|
501
|
+
[INFRA_FAILURE_ENV]: INFRA_REASON,
|
|
502
|
+
WORKFLOW_LINT_ENFORCE: "true",
|
|
503
|
+
});
|
|
504
|
+
assert.equal(res.status, 1);
|
|
505
|
+
assert.match(res.stdout, /::error::/);
|
|
506
|
+
assert.ok(res.stdout.includes(INFRA_MARKER), res.stdout);
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
test("CLI: a per-tool enforce alone makes an infra failure blocking", () => {
|
|
510
|
+
const res = runGate({
|
|
511
|
+
[INFRA_FAILURE_ENV]: INFRA_REASON,
|
|
512
|
+
WORKFLOW_LINT_ENFORCE: "false",
|
|
513
|
+
WORKFLOW_LINT_ENFORCE_ZIZMOR: "true",
|
|
514
|
+
});
|
|
515
|
+
assert.equal(res.status, 1);
|
|
516
|
+
assert.match(res.stdout, /::error::/);
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
test("CLI: an advisory infra failure is never reported as a clean run", () => {
|
|
520
|
+
// The empty reports the composite writes alongside the signal carry no
|
|
521
|
+
// information. Rendering them as "no findings" would turn a gate that never
|
|
522
|
+
// ran into a green tick that looks audited.
|
|
523
|
+
const res = runGate({
|
|
524
|
+
[INFRA_FAILURE_ENV]: INFRA_REASON,
|
|
525
|
+
WORKFLOW_LINT_ENFORCE: "false",
|
|
526
|
+
});
|
|
527
|
+
assert.equal(res.status, 0, res.stderr);
|
|
528
|
+
assert.doesNotMatch(res.stdout, /no findings/);
|
|
529
|
+
assert.doesNotMatch(res.stdout, /reported no findings/);
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
test("CLI: an unset infra reason leaves the findings path untouched", () => {
|
|
533
|
+
const res = runGate({ [INFRA_FAILURE_ENV]: "" }, { zizmor: [zizmorRow()] });
|
|
534
|
+
assert.equal(res.status, 0, res.stderr);
|
|
535
|
+
assert.match(res.stdout, /excessive-permissions/);
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
test("an infra failure does not relax the missing-report rule", () => {
|
|
539
|
+
// Different class, different answer: a report that never arrived means the
|
|
540
|
+
// linter ran and produced nothing readable, which stays fatal.
|
|
541
|
+
const res = spawnSync(process.execPath, [GATE], {
|
|
542
|
+
encoding: "utf8",
|
|
543
|
+
env: {
|
|
544
|
+
...process.env,
|
|
545
|
+
ACTIONLINT_REPORT: "/no/such/al.json",
|
|
546
|
+
ZIZMOR_REPORT: "",
|
|
547
|
+
WORKFLOW_LINT_ENFORCE: "false",
|
|
548
|
+
[INFRA_FAILURE_ENV]: "",
|
|
549
|
+
},
|
|
550
|
+
});
|
|
551
|
+
assert.equal(res.status, 1);
|
|
552
|
+
assert.match(res.stderr, /::error::workflow-lint gate/);
|
|
553
|
+
});
|