Source commit: f70e47af061d8f7ba27cd769c8cd2e2f8707dc72 Public tree identity: sha256:03a43db60d295811688bedaa5ad6460df0dbde27fbf37033997f4d8100f83ab2
111 lines
4 KiB
JavaScript
111 lines
4 KiB
JavaScript
#!/usr/bin/env node
|
|
const fs = require("node:fs");
|
|
const path = require("node:path");
|
|
|
|
const root = path.resolve(__dirname, "..");
|
|
const publicDocs = [
|
|
"README.md",
|
|
"SECURITY.md",
|
|
"docs/getting-started.md",
|
|
"docs/architecture.md",
|
|
"docs/nodes.md",
|
|
"docs/environments.md",
|
|
"docs/artifacts.md",
|
|
"docs/debugging.md",
|
|
"docs/task-abi.md",
|
|
"docs/self-hosting.md",
|
|
"docs/security.md",
|
|
];
|
|
const privateDocs = [
|
|
"private/docs/hosted-deployment.md",
|
|
"private/docs/authentik.md",
|
|
"private/docs/community-policy.md",
|
|
"private/docs/bandwidth-and-cost-controls.md",
|
|
"private/docs/publishing.md",
|
|
];
|
|
const internalDocs = ["internal/finish_mvp_2.md"];
|
|
const filteredPublicTree =
|
|
process.env.CLUSTERFLUX_FILTERED_PUBLIC_TREE === "1" ||
|
|
fs.existsSync(path.join(root, "CLUSTERFLUX_PUBLIC_TREE.json"));
|
|
|
|
const failures = [];
|
|
const expectExactMarkdownSet = (directory, expected) => {
|
|
const actual = fs
|
|
.readdirSync(path.join(root, directory), { withFileTypes: true })
|
|
.filter((entry) => entry.isFile() && entry.name.endsWith(".md"))
|
|
.map((entry) => path.posix.join(directory, entry.name))
|
|
.sort();
|
|
const wanted = [...expected].sort();
|
|
if (JSON.stringify(actual) !== JSON.stringify(wanted)) {
|
|
failures.push(`${directory} markdown set is ${actual.join(", ")}; expected ${wanted.join(", ")}`);
|
|
}
|
|
};
|
|
|
|
const requiredDocs = filteredPublicTree
|
|
? publicDocs
|
|
: [...publicDocs, ...privateDocs, ...internalDocs];
|
|
for (const file of requiredDocs) {
|
|
if (!fs.existsSync(path.join(root, file))) failures.push(`missing canonical document: ${file}`);
|
|
}
|
|
expectExactMarkdownSet("docs", publicDocs.filter((file) => file.startsWith("docs/")));
|
|
if (filteredPublicTree) {
|
|
for (const directory of ["private", "internal"]) {
|
|
if (fs.existsSync(path.join(root, directory))) {
|
|
failures.push(`${directory}/ must not exist in the filtered public tree`);
|
|
}
|
|
}
|
|
}
|
|
|
|
const forbidden = [
|
|
[/\bmvp\b/i, "internal milestone term"],
|
|
[/acceptance criteria/i, "internal gate language"],
|
|
[/release verification/i, "internal release language"],
|
|
[/public\/private source split/i, "source split narrative"],
|
|
[/founder|business decisions/i, "business planning narrative"],
|
|
[/hacker news|\bHN\b/, "launch-channel narrative"],
|
|
[/\busers can\b/i, "indirect reader wording"],
|
|
[/node\s+scripts\//i, "developer script instruction"],
|
|
[/scripts\/[^\s)]*smoke/i, "smoke script instruction"],
|
|
[/internal\/[^\s)]*/i, "internal tooling reference"],
|
|
[/private\/[^\s)]*/i, "private source reference"],
|
|
];
|
|
const topLevelCommands = new Set([
|
|
"doctor", "login", "logout", "auth", "agent", "key", "project", "inspect",
|
|
"build", "bundle", "run", "node", "process", "task", "logs", "artifact",
|
|
"dap", "debug", "quota", "admin",
|
|
]);
|
|
|
|
for (const file of publicDocs) {
|
|
const absolute = path.join(root, file);
|
|
const content = fs.readFileSync(absolute, "utf8");
|
|
for (const [pattern, description] of forbidden) {
|
|
if (pattern.test(content)) failures.push(`${file}: contains ${description}`);
|
|
}
|
|
for (const match of content.matchAll(/\]\(([^)#]+)(?:#[^)]+)?\)/g)) {
|
|
const target = match[1];
|
|
if (/^(?:https?:|mailto:)/.test(target)) continue;
|
|
const resolved = path.resolve(path.dirname(absolute), target);
|
|
if (!fs.existsSync(resolved)) failures.push(`${file}: broken link ${target}`);
|
|
}
|
|
for (const line of content.split(/\r?\n/)) {
|
|
const command = line.trim().match(/^clusterflux\s+([a-z][a-z-]*)\b/);
|
|
if (command && !topLevelCommands.has(command[1])) {
|
|
failures.push(`${file}: unknown top-level CLI command ${command[1]}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
const rootMarkdown = fs
|
|
.readdirSync(root, { withFileTypes: true })
|
|
.filter((entry) => entry.isFile() && entry.name.endsWith(".md"))
|
|
.map((entry) => entry.name)
|
|
.sort();
|
|
if (JSON.stringify(rootMarkdown) !== JSON.stringify(["README.md", "SECURITY.md"])) {
|
|
failures.push(`top-level markdown set is ${rootMarkdown.join(", ")}; expected README.md, SECURITY.md`);
|
|
}
|
|
|
|
if (failures.length) {
|
|
for (const failure of failures) console.error(failure);
|
|
process.exit(1);
|
|
}
|
|
console.log("documentation checks passed");
|