Skip to content

Commit cf6fc14

Browse files
authored
Merge pull request #48 from script-development/armory-publint-git-prefix-sweep
fs-packages: re-normalize 10 publint git+ prefix regressions (PR #35 aftermath)
2 parents 219b114 + 05149fc commit cf6fc14

3 files changed

Lines changed: 92 additions & 2 deletions

File tree

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Shared frontend service packages monorepo under the `@script-development` npm sc
99
- **Test:** vitest 4 (100% coverage threshold) + Stryker (90% mutation threshold)
1010
- **Lint:** oxlint (explicit config at `.oxlintrc.json`)
1111
- **Format:** oxfmt
12-
- **Package lint:** publint + attw (Are The Types Wrong)
12+
- **Package lint:** publint + attw (Are The Types Wrong)`lint:pkg` enforces fail-on-any-advisory via `scripts/lint-pkg.mjs` (suggestions, warnings, and errors all treat as fatal — publint CLI default and `--strict` both exit 0 on suggestions). Motivated by enforcement queue #33 + the PR #35 `git+` prefix regression that silently drifted across 10 packages because the unenforced gate only printed the suggestion.
1313
- **Publish:** OIDC Trusted Publishing to public npm registry (no stored tokens)
1414
- **CI:** 8-gate pipeline: audit → format → lint → build → typecheck → lint:pkg → coverage → mutation
1515

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"test:mutation": "npm run test:mutation --workspaces",
1212
"lint": "oxlint .",
1313
"typecheck": "npm run typecheck --workspaces",
14-
"lint:pkg": "npm run lint:pkg --workspaces",
14+
"lint:pkg": "node scripts/lint-pkg.mjs",
1515
"audit": "npm audit",
1616
"format": "oxfmt --write .",
1717
"format:check": "oxfmt --check .",

scripts/lint-pkg.mjs

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
#!/usr/bin/env node
2+
// Gate 6 (lint:pkg) enforcer — treats publint suggestions/warnings/errors as fatal.
3+
//
4+
// publint 0.3.18 CLI does not expose a flag to fail on suggestions (--strict
5+
// only promotes warnings → errors). This wrapper fills that gap: it runs
6+
// publint per workspace, captures stdout, and fails the gate if any package
7+
// emits a "Suggestions:", "Warnings:", or "Errors:" block. attw --pack runs
8+
// after publint per package and preserves its own exit code.
9+
//
10+
// See enforcement queue #33 and the PR #35 regression that motivated this
11+
// tightening: publint suggestions about the "git+" URL prefix silently
12+
// re-drifted across 10 packages because the gate tolerated them.
13+
14+
import { spawnSync } from "node:child_process";
15+
import { readdirSync, readFileSync, statSync } from "node:fs";
16+
import { join } from "node:path";
17+
18+
const PACKAGES_DIR = "packages";
19+
const PUBLINT_BLOCK_RE = /^(Suggestions|Warnings|Errors):$/m;
20+
21+
function listPackageDirs() {
22+
return readdirSync(PACKAGES_DIR)
23+
.map((name) => join(PACKAGES_DIR, name))
24+
.filter((dir) => {
25+
try {
26+
return statSync(dir).isDirectory() && statSync(join(dir, "package.json")).isFile();
27+
} catch {
28+
return false;
29+
}
30+
})
31+
.sort();
32+
}
33+
34+
function packageName(dir) {
35+
const pkg = JSON.parse(readFileSync(join(dir, "package.json"), "utf8"));
36+
return pkg.name ?? dir;
37+
}
38+
39+
function runCaptured(cmd, args, cwd) {
40+
const result = spawnSync(cmd, args, {
41+
cwd,
42+
stdio: ["ignore", "pipe", "pipe"],
43+
encoding: "utf8",
44+
shell: false,
45+
});
46+
const stdout = result.stdout ?? "";
47+
const stderr = result.stderr ?? "";
48+
process.stdout.write(stdout);
49+
process.stderr.write(stderr);
50+
return { stdout, stderr, status: result.status ?? 1 };
51+
}
52+
53+
function main() {
54+
const dirs = listPackageDirs();
55+
const failures = [];
56+
57+
for (const dir of dirs) {
58+
const name = packageName(dir);
59+
process.stdout.write(`\n--- lint:pkg ${name} (${dir}) ---\n`);
60+
61+
const publint = runCaptured("npx", ["publint", "run"], dir);
62+
const publintBlock = PUBLINT_BLOCK_RE.exec(publint.stdout);
63+
if (publint.status !== 0) {
64+
failures.push(`${name}: publint exited ${publint.status}`);
65+
} else if (publintBlock) {
66+
failures.push(
67+
`${name}: publint emitted "${publintBlock[1]}:" block (fail-on-suggestion gate)`,
68+
);
69+
}
70+
71+
const attw = runCaptured("npx", ["attw", "--pack"], dir);
72+
if (attw.status !== 0) {
73+
failures.push(`${name}: attw exited ${attw.status}`);
74+
}
75+
}
76+
77+
if (failures.length > 0) {
78+
process.stderr.write(`\n\nlint:pkg gate FAILED (${failures.length}):\n`);
79+
for (const f of failures) {
80+
process.stderr.write(` - ${f}\n`);
81+
}
82+
process.exit(1);
83+
}
84+
85+
process.stdout.write(
86+
`\nlint:pkg gate PASS — ${dirs.length} packages clean (publint suggestions/warnings/errors all treated as fatal).\n`,
87+
);
88+
}
89+
90+
main();

0 commit comments

Comments
 (0)