From 5301080508d95bdac5adc1c5e24ff77289a1535f Mon Sep 17 00:00:00 2001 From: Scott Lowe Date: Sat, 3 Oct 2026 00:16:35 -0700 Subject: [PATCH] test(docs): check that doc links, commands and flags exist The README now links into eight guides that link to each other, and the docs name many commands and flags. Nothing checked either: renaming a heading, a script or a flag would leave the docs quietly wrong. - tests/docs-links.test.ts: every relative link and #anchor in the root markdown files, docs/ and examples/ resolves (GitHub's heading anchor rules, code blocks ignored). - tests/docs-commands.test.ts: every `npm run ` in the docs is a package.json script, and every documented --flag (after `npm run` on the same line, or on its own in inline code) appears in src/. Flags of other tools are allow-listed, and improvements.md is skipped as a historical log. Each test fails when a heading, a script or a flag is renamed (checked by breaking one of each). Co-Authored-By: Claude Opus 5.5 --- tests/docs-commands.test.ts | 102 ++++++++++++++++++++++++++++++++++++ tests/docs-links.test.ts | 75 ++++++++++++++++++++++++++ 2 files changed, 177 insertions(+) create mode 100644 tests/docs-commands.test.ts create mode 100644 tests/docs-links.test.ts diff --git a/tests/docs-commands.test.ts b/tests/docs-commands.test.ts new file mode 100644 index 0000000..974e278 --- /dev/null +++ b/tests/docs-commands.test.ts @@ -0,0 +1,102 @@ +import assert from "node:assert/strict"; +import { readdirSync, readFileSync } from "node:fs"; +import { join } from "node:path"; +import test from "node:test"; +import { fileURLToPath } from "node:url"; + +// The commands the docs tell people to run must exist: every `npm run ` +// is a package.json script, and every documented `--flag` is one the engine +// still knows. Renaming or removing a script or a flag fails here until the +// docs follow. + +const REPO = fileURLToPath(new URL("..", import.meta.url)); + +// improvements.md is a historical log: its entries describe commands and +// flags as they were when each problem was found, and are never rewritten. +const HISTORICAL_DOCS = new Set(["improvements.md"]); + +// Flags in the docs that belong to other tools (git, npm, node). +const EXTERNAL_FLAGS = new Set(["--allow-unrelated-histories"]); + +function docFiles(): string[] { + const files = readdirSync(REPO).filter( + (name) => name.endsWith(".md") && !HISTORICAL_DOCS.has(name), + ); + const walk = (dir: string) => { + for (const entry of readdirSync(join(REPO, dir), { withFileTypes: true })) { + const path = `${dir}/${entry.name}`; + if (entry.isDirectory()) walk(path); + else if (entry.name.endsWith(".md")) files.push(path); + } + }; + walk("docs"); + walk("examples"); + return files.sort(); +} + +interface Mention { + file: string; + value: string; +} + +function mentions(): { scripts: Mention[]; flags: Mention[] } { + const scripts: Mention[] = []; + const flags: Mention[] = []; + for (const file of docFiles()) { + const text = readFileSync(join(REPO, file), "utf8"); + for (const line of text.split("\n")) { + for (const match of line.matchAll(/npm run ([a-z][a-z:-]*)/g)) + scripts.push({ file, value: match[1]! }); + // Flags written after `npm run …` on the same line. + const start = line.indexOf("npm run "); + if (start >= 0) + for (const match of line + .slice(start) + .matchAll(/(? name.endsWith(".ts")) + .map((name) => readFileSync(join(REPO, "src", name), "utf8")) + .join("\n"); +} + +const unique = (list: Mention[]) => + [...new Map(list.map((m) => [`${m.value} (${m.file})`, m])).keys()].sort(); + +test("every `npm run