const fs = require("fs"); const path = require("path"); const http = require("http"); const readline = require("readline"); const BASE_URL = "http://127.0.0.1:1313"; const CONTENT_DIR = path.join(__dirname, "..", "content"); const SITE_ROOT = path.resolve(__dirname, ".."); const BAD_LINKS = []; function isInternalLink(link) { return !link.includes("://") && !link.startsWith("mailto:") && !link.startsWith("tel:"); } function extractLinksFromLine(line) { const regex = /\]\(([^)]+)\)/g; let match; const links = []; while ((match = regex.exec(line)) !== null) { links.push(match[1]); } return links; } function getBundleRelativeUrl(mdPath, link) { const bundleRoot = path.dirname(mdPath); let urlPath; if (link.startsWith("/")) { urlPath = link; } else { const fullPath = path.resolve(bundleRoot, link); const relative = path.relative(CONTENT_DIR, fullPath); urlPath = "/" + relative.replace(/\\/g, "/"); } return urlPath; } async function checkLink(file, lineNumber, link) { const relativeUrl = getBundleRelativeUrl(file, link); const fullUrl = `${BASE_URL}${relativeUrl}`; return new Promise((resolve) => { http.get(fullUrl, (res) => { if (res.statusCode !== 200) { BAD_LINKS.push([path.relative(SITE_ROOT, file), link, lineNumber]); } res.resume(); resolve(); }).on("error", () => { BAD_LINKS.push([path.relative(SITE_ROOT, file), link, lineNumber]); resolve(); }); }); } async function processFile(filePath) { const fileStream = fs.createReadStream(filePath); const rl = readline.createInterface({ input: fileStream, crlfDelay: Infinity }); let lineNumber = 0; for await (const line of rl) { lineNumber++; const links = extractLinksFromLine(line); for (const link of links) { if (isInternalLink(link)) { process.stdout.write("."); await checkLink(filePath, lineNumber, link); } } } } function walk(dir) { let results = []; const list = fs.readdirSync(dir); list.forEach((file) => { file = path.resolve(dir, file); const stat = fs.statSync(file); if (stat && stat.isDirectory()) { results = results.concat(walk(file)); } else if (file.endsWith(".md")) { results.push(file); } }); return results; } (async () => { const files = walk(CONTENT_DIR); console.log(`Analyzing ${files.length} Markdown files...`); for (const file of files) { await processFile(file); } console.log("\n\n=== Broken Internal Links Report ==="); if (BAD_LINKS.length === 0) { console.log("✅ No broken internal links found."); } else { console.table(BAD_LINKS.map(([f, u, l]) => ({ File: f, URL: u, Line: l }))); } })();