Files
initiative/scripts/check-gates.mjs
T
LukasandClaude Opus 5 67c398d3a2 Make the test gate fail when no tests match
vitest.config.ts set passWithNoTests: true, added in the 7dd4abb
scaffolding commit when the repo genuinely had no tests yet. Once tests
existed it became the same hazard as the oxlint bugs fixed in 91f46ff:
if the include globs ever stopped matching, the suite would exit 0
having run nothing.

Vitest already defaults to failing in that case, so removing the line is
the whole fix — a broken glob now reports "No test files found, exiting
with code 1". Verified by pointing the globs at a nonexistent directory.

Extends the gate check to flag the option coming back, and renames it
from check-lint-gate to check-gates now that it covers more than oxlint.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 14:16:04 +02:00

78 lines
2.5 KiB
JavaScript

import { execFileSync } from "node:child_process";
import { readFileSync } from "node:fs";
import { join } from "node:path";
const ROOT = new URL("..", import.meta.url).pathname.replace(/\/$/, "");
const RUN_LINE_RE = /^\s*run:\s*(.+?)\s*$/;
/** @param {string} path */
const read = (path) => readFileSync(join(ROOT, path), "utf-8");
/**
* Gates must invoke `pnpm oxlint` with no extra arguments: pnpm forwards them
* after `--`, where oxlint reads them as file paths and lints nothing.
* @param {Record<string, string>} scripts
*/
function findGatesWithOwnFlags(scripts) {
const commands = Object.entries(scripts)
.filter(([name]) => name !== "oxlint")
.flatMap(([name, body]) =>
body.split("&&").map((c) => [`scripts.${name}`, c.trim()]),
);
for (const line of read("lefthook.yml").split("\n")) {
const command = RUN_LINE_RE.exec(line)?.[1];
if (command) commands.push(["lefthook.yml", command]);
}
return commands
.filter(([, c]) => c.includes("oxlint") && c !== "pnpm oxlint")
.map(([source, c]) => `${source} runs "${c}" instead of "pnpm oxlint"`);
}
/**
* Runs the configured command with one rule forced to warn. A working gate
* exits non-zero; exiting 0 means it lints nothing, or does not fail on
* warnings.
* @param {string} oxlintScript
*/
function failsOnWarnings(oxlintScript) {
const args = [...oxlintScript.split(" ").slice(1), "-W", "no-console"];
try {
execFileSync(join(ROOT, "node_modules/.bin/oxlint"), args, {
cwd: ROOT,
stdio: "ignore",
});
return false;
} catch {
return true;
}
}
/**
* Reports ways a quality gate could pass without checking anything, which is
* indistinguishable from a clean run.
* @returns {string[]}
*/
export function checkGates() {
/** @type {Record<string, string>} */
const scripts = JSON.parse(read("package.json")).scripts;
const violations = findGatesWithOwnFlags(scripts);
// Vitest defaults to failing when no test file matches. Setting this puts
// the test gate back to exiting 0 if the include globs ever stop matching.
if (read("vitest.config.ts").includes("passWithNoTests")) {
violations.push("vitest.config.ts sets passWithNoTests");
}
// The canary rule below is not type-aware, so the probe alone cannot
// detect type-aware rules being switched off.
if (!scripts.oxlint.includes("--type-aware")) {
violations.push("scripts.oxlint is missing --type-aware");
}
if (!failsOnWarnings(scripts.oxlint)) {
violations.push(
`scripts.oxlint exits 0 despite no-console violations: "${scripts.oxlint}"`,
);
}
return violations;
}