Initial commit
This commit is contained in:
102
tools/check_internal_links.js
Normal file
102
tools/check_internal_links.js
Normal file
@@ -0,0 +1,102 @@
|
||||
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 })));
|
||||
}
|
||||
})();
|
||||
Reference in New Issue
Block a user