rea/scripts/check-typedoc.mjs

68 lines
2.4 KiB
JavaScript

import { execFile } from "node:child_process";
import { createHash } from "node:crypto";
import { mkdtemp, readFile, readdir, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { dirname, join, relative } from "node:path";
import { promisify } from "node:util";
import { fileURLToPath } from "node:url";
const execFileAsync = promisify(execFile);
const root = join(dirname(fileURLToPath(import.meta.url)), "..");
const temporaryRoot = await mkdtemp(join(tmpdir(), "rea-typedoc-check-"));
const generated = join(temporaryRoot, "api");
const files = async (directory) =>
(await readdir(directory, { recursive: true, withFileTypes: true }))
.filter((entry) => entry.isFile())
.map((entry) => relative(directory, join(entry.parentPath, entry.name)))
.sort();
const digest = async (path) =>
createHash("sha256")
.update(await readFile(path))
.digest("hex");
try {
const { stderr } = await execFileAsync(
process.execPath,
[join(root, "node_modules/typedoc/bin/typedoc"), "--out", generated],
{ cwd: root, maxBuffer: 4 * 1024 * 1024 },
);
if (stderr.trim().length > 0) process.stderr.write(stderr);
const committed = join(root, "docs/api");
const [expectedFiles, actualFiles] = await Promise.all([
files(generated),
files(committed),
]);
const expectedSet = new Set(expectedFiles);
const actualSet = new Set(actualFiles);
const missing = expectedFiles.filter((path) => !actualSet.has(path));
const extra = actualFiles.filter((path) => !expectedSet.has(path));
const common = expectedFiles.filter((path) => actualSet.has(path));
const changed = (
await Promise.all(
common.map(async (path) =>
(await digest(join(generated, path))) ===
(await digest(join(committed, path)))
? undefined
: path,
),
)
).filter((path) => path !== undefined);
if (missing.length > 0 || extra.length > 0 || changed.length > 0) {
const summarize = (label, paths) =>
paths.length === 0
? []
: [`${label}:`, ...paths.slice(0, 20).map((path) => ` ${path}`)];
throw new Error(
[
"Committed TypeDoc output is stale. Run `npm run docs:generate`.",
...summarize("Missing", missing),
...summarize("Extra", extra),
...summarize("Changed", changed),
].join("\n"),
);
}
} finally {
await rm(temporaryRoot, { recursive: true, force: true });
}