paperlint 3.1.0 → 3.1.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/.github/workflows/ci.yml +28 -41
- package/.github/workflows/dependabot-automerge.yml +1 -1
- package/CLAUDE.md +51 -56
- package/CONTRIBUTING.md +30 -18
- package/bin/paperlint.test.mjs +45 -0
- package/dist/adapters/node/files.io.d.ts +2 -0
- package/dist/adapters/node/files.io.d.ts.map +1 -1
- package/dist/adapters/node/files.io.js +3 -2
- package/dist/adapters/node/files.io.js.map +1 -1
- package/dist/adapters/pdfjs/fill.d.ts +15 -4
- package/dist/adapters/pdfjs/fill.d.ts.map +1 -1
- package/dist/adapters/pdfjs/fill.js +31 -21
- package/dist/adapters/pdfjs/fill.js.map +1 -1
- package/dist/adapters/references/index.d.ts.map +1 -1
- package/dist/adapters/references/index.js +14 -5
- package/dist/adapters/references/index.js.map +1 -1
- package/dist/adapters/references/pool.d.ts +8 -0
- package/dist/adapters/references/pool.d.ts.map +1 -0
- package/dist/adapters/references/pool.js +19 -0
- package/dist/adapters/references/pool.js.map +1 -0
- package/dist/build.d.ts +10 -2
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +7 -8
- package/dist/build.js.map +1 -1
- package/dist/cli.d.ts +27 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +71 -55
- package/dist/cli.js.map +1 -1
- package/dist/doctor.d.ts +27 -1
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +4 -3
- package/dist/doctor.js.map +1 -1
- package/dist/domain/result.d.ts +0 -2
- package/dist/domain/result.d.ts.map +1 -1
- package/dist/domain/result.js +0 -4
- package/dist/domain/result.js.map +1 -1
- package/dist/hooks-settings.d.ts +8 -5
- package/dist/hooks-settings.d.ts.map +1 -1
- package/dist/hooks-settings.js +32 -32
- package/dist/hooks-settings.js.map +1 -1
- package/dist/init.d.ts +8 -0
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +3 -3
- package/dist/init.js.map +1 -1
- package/dist/latex-log.d.ts +0 -2
- package/dist/latex-log.d.ts.map +1 -1
- package/dist/latex-log.js +7 -13
- package/dist/latex-log.js.map +1 -1
- package/dist/link-skills.d.ts +15 -2
- package/dist/link-skills.d.ts.map +1 -1
- package/dist/link-skills.js +36 -20
- package/dist/link-skills.js.map +1 -1
- package/dist/paper-files.d.ts.map +1 -1
- package/dist/paper-files.js +6 -4
- package/dist/paper-files.js.map +1 -1
- package/dist/pdf-facts.d.ts +11 -0
- package/dist/pdf-facts.d.ts.map +1 -1
- package/dist/pdf-facts.js +16 -18
- package/dist/pdf-facts.js.map +1 -1
- package/dist/pdf-geometry.js +1 -1
- package/dist/pdf-geometry.js.map +1 -1
- package/dist/presets.d.ts +2 -0
- package/dist/presets.d.ts.map +1 -1
- package/dist/presets.js +3 -1
- package/dist/presets.js.map +1 -1
- package/dist/references.js +2 -2
- package/dist/references.js.map +1 -1
- package/dist/tex-requirements.d.ts +8 -0
- package/dist/tex-requirements.d.ts.map +1 -1
- package/dist/tex-requirements.js +6 -5
- package/dist/tex-requirements.js.map +1 -1
- package/dist/toolchain.d.ts +3 -2
- package/dist/toolchain.d.ts.map +1 -1
- package/dist/toolchain.js +4 -5
- package/dist/toolchain.js.map +1 -1
- package/dist/venue-rules.d.ts.map +1 -1
- package/dist/venue-rules.js +11 -9
- package/dist/venue-rules.js.map +1 -1
- package/docs/prior-art/README.md +1 -1
- package/docs/prior-art/test-tooling.md +7 -30
- package/eslint-rules/install-path-literals.harness.mjs +17 -1
- package/eslint-rules/latex-language.harness.mjs +3 -3
- package/eslint-rules/latex-language.mjs +12 -9
- package/eslint-rules/latex-language.test.mjs +200 -0
- package/eslint-rules/paper-research-question.harness.mjs +73 -7
- package/eslint-rules/paper-stages.harness.mjs +9 -14
- package/eslint-rules/paper-stages.test.mjs +135 -0
- package/eslint-rules/paper-typography.mjs +9 -2
- package/eslint-rules/paper-typography.test.mjs +146 -1
- package/eslint-rules/pdf-last-page-balance.harness.mjs +63 -7
- package/eslint-rules/review-frontmatter.mjs +2 -1
- package/eslint-rules/review-frontmatter.test.mjs +37 -1
- package/eslint-rules/temp-root-realpath.harness.mjs +49 -0
- package/eslint-rules/tex-build.mjs +13 -13
- package/eslint.config.mjs +6 -2
- package/hooks/hooks.harness.mjs +6 -11
- package/hooks/paper-edit-guard.hook.mjs +2 -2
- package/hooks/paper-edit-guard.test.mjs +61 -0
- package/hooks/paper-skills-nudge.hook.mjs +2 -2
- package/hooks/paper-skills-nudge.test.mjs +38 -0
- package/hooks/paper-status-gates.test.mjs +42 -0
- package/lib/agent-cli-version.harness.mjs +7 -0
- package/lib/check.d.mts +17 -0
- package/lib/check.mjs +50 -0
- package/lib/check.test.ts +67 -0
- package/lib/markdown.mjs +16 -69
- package/lib/markdown.test.mjs +246 -0
- package/lib/paper-config.harness.mjs +28 -9
- package/lib/paper-config.mjs +7 -2
- package/lib/skill-checks.mjs +18 -46
- package/lib/skill-checks.test.mjs +297 -0
- package/lib/skill-corpus.mjs +11 -13
- package/lib/skill-corpus.test.mjs +54 -0
- package/lib/skill-trigger-cases.harness.mjs +57 -4
- package/package.json +6 -5
- package/scripts/check.harness.mjs +4 -10
- package/scripts/check.mjs +45 -42
- package/scripts/check.test.mjs +104 -0
- package/scripts/coverage-config.test.ts +406 -0
- package/scripts/eslint-report-guard.test.mjs +48 -0
- package/scripts/harness-api.frozen.json +0 -2
- package/scripts/layer-legacy-frozen.d.mts +9 -1
- package/scripts/layer-legacy-frozen.mjs +28 -11
- package/scripts/layer-legacy-frozen.test.ts +143 -1
- package/scripts/rules-are-content-only.harness.mjs +3 -11
- package/scripts/rules-are-content-only.mjs +29 -19
- package/scripts/rules-are-content-only.test.mjs +61 -0
- package/scripts/rules-see-files.mjs +11 -9
- package/scripts/rules-see-files.test.mjs +71 -0
- package/scripts/test.mjs +44 -0
- package/scripts/test.test.mjs +52 -0
- package/skills/grade-paper-writing/__snapshots__/prose-lint.test.mjs.snap +57 -0
- package/skills/grade-paper-writing/prose-lint.mjs +31 -12
- package/skills/grade-paper-writing/prose-lint.test.mjs +153 -0
- package/skills/paper-pipeline/grade-paper-writing-ablation.eval.mjs +3 -3
- package/skills/paper-pipeline/scripts/README.md +13 -132
- package/skills/paper-pipeline/scripts/announce.test.mjs +102 -0
- package/skills/paper-pipeline/scripts/artifact-coverage.test.mjs +86 -0
- package/skills/paper-pipeline/scripts/consumer.d.mts +8 -1
- package/skills/paper-pipeline/scripts/consumer.harness.mjs +3 -3
- package/skills/paper-pipeline/scripts/consumer.mjs +13 -25
- package/skills/paper-pipeline/scripts/consumer.test.mjs +113 -0
- package/skills/paper-pipeline/scripts/extract-ref-facts.mjs +9 -4
- package/skills/paper-pipeline/scripts/extract-ref-facts.test.mjs +380 -0
- package/skills/paper-pipeline/scripts/fixtures/scorecard.mjs +71 -0
- package/skills/paper-pipeline/scripts/generated-code.harness.mjs +4 -4
- package/skills/paper-pipeline/scripts/generated-code.mjs +3 -2
- package/skills/paper-pipeline/scripts/generated-code.test.mjs +62 -0
- package/skills/paper-pipeline/scripts/ledger.cli.test.mjs +302 -0
- package/skills/paper-pipeline/scripts/ledger.mjs +1 -1
- package/skills/paper-pipeline/scripts/{ledger.selftest.mjs → ledger.test.mjs} +44 -76
- package/skills/paper-pipeline/scripts/pipeline-check.cli.test.mjs +474 -0
- package/skills/paper-pipeline/scripts/pipeline-check.harness.mjs +2 -63
- package/skills/paper-pipeline/scripts/pipeline-check.mjs +37 -21
- package/skills/paper-pipeline/scripts/pipeline-edges.mjs +2 -2
- package/skills/paper-pipeline/scripts/pipeline-edges.test.mjs +73 -0
- package/skills/paper-pipeline/scripts/population-map.harness.mjs +4 -47
- package/skills/paper-pipeline/scripts/population-map.mjs +12 -2
- package/skills/paper-pipeline/scripts/population-map.test.mjs +154 -0
- package/skills/paper-pipeline/scripts/provenance.harness.mjs +26 -0
- package/skills/paper-pipeline/scripts/round-diff.harness.mjs +1 -2
- package/skills/paper-pipeline/scripts/round-diff.mjs +13 -6
- package/skills/paper-pipeline/scripts/round-diff.test.mjs +159 -0
- package/skills/paper-pipeline/scripts/run-mechanical.mjs +30 -6
- package/skills/paper-pipeline/scripts/run-mechanical.test.mjs +468 -0
- package/skills/paper-pipeline/scripts/status.mjs +2 -2
- package/skills/paper-pipeline/scripts/status.test.mjs +232 -0
- package/skills/plan-paper-timeline/plan-paper-timeline.effects.harness.mjs +2 -2
- package/skills/render-paper/extract-pdf-facts.harness.mjs +56 -7
- package/skills/tighten-paper/__snapshots__/structure.test.mjs.snap +133 -0
- package/skills/tighten-paper/structure.test.mjs +116 -0
- package/skills/verify-citations/SKILL.md +5 -5
- package/skills/verify-citations/SKILL.md.spec.ts +4 -4
- package/skills/verify-citations/scripts/__snapshots__/verify-cites.net.test.mjs.snap +235 -0
- package/skills/verify-citations/scripts/bib-authors.check.test.mjs +274 -0
- package/skills/verify-citations/scripts/bib-authors.cli.test.mjs +178 -0
- package/skills/verify-citations/scripts/bib-authors.mjs +2 -1
- package/skills/verify-citations/scripts/verify-cites.cli.test.mjs +160 -0
- package/skills/verify-citations/scripts/verify-cites.mjs +96 -96
- package/skills/verify-citations/scripts/verify-cites.net.test.mjs +287 -0
- package/skills/verify-citations/scripts/verify-cites.test.mjs +149 -5
- package/src/adapters/banal/failure.test.ts +70 -1
- package/src/adapters/banal/index.test.ts +16 -0
- package/src/adapters/banal/output.test.ts +5 -0
- package/src/adapters/curl/download.test.ts +43 -1
- package/src/adapters/memory/index.test.ts +25 -0
- package/src/adapters/node/files.io.ts +3 -2
- package/src/adapters/node/files.test.ts +13 -1
- package/src/adapters/node/process.test.ts +38 -0
- package/src/adapters/pdfjs/fill.test.ts +34 -0
- package/src/adapters/pdfjs/fill.ts +44 -25
- package/src/adapters/references/index.test.ts +228 -0
- package/src/adapters/references/index.ts +21 -6
- package/src/adapters/references/pool.test.ts +31 -0
- package/src/adapters/references/pool.ts +24 -0
- package/src/build-engine.harness.mjs +3 -7
- package/src/build-engine.test.ts +137 -0
- package/src/build.harness.mjs +5 -9
- package/src/build.test.ts +362 -0
- package/src/build.ts +13 -9
- package/src/cli.harness.mjs +10 -17
- package/src/cli.test.ts +584 -0
- package/src/cli.ts +112 -66
- package/src/doctor.harness.mjs +3 -8
- package/src/doctor.test.ts +64 -0
- package/src/doctor.ts +12 -4
- package/src/domain/result.test.ts +4 -0
- package/src/domain/result.ts +0 -5
- package/src/engine.harness.mjs +27 -7
- package/src/facts-file.test.ts +110 -0
- package/src/hooks-settings.harness.mjs +43 -13
- package/src/hooks-settings.ts +41 -35
- package/src/init.test.ts +308 -5
- package/src/init.ts +3 -3
- package/src/latex-log.harness.mjs +32 -7
- package/src/latex-log.ts +6 -13
- package/src/latex-loop.harness.mjs +8 -7
- package/src/link-skills.harness.mjs +3 -8
- package/src/link-skills.test.ts +105 -0
- package/src/link-skills.ts +75 -21
- package/src/new-paper.harness.mjs +3 -7
- package/src/new-paper.test.ts +31 -1
- package/src/paper-files.test.ts +12 -0
- package/src/paper-files.ts +9 -5
- package/src/paper-settings.test.ts +20 -0
- package/src/pdf-facts.harness.mjs +3 -6
- package/src/pdf-facts.test.ts +144 -0
- package/src/pdf-facts.ts +17 -19
- package/src/pdf-geometry.harness.mjs +3 -7
- package/src/pdf-geometry.ts +1 -1
- package/src/presets.test.ts +2 -0
- package/src/presets.ts +7 -1
- package/src/references.test.ts +97 -0
- package/src/references.ts +2 -2
- package/src/rules-config.harness.mjs +10 -8
- package/src/structure.harness.mjs +3 -7
- package/src/tex-requirements.harness.mjs +34 -7
- package/src/tex-requirements.ts +12 -6
- package/src/toolchain.harness.mjs +3 -7
- package/src/toolchain.test.ts +280 -0
- package/src/toolchain.ts +8 -7
- package/src/venue-rules.test.ts +60 -0
- package/src/venue-rules.ts +13 -11
- package/action.mutations.mjs +0 -162
- package/eslint-rules/install-path-literals.mutations.mjs +0 -62
- package/eslint-rules/latex-language.mutations.mjs +0 -196
- package/eslint-rules/paper-research-question.mutations.mjs +0 -127
- package/eslint-rules/paper-stages.mutations.mjs +0 -81
- package/eslint-rules/papers.mutations.mjs +0 -186
- package/eslint-rules/temp-root-realpath.mutations.mjs +0 -99
- package/eslint-rules/tex-build.mutations.mjs +0 -258
- package/hooks/hooks.mutations.mjs +0 -337
- package/lib/agent-cli-version.mutations.mjs +0 -109
- package/lib/mutation-driver.harness.mjs +0 -227
- package/lib/mutation-driver.mjs +0 -397
- package/lib/mutation-driver.mutations.mjs +0 -68
- package/lib/paper-config.mutations.mjs +0 -143
- package/lib/skill-trigger-cases.mutations.mjs +0 -65
- package/scripts/check.mutations.mjs +0 -110
- package/scripts/exclusive.mjs +0 -138
- package/scripts/mutation-batteries-frozen.harness.mjs +0 -204
- package/scripts/mutation-batteries-frozen.mjs +0 -238
- package/scripts/mutation-batteries.frozen.json +0 -108
- package/scripts/rules-are-content-only.mutations.mjs +0 -81
- package/scripts/rules-see-files.mutations.mjs +0 -131
- package/scripts/run-mutations.mjs +0 -100
- package/skills/paper-pipeline/scripts/artifact-coverage.mutations.mjs +0 -218
- package/skills/paper-pipeline/scripts/consumer.mutations.mjs +0 -190
- package/skills/paper-pipeline/scripts/extract-ref-facts.mutations.mjs +0 -54
- package/skills/paper-pipeline/scripts/generated-code.mutations.mjs +0 -254
- package/skills/paper-pipeline/scripts/pipeline-check.mutations.mjs +0 -54
- package/skills/paper-pipeline/scripts/population-map.mutations.mjs +0 -65
- package/skills/paper-pipeline/scripts/population-map.selftest.mjs +0 -122
- package/skills/paper-pipeline/scripts/provenance.mutations.mjs +0 -59
- package/skills/paper-pipeline/scripts/round-diff.mutations.mjs +0 -276
- package/skills/plan-paper-timeline/plan-paper-timeline.effects.mutations.mjs +0 -65
- package/skills/skill-contract.mutations.mjs +0 -179
- package/src/build.mutations.mjs +0 -196
- package/src/cli.mutations.mjs +0 -414
- package/src/doctor.mutations.mjs +0 -175
- package/src/hooks-settings.mutations.mjs +0 -99
- package/src/link-skills.mutations.mjs +0 -140
- package/src/new-paper.mutations.mjs +0 -65
- package/src/structure.mutations.mjs +0 -83
package/scripts/check.mjs
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* ── WHY IT EXISTS, AND IT IS A MEASURED FAILURE, NOT A TIDINESS IDEA ───────────────────────
|
|
6
6
|
* On 2026-09-19 a change to `paper/research-question` was pushed that broke the test suite.
|
|
7
7
|
* The gates were run afterwards — `lint`, `check:readme`, `check:globs`, `check:content-only`,
|
|
8
|
-
* and the rule's own mutation battery, all green — but `npm test` and `test:install` were not,
|
|
8
|
+
* and the rule's own (since-removed) mutation battery, all green — but `npm test` and `test:install` were not,
|
|
9
9
|
* because there were eleven separate scripts and no way to run them except from memory. Five
|
|
10
10
|
* callers across two files had been left behind by the change, and the suite said so; nobody
|
|
11
11
|
* asked it.
|
|
@@ -37,6 +37,7 @@ import { spawnSync } from "node:child_process";
|
|
|
37
37
|
import { existsSync } from "node:fs";
|
|
38
38
|
import { delimiter, dirname, join } from "node:path";
|
|
39
39
|
import { fileURLToPath } from "node:url";
|
|
40
|
+
import { isMain } from "../skills/paper-pipeline/scripts/consumer.mjs";
|
|
40
41
|
|
|
41
42
|
const ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
42
43
|
|
|
@@ -52,8 +53,6 @@ const ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
|
52
53
|
* Programs installed by npm (`vigiles`, `eslint`) are found because `node_modules/.bin` is put
|
|
53
54
|
* first on PATH below, the same way `npm run` does it.
|
|
54
55
|
*/
|
|
55
|
-
const locked = (...cmd) => ["node", "scripts/exclusive.mjs", ...cmd];
|
|
56
|
-
|
|
57
56
|
export const GATES = [
|
|
58
57
|
{
|
|
59
58
|
name: "the package compiles",
|
|
@@ -70,7 +69,7 @@ export const GATES = [
|
|
|
70
69
|
{
|
|
71
70
|
name: "skills lint, and the vigiles marks in README.md and CLAUDE.md",
|
|
72
71
|
job: "gates",
|
|
73
|
-
run:
|
|
72
|
+
run: ["vigiles", "lint", ".", "README.md"],
|
|
74
73
|
// README.md is passed to `vigiles lint` by name: it is not an instruction file, so vigiles
|
|
75
74
|
// would not open it on its own. The marks tie the `paperlint init` and `paperlint new` sections to the
|
|
76
75
|
// functions that implement them, and the lint fails when either function is renamed.
|
|
@@ -85,7 +84,7 @@ export const GATES = [
|
|
|
85
84
|
{
|
|
86
85
|
name: "every declared rule is enabled for a file on disk",
|
|
87
86
|
job: "gates",
|
|
88
|
-
run:
|
|
87
|
+
run: ["node", "scripts/rules-see-files.mjs"],
|
|
89
88
|
},
|
|
90
89
|
{
|
|
91
90
|
name: "rules read content, not the filesystem",
|
|
@@ -96,12 +95,15 @@ export const GATES = [
|
|
|
96
95
|
// locally buys a defect that reaches review.
|
|
97
96
|
reason:
|
|
98
97
|
"source-only property — identical in every environment, so CI adds nothing",
|
|
99
|
-
run:
|
|
98
|
+
run: ["node", "scripts/rules-are-content-only.mjs"],
|
|
100
99
|
},
|
|
101
100
|
{
|
|
102
|
-
name: "every test — vitest
|
|
101
|
+
name: "every test, under coverage — vitest, then the vigiles harnesses, with c8's thresholds (npm run coverage)",
|
|
103
102
|
job: "gates",
|
|
104
|
-
script: "
|
|
103
|
+
script: "coverage",
|
|
104
|
+
// `npm run coverage` IS `npm test`, run under c8 with `--check-coverage` (.c8rc.json holds
|
|
105
|
+
// the thresholds). One run, not two: the tests pass and the coverage floor holds, or the gate
|
|
106
|
+
// is red and says which.
|
|
105
107
|
},
|
|
106
108
|
{
|
|
107
109
|
// vitest transpiles without type-checking, so the tests' types are checked here.
|
|
@@ -109,40 +111,30 @@ export const GATES = [
|
|
|
109
111
|
job: "gates",
|
|
110
112
|
run: ["tsc", "-p", "tsconfig.test.json"],
|
|
111
113
|
},
|
|
112
|
-
{
|
|
113
|
-
name: "mutation batteries are frozen — none new, none grown (#52)",
|
|
114
|
-
job: "gates",
|
|
115
|
-
run: locked("node", "scripts/mutation-batteries-frozen.mjs"),
|
|
116
|
-
},
|
|
117
114
|
{
|
|
118
115
|
name: "legacy layer exemptions are frozen — none new, none grown (#76)",
|
|
119
116
|
job: "gates",
|
|
120
|
-
run:
|
|
121
|
-
},
|
|
122
|
-
{
|
|
123
|
-
name: "mutation batteries — every guard is killed by its own assertion",
|
|
124
|
-
job: "gates",
|
|
125
|
-
run: locked("node", "scripts/run-mutations.mjs"),
|
|
117
|
+
run: ["node", "scripts/layer-legacy-frozen.mjs"],
|
|
126
118
|
},
|
|
127
119
|
{
|
|
128
120
|
name: "install e2e — pack, install under npm and pnpm, run the binary",
|
|
129
121
|
job: "gates",
|
|
130
|
-
run:
|
|
122
|
+
run: ["node", "test/e2e/install.mjs"],
|
|
131
123
|
},
|
|
132
124
|
{
|
|
133
125
|
name: "build e2e — a real pdflatex, and the PDF's fonts are measured",
|
|
134
126
|
job: "build-e2e",
|
|
135
|
-
run:
|
|
127
|
+
run: ["node", "test/e2e/build.mjs"],
|
|
136
128
|
},
|
|
137
129
|
{
|
|
138
130
|
name: "banal e2e — the real banal on pdf.js-written XML gives banal-on-pdftohtml's numbers",
|
|
139
131
|
job: "build-e2e",
|
|
140
|
-
run:
|
|
132
|
+
run: ["node", "test/e2e/banal.mjs"],
|
|
141
133
|
},
|
|
142
134
|
{
|
|
143
135
|
name: "toolchain e2e — real TeX Live into $PAPERLINT_TEXLIVE_DIR, then a build with only it on PATH",
|
|
144
136
|
job: "build-e2e",
|
|
145
|
-
run:
|
|
137
|
+
run: ["node", "test/e2e/toolchain.mjs"],
|
|
146
138
|
},
|
|
147
139
|
];
|
|
148
140
|
|
|
@@ -170,8 +162,7 @@ export const NOT_COVERED = {
|
|
|
170
162
|
};
|
|
171
163
|
|
|
172
164
|
/**
|
|
173
|
-
* Exit 77 means "declared skip" — the same code vigiles' runner uses (`SKIP_EXIT_CODE`)
|
|
174
|
-
* one lib/mutation-driver.mjs already recognizes. The e2e steps exit 77 when a tool they need is
|
|
165
|
+
* Exit 77 means "declared skip" — the same code vigiles' runner uses (`SKIP_EXIT_CODE`). The e2e steps exit 77 when a tool they need is
|
|
175
166
|
* absent and --strict is off. Anything else nonzero is a failure.
|
|
176
167
|
*
|
|
177
168
|
* 🔴 A SKIP IS A THIRD OUTCOME. Until the Codex review on #45 the e2e steps exited 0 on a skip,
|
|
@@ -185,17 +176,29 @@ export function outcome(status) {
|
|
|
185
176
|
return "fail";
|
|
186
177
|
}
|
|
187
178
|
|
|
188
|
-
|
|
179
|
+
/**
|
|
180
|
+
* Run every gate in order and print the verdict and THE TAIL. `spawn`, `log`, `err` and `write`
|
|
181
|
+
* are injected so a test can drive every outcome without running eleven real gates.
|
|
182
|
+
*
|
|
183
|
+
* @returns the exit code: 1 when a gate failed, else 0 (a skip is not a failure, and says so).
|
|
184
|
+
*/
|
|
185
|
+
export function runGates({
|
|
186
|
+
gates = GATES,
|
|
187
|
+
spawn = spawnSync,
|
|
188
|
+
log = console.log,
|
|
189
|
+
err = console.error,
|
|
190
|
+
write = (s) => process.stdout.write(s),
|
|
191
|
+
} = {}) {
|
|
189
192
|
const BIN_FIRST_PATH = [join(ROOT, "node_modules", ".bin"), process.env.PATH]
|
|
190
193
|
.filter(Boolean)
|
|
191
194
|
.join(delimiter);
|
|
192
195
|
const failed = [];
|
|
193
196
|
const skipped = [];
|
|
194
197
|
|
|
195
|
-
for (const g of
|
|
196
|
-
|
|
198
|
+
for (const g of gates) {
|
|
199
|
+
write(`── ${g.name}\n`);
|
|
197
200
|
const [cmd, ...args] = commandOf(g);
|
|
198
|
-
const r =
|
|
201
|
+
const r = spawn(cmd, args, {
|
|
199
202
|
cwd: ROOT,
|
|
200
203
|
stdio: "inherit",
|
|
201
204
|
encoding: "utf8",
|
|
@@ -211,29 +214,29 @@ if (import.meta.url === `file://${process.argv[1]}`) {
|
|
|
211
214
|
else failed.push(`${g.name} (${shown} → ${r.status})`);
|
|
212
215
|
}
|
|
213
216
|
|
|
214
|
-
|
|
217
|
+
log("");
|
|
215
218
|
if (failed.length) {
|
|
216
|
-
|
|
217
|
-
for (const f of failed)
|
|
219
|
+
err("🔴 gates failed:");
|
|
220
|
+
for (const f of failed) err(` ${f}`);
|
|
218
221
|
} else {
|
|
219
|
-
const passed =
|
|
220
|
-
|
|
222
|
+
const passed = gates.length - skipped.length;
|
|
223
|
+
log(
|
|
221
224
|
`✓ ${passed} gate(s) passed${skipped.length ? `, ${skipped.length} SKIPPED — not run, not passed` : ""}`,
|
|
222
225
|
);
|
|
223
226
|
}
|
|
224
|
-
|
|
227
|
+
for (const s of skipped) log(`⏳ skipped: ${s}`);
|
|
225
228
|
|
|
226
229
|
// THE TAIL. Not decoration — the reason this file exists rather than a list in a doc.
|
|
227
|
-
|
|
230
|
+
log("\nWhat this command does NOT cover:");
|
|
228
231
|
for (const [job, why] of Object.entries(NOT_COVERED)) {
|
|
229
|
-
|
|
232
|
+
log(` CI job «${job}» — ${why}`);
|
|
230
233
|
}
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
console.log(` (and «${g.name}» runs ONLY here — ${g.reason})`);
|
|
234
|
+
for (const g of gates.filter((x) => x.job === null)) {
|
|
235
|
+
log(` (and «${g.name}» runs ONLY here — ${g.reason})`);
|
|
234
236
|
}
|
|
235
|
-
|
|
236
|
-
process.exit(failed.length ? 1 : 0);
|
|
237
|
+
return failed.length ? 1 : 0;
|
|
237
238
|
}
|
|
238
239
|
|
|
240
|
+
if (isMain(import.meta.url)) process.exit(runGates());
|
|
241
|
+
|
|
239
242
|
export { ROOT, existsSync, join };
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* check.mjs's runGates — the one command run before a push — on every outcome, with an injected
|
|
3
|
+
* spawn: a pass, a declared skip (77) and a failure, and THE TAIL that names what it does not cover.
|
|
4
|
+
*/
|
|
5
|
+
import assert from "node:assert/strict";
|
|
6
|
+
import { spawnSync } from "node:child_process";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { test } from "vitest";
|
|
9
|
+
import { GATES, NOT_COVERED, runGates } from "./check.mjs";
|
|
10
|
+
|
|
11
|
+
const capture = () => {
|
|
12
|
+
const out = [];
|
|
13
|
+
const io = { log: (s) => out.push(s), err: (s) => out.push(`E ${s}`) };
|
|
14
|
+
return { out, io };
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
test("runGates: a pass, a declared skip (77) and a failure, then the tail naming what is not covered", () => {
|
|
18
|
+
const gates = [
|
|
19
|
+
{ name: "passes", job: "gates", run: ["true"] },
|
|
20
|
+
{ name: "skips", job: "gates", run: ["skipper", "--x"] },
|
|
21
|
+
{
|
|
22
|
+
name: "fails",
|
|
23
|
+
job: null,
|
|
24
|
+
reason: "runs only here, a reason long enough",
|
|
25
|
+
run: ["false"],
|
|
26
|
+
},
|
|
27
|
+
];
|
|
28
|
+
const status = { true: 0, skipper: 77, false: 3 };
|
|
29
|
+
const written = [];
|
|
30
|
+
const { out, io } = capture();
|
|
31
|
+
const code = runGates({
|
|
32
|
+
gates,
|
|
33
|
+
spawn: (cmd) => ({ status: status[cmd] }),
|
|
34
|
+
write: (s) => written.push(s),
|
|
35
|
+
...io,
|
|
36
|
+
});
|
|
37
|
+
assert.deepEqual(
|
|
38
|
+
{ code, written, out },
|
|
39
|
+
{
|
|
40
|
+
code: 1,
|
|
41
|
+
written: ["── passes\n", "── skips\n", "── fails\n"],
|
|
42
|
+
out: [
|
|
43
|
+
"",
|
|
44
|
+
"E 🔴 gates failed:",
|
|
45
|
+
"E fails (false → 3)",
|
|
46
|
+
"⏳ skipped: skips (skipper --x → 77)",
|
|
47
|
+
"\nWhat this command does NOT cover:",
|
|
48
|
+
...Object.entries(NOT_COVERED).map(
|
|
49
|
+
([job, why]) => ` CI job «${job}» — ${why}`,
|
|
50
|
+
),
|
|
51
|
+
" (and «fails» runs ONLY here — runs only here, a reason long enough)",
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("runGates: all passing, with a skip, says how many passed and that the skip is not a pass", () => {
|
|
58
|
+
const { out, io } = capture();
|
|
59
|
+
const code = runGates({
|
|
60
|
+
gates: [
|
|
61
|
+
{ name: "a", job: "gates", script: "build" },
|
|
62
|
+
{ name: "b", job: "gates", run: ["skipper"] },
|
|
63
|
+
],
|
|
64
|
+
spawn: (cmd, args) => ({
|
|
65
|
+
status: cmd === "npm" && args[2] === "build" ? 0 : 77,
|
|
66
|
+
}),
|
|
67
|
+
write: () => {},
|
|
68
|
+
...io,
|
|
69
|
+
});
|
|
70
|
+
assert.deepEqual(
|
|
71
|
+
[code, out[1], out[2]],
|
|
72
|
+
[
|
|
73
|
+
0,
|
|
74
|
+
"✓ 1 gate(s) passed, 1 SKIPPED — not run, not passed",
|
|
75
|
+
"⏳ skipped: b (skipper → 77)",
|
|
76
|
+
],
|
|
77
|
+
);
|
|
78
|
+
assert.ok(GATES.length > 0);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("run as a program with every gate passing: the plain verdict, exit 0", () => {
|
|
82
|
+
// spawnSync is replaced before check.mjs loads, so the real file runs end to end — every gate
|
|
83
|
+
// "passes" in microseconds instead of the eleven real ones running.
|
|
84
|
+
const allPass = [
|
|
85
|
+
'import cp from "node:child_process";',
|
|
86
|
+
'import { syncBuiltinESMExports } from "node:module";',
|
|
87
|
+
"cp.spawnSync = () => ({ status: 0 });",
|
|
88
|
+
"syncBuiltinESMExports();",
|
|
89
|
+
].join("\n");
|
|
90
|
+
const r = spawnSync(
|
|
91
|
+
process.execPath,
|
|
92
|
+
[
|
|
93
|
+
"--import",
|
|
94
|
+
`data:text/javascript,${encodeURIComponent(allPass)}`,
|
|
95
|
+
fileURLToPath(new URL("./check.mjs", import.meta.url)),
|
|
96
|
+
],
|
|
97
|
+
{ encoding: "utf8" },
|
|
98
|
+
);
|
|
99
|
+
assert.equal(r.status, 0, r.stderr);
|
|
100
|
+
assert.match(
|
|
101
|
+
r.stdout,
|
|
102
|
+
new RegExp(`\\n✓ ${String(GATES.length)} gate\\(s\\) passed\\n`),
|
|
103
|
+
);
|
|
104
|
+
});
|