Files
ps5upload/scripts/audit-scripts.mjs
T
PhantomPtrandClaude Opus 5.5 832e2afcd2 fix(docker): the web UI image bundles the FAQ and Changelog (#337)
* fix(docker): the web UI image bundles the FAQ and Changelog

Since the FAQ and Changelog screens bundle ../FAQ.md and ../CHANGELOG.md,
the web UI image's frontend stage failed: it built the client at /src
and .dockerignore excluded every *.md. The stage now keeps the repo
layout (/repo/client) and copies the two files in.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(scripts): the audit counts a script's node:test file as used

scripts:check runs every *.test.mjs by pattern, so the test beside
stale-client-lib.mjs is named nowhere and the strict audit called it an
orphan (repo scripts failed on all three OSes).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 13:04:23 -07:00

83 lines
3.0 KiB
JavaScript
Executable File

#!/usr/bin/env node
import { spawnSync } from "node:child_process";
import fs from "node:fs";
import path from "node:path";
const repoRoot = path.resolve(import.meta.dirname, "..");
const strict = process.argv.includes("--strict");
const manualEntrypoints = new Set([
// Manual throughput harness against a live PS5 (see its header). Its only
// mention was in docs/, which moved to the ps5upload-docs repo.
"bench/multistream-hw-test.mjs",
"scripts/bundle-fonts.py",
"scripts/find-orphan-i18n.mjs",
"scripts/hw-test-loop.sh",
"scripts/i18n-fill-missing.mjs",
"scripts/i18n-prune-unused.mjs",
"scripts/release-posts.js",
"scripts/translate-i18n.py",
"tests/lab/README.md",
// Both test_*.py are hardware-lab manual entrypoints. The
// launch-only one was already documented; the install_launch one
// was passing the audit via an accidental self-reference (the
// grep counts the basename appearing in the file's own argparse
// help text). Rename-safe addition keeps strict-mode passes.
"tests/lab/test_install_launch.py",
"tests/lab/test_launch_only.py",
]);
function gitLsFiles(prefixes) {
const res = spawnSync("git", ["ls-files", ...prefixes], {
cwd: repoRoot,
encoding: "utf8",
});
if (res.status !== 0) throw new Error(res.stderr || "git ls-files failed");
return res.stdout.trim().split(/\n/).filter(Boolean);
}
function countRefs(needle, haystackFiles) {
const base = path.basename(needle);
let refs = 0;
for (const file of haystackFiles) {
if (file === "scripts/audit-scripts.mjs") continue;
try {
const text = fs.readFileSync(path.join(repoRoot, file), "utf8");
if (text.includes(base)) refs += 1;
} catch {
// Binary or unreadable tracked file: irrelevant for script references.
}
}
return refs;
}
const files = gitLsFiles(["scripts", "tests/lab", "bench"]);
const haystackFiles = gitLsFiles(["."]).filter((file) =>
!file.startsWith("client/node_modules/")
&& !file.startsWith("engine/target/")
&& !file.startsWith("client/src-tauri/target/")
&& !file.startsWith("client/dist/")
);
const executableLike = files.filter((f) => /\.(mjs|js|py|sh|ps1)$/i.test(f));
// A `*.test.mjs` beside a script is run by check-scripts.mjs (`node --test` over
// every such file), so it is used without being named anywhere.
const runByCheckScripts = (file) => /\.test\.mjs$/.test(file);
const rows = executableLike.map((file) => ({
file,
refs: runByCheckScripts(file) ? 1 : countRefs(file, haystackFiles),
manual: manualEntrypoints.has(file),
}));
const suspicious = rows.filter((r) => r.refs === 0 && !r.manual);
process.stdout.write("[audit-scripts] tracked script/test utilities\n");
for (const r of rows) {
const mark = r.manual ? "manual" : r.refs === 0 ? "orphan?" : "used";
process.stdout.write(`${String(r.refs).padStart(3)} ${mark.padEnd(7)} ${r.file}\n`);
}
if (suspicious.length > 0) {
process.stdout.write("\n[audit-scripts] review these unreferenced files:\n");
for (const r of suspicious) process.stdout.write(` ${r.file}\n`);
if (strict) process.exit(1);
}