feat(FN-5189): complete Step 5 — guard against nohup regressions

Fusion-Task-Id: FN-5189
Fusion-Task-Lineage: 4caa3f0a-af81-4c60-88e8-de229ed72e08
This commit is contained in:
Fusion (runfusion.ai)
2026-05-19 15:56:28 -07:00
committed by gsxdsm
parent dc8dd33f80
commit 8c05682ab4
4 changed files with 202 additions and 0 deletions

View File

@@ -0,0 +1,28 @@
import test from "node:test";
import assert from "node:assert/strict";
const checkerModule = await import(["..", "/check-no-no", "hup", ".mjs"].join(""));
const { formatFailureMessage, scanFileContent } = checkerModule;
const bannedToken = ["no", "hup"].join("");
test("scanFileContent reports banned token matches", () => {
const source = `pnpm ${bannedToken} dev`;
const matches = scanFileContent(source, "scripts/example.mjs");
assert.equal(matches.length, 1);
assert.equal(matches[0].lineNumber, 1);
assert.match(matches[0].line, new RegExp(bannedToken));
});
test("scanFileContent ignores allowlisted lines", () => {
const source = `// process-supervisor-allowlist: ${bannedToken} mention is explanatory only`;
const matches = scanFileContent(source, "scripts/example.mjs");
assert.equal(matches.length, 0);
});
test("formatFailureMessage points callers at superviseSpawn", () => {
const message = formatFailureMessage([
{ filePath: "scripts/example.mjs", lineNumber: 3, line: `pnpm ${bannedToken} dev` },
]);
assert.match(message, /superviseSpawn/);
assert.match(message, /scripts\/example\.mjs:3/);
});

View File

@@ -0,0 +1,68 @@
#!/usr/bin/env node
import { readFileSync } from "node:fs";
import { spawnSync } from "node:child_process";
import { fileURLToPath } from "node:url";
const token = ["no", "hup"].join("");
const tokenPattern = new RegExp(`\\b${token}\\b`);
const allowlistMarker = "process-supervisor-allowlist";
function listTrackedTargets() {
const result = spawnSync("git", ["ls-files", "--", "packages", "scripts"], {
encoding: "utf8",
stdio: ["ignore", "pipe", "pipe"],
});
if (result.status !== 0) {
throw new Error(result.stderr?.trim() || "git ls-files failed");
}
return result.stdout
.split("\n")
.map((line) => line.trim())
.filter(Boolean);
}
export function scanFileContent(content, filePath) {
const matches = [];
const lines = content.split(/\r?\n/);
for (let index = 0; index < lines.length; index += 1) {
const line = lines[index];
if (!tokenPattern.test(line) || line.includes(allowlistMarker)) {
continue;
}
matches.push({ filePath, lineNumber: index + 1, line });
}
return matches;
}
export function scanTrackedFiles(files = listTrackedTargets()) {
const matches = [];
for (const filePath of files) {
const content = readFileSync(filePath, "utf8");
matches.push(...scanFileContent(content, filePath));
}
return matches;
}
export function formatFailureMessage(matches) {
const lines = matches.map(({ filePath, lineNumber, line }) => `${filePath}:${lineNumber}: ${line.trim()}`);
return [
`[check-no-${token}] found banned ${token} usage under packages/** or scripts/**. Use superviseSpawn(...) instead.`,
...lines,
].join("\n");
}
export function main() {
const matches = scanTrackedFiles();
if (matches.length === 0) {
return 0;
}
console.error(formatFailureMessage(matches));
return 1;
}
if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
process.exitCode = main();
}