#!/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.`);