24 lines
1.4 KiB
JavaScript
24 lines
1.4 KiB
JavaScript
#!/usr/bin/env node
|
|
import { readFile, readdir, stat } from "node:fs/promises";
|
|
import { dirname, resolve, relative } from "node:path";
|
|
import process from "node:process";
|
|
|
|
const root = resolve(import.meta.dirname, "..");
|
|
const docsRoot = resolve(root, "docs");
|
|
const walk = async (directory) => (await Promise.all((await readdir(directory, { withFileTypes: true })).map((entry) => entry.isDirectory() ? walk(resolve(directory, entry.name)) : [resolve(directory, entry.name)]))).flat();
|
|
const files = (await walk(docsRoot)).filter((path) => path.endsWith(".md") && !path.includes(`${resolve(docsRoot, "archive")}\\`) && !path.includes(`${resolve(docsRoot, "archive")}/`));
|
|
const failures = [];
|
|
for (const file of files) {
|
|
const source = await readFile(file, "utf8");
|
|
const links = [...source.matchAll(/\[[^\]]*\]\(([^)]+)\)/g)].map((match) => match[1].trim().replace(/^<|>$/g, ""));
|
|
for (const link of links) {
|
|
if (!link || /^(https?:|mailto:|#)/i.test(link)) continue;
|
|
const targetPart = decodeURIComponent(link.split("#")[0]);
|
|
if (!targetPart) continue;
|
|
const target = resolve(dirname(file), targetPart);
|
|
try { await stat(target); } catch { failures.push(`${relative(root, file)} -> ${link}`); }
|
|
}
|
|
}
|
|
if (failures.length) { console.error(`Broken active documentation links:\n- ${failures.join("\n- ")}`); process.exit(1); }
|
|
console.log(`Documentation link audit passed for ${files.length} active Markdown files.`);
|