#!/usr/bin/env node // Durable guard for the "every first-party source file is format-gated" invariant // that #1789 established by a one-shot manual audit (PR #1792). A prettier // `format`/`format:check` glob that stops covering a file fails silently — the // file is simply skipped, not reported — so a nested dir (`.storybook/`, a // client `scripts/`) or a new extension can re-open the gap unnoticed. This is // the format-coverage analog of `verify:build-gate`: it enumerates every tracked // source file and asserts each is matched by at least one `prettier --check` // glob declared across the repo's `package.json`s. Exits non-zero, listing the // offenders, on any miss. // // Source of truth is the `format:check*` scripts themselves — this parser reads // the globs out of them, so widening/narrowing a glob is reflected here with no // second list to keep in sync. import { readFileSync } from "node:fs"; import { execFileSync } from "node:child_process"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { reachableScripts, rootReachesScript, rootRunsClientValidate, tokenize, } from "./lib/npm-scripts.mjs"; const repoRoot = path.resolve( path.dirname(fileURLToPath(import.meta.url)), "..", ); // File extensions prettier formats as first-party source here. Kept in sync with // the union of the format globs; a file with one of these extensions that no // glob matches is the failure this guard exists to catch. const SOURCE_EXTENSIONS = [ "ts", "tsx", "mts", "cts", "js", "jsx", "mjs", "cjs", ]; // The `package.json`s whose `format:check*` scripts define the gate. The root // carries the split `format:check:core|scripts|shared`; each client carries a // single `format:check`. Paths are relative to the repo root; each glob resolves // relative to its manifest's directory (prettier's cwd for that script). const MANIFESTS = [ ".", "clients/web", "clients/cli", "clients/tui", "clients/launcher", ]; /** * Extract the path/glob args from every `prettier --check …` in a manifest's * scripts that is reachable from `validate`. Restricting to reachable scripts is * what makes the guard assert "this file is checked by CI", not merely "some * glob covers it". */ function prettierCheckArgs(scripts) { const reachable = reachableScripts(scripts); const args = []; for (const [name, value] of Object.entries(scripts ?? {})) { if (!reachable.has(name)) continue; if (typeof value !== "string" || !value.includes("prettier --check")) continue; // A manifest may chain `prettier --check …` inside a larger script; take the // segment starting at each occurrence up to the next `&&`/`||`/`;`. for (const segment of value.split(/&&|\|\||;/)) { const trimmed = segment.trim(); if (!trimmed.startsWith("prettier --check")) continue; const tokens = tokenize(trimmed).slice(2); // drop `prettier` `--check` for (const t of tokens) { if (t.startsWith("-")) continue; // a flag, not a path args.push(t); } } } return args; } const GLOB_CHARS = /[*?{}[\]]/; /** Convert a prettier glob to an anchored RegExp over repo-relative POSIX paths. */ function globToRegExp(glob) { let re = ""; for (let i = 0; i < glob.length; i++) { const c = glob[i]; if (c === "*") { if (glob[i + 1] === "*") { // `**` (optionally `**/`) crosses path separators. if (glob[i + 2] === "/") { re += "(?:.*/)?"; i += 2; } else { re += ".*"; i += 1; } } else { re += "[^/]*"; // `*` stays within a segment } } else if (c === "?") { re += "[^/]"; } else if (c === "{") { re += "(?:"; } else if (c === "}") { re += ")"; } else if (c === ",") { re += "|"; } else if (".+^$()|\\/".includes(c)) { re += "\\" + c; } else { re += c; } } return new RegExp("^" + re + "$"); } /** * Assert the root `validate` chain actually invokes each non-root manifest's * `validate` (via `cd