Files
nxdns/website/scripts/check-links.mjs
T
mokhtar 93e2683e71
Gates / frontend (push) Failing after 8m6s
Gates / package (push) Skipped
Gates / container (push) Skipped
Gates / test (push) Successful in 2m38s
Gates / test-aarch64 (push) Successful in 8m52s
CI / gates (push) Failing after 11m33s
website: nxdns.sh on astro and starlight, with the docs and 0.0.24 screenshots
2026-10-21 17:03:01 +02:00

41 lines
1.6 KiB
JavaScript

// Fails if any page in dist/ links to a site path or an #anchor that does not exist.
// The docs are written for the forge and turned into routes at build time, so a
// renamed page or heading surfaces here instead of as a 404 for a reader.
import { readFileSync, readdirSync, existsSync } from "node:fs";
import { join, relative } from "node:path";
const dist = new URL("../dist/", import.meta.url).pathname;
const pages = readdirSync(dist, { recursive: true })
.filter((f) => f.endsWith(".html"))
.map((f) => join(dist, f));
const ids = new Map();
const idsOf = (file) => {
if (!ids.has(file)) ids.set(file, new Set([...readFileSync(file, "utf8").matchAll(/\sid="([^"]+)"/g)].map((m) => m[1])));
return ids.get(file);
};
const pageOf = (path) => {
const file = path.endsWith("/") ? join(dist, path, "index.html") : join(dist, path);
return existsSync(file) ? file : null;
};
const broken = [];
for (const page of pages) {
const from = "/" + relative(dist, page).replace(/index\.html$/, "");
for (const [, href] of readFileSync(page, "utf8").matchAll(/\shref="([^"]+)"/g)) {
if (/^[a-z][a-z0-9+.-]*:/i.test(href) || href.startsWith("//")) continue;
const [path, hash] = href.split("#");
const target = path === "" ? page : pageOf(path);
if (!target) broken.push(`${from} -> ${href} (no page)`);
else if (hash && !idsOf(target).has(decodeURIComponent(hash))) broken.push(`${from} -> ${href} (no #${hash})`);
}
}
if (broken.length) {
console.error(`${broken.length} broken link(s):\n${[...new Set(broken)].join("\n")}`);
process.exit(1);
}
console.log(`links: ${pages.length} pages checked, no broken links`);