"use strict";
const { execFileSync } = require("node:child_process");
const fs = require("node:fs");
const path = require("node:path");
const root = path.resolve(__dirname, "..");
const read = (f) => fs.readFileSync(path.join(root, f), "utf8");
const SELF = "scripts/check-no-local-refs.cjs";
function buildAllowlist() {
const allow = new Set(["murk-cli", "murk-key"]);
try {
const ref = read("docs/cli-reference.md");
const re = /\[`(murk[a-z0-9 -]*)`/g;
let m;
while ((m = re.exec(ref)) !== null) {
allow.add(m[1].trim().replace(/\s+/g, "-"));
}
} catch {
}
return allow;
}
const TOKEN = /(?<![a-z0-9-])murk-[a-z0-9]{3}(?![a-z0-9-])(?:\.[0-9]+)*/g;
const baseOf = (token) => token.replace(/(?:\.[0-9]+)+$/, "");
const isAllowed = (allow, token) => allow.has(token) || allow.has(baseOf(token));
const flaggedInLine = (allow, line) =>
(line.match(TOKEN) || []).filter((tok) => !isAllowed(allow, tok));
const isBinaryPath = (p) =>
/\.(gif|png|jpe?g|webp|ico|svg|woff2?|ttf|otf|eot|pdf|zip|gz|wasm)$/i.test(p);
function selfTest(allow) {
const flag = [
"see murk-x1y.2 for context",
"(murk-q9q.5)",
"murk-z0z",
"regression for murk-a7b",
"murk-q9q.1",
"(murk-x1y.3)",
];
const pass = [
"murk-cli",
"the murk-key: prefix",
"@interrupted/murk-secrets",
"murk-napi crate",
"#murk-agent-plan",
"murk-init",
"murk-mcp anchor",
"the murk-docs package",
"murk-linux artifact",
"--murk-accent-glow",
"murk-secrets-linux-x64-gnu",
"murk-cli-fuzz",
"murk-v0.8.0",
];
let ok = true;
for (const line of flag) {
if (flaggedInLine(allow, line).length === 0) {
console.error(`self-test FAIL: expected a hit in: ${line}`);
ok = false;
}
}
for (const line of pass) {
const hits = flaggedInLine(allow, line);
if (hits.length) {
console.error(`self-test FAIL: false positive ${hits.join(",")} in: ${line}`);
ok = false;
}
}
if (!ok) process.exit(1);
console.log(`self-test OK (${flag.length} hits caught, ${pass.length} legit tokens passed)`);
}
function main() {
const allow = buildAllowlist();
if (process.argv.includes("--self-test")) {
selfTest(allow);
return;
}
const files = execFileSync("git", ["ls-files"], {
cwd: root,
encoding: "utf8",
maxBuffer: 1 << 26,
})
.split("\n")
.filter(Boolean)
.filter((f) => f !== SELF)
.filter((f) => !isBinaryPath(f));
const violations = [];
for (const f of files) {
let text;
try {
text = read(f);
} catch {
continue;
}
if (text.includes("\u0000")) continue; text.split("\n").forEach((line, i) => {
for (const tok of flaggedInLine(allow, line)) {
violations.push({ file: f, line: i + 1, tok });
}
});
}
if (violations.length) {
console.error(
`::error::local-only reference IDs must not appear in committed files (${violations.length} found):`,
);
for (const v of violations) {
console.error(` ${v.file}:${v.line} ${v.tok}`);
}
process.exit(1);
}
console.log(`OK: no stray local reference IDs in ${files.length} tracked files`);
}
main();