Skip to content

Commit 8659917

Browse files
Copilotalexsb
andcommitted
Add custom link checker script with deduplication and error highlighting
Co-authored-by: alexsb <2039375+alexsb@users.noreply.github.com>
1 parent 22eea5e commit 8659917

2 files changed

Lines changed: 72 additions & 1 deletion

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"write-translations": "docusaurus write-translations",
1515
"write-heading-ids": "docusaurus write-heading-ids",
1616
"typecheck": "tsc",
17-
"checkLinks": "linkinator ./build -r --skip revisit.dev --skip localhost:8080 --skip wpi.edu --skip petra.isenberg.cc --skip doi.org --skip acm.org --skip cs.utah.edu"
17+
"checkLinks": "node scripts/checkLinks.mjs"
1818
},
1919
"dependencies": {
2020
"@docusaurus/core": "^3.7.0",

scripts/checkLinks.mjs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { LinkChecker, LinkState } from 'linkinator';
2+
3+
const RESET = '\x1b[0m';
4+
const BOLD = '\x1b[1m';
5+
const RED = '\x1b[31m';
6+
const GREEN = '\x1b[32m';
7+
const YELLOW = '\x1b[33m';
8+
9+
const linksToSkip = [
10+
'revisit.dev',
11+
'localhost:8080',
12+
'wpi.edu',
13+
'petra.isenberg.cc',
14+
'doi.org',
15+
'acm.org',
16+
'cs.utah.edu',
17+
];
18+
19+
const checker = new LinkChecker();
20+
21+
// Deduplicate: track URLs that have already been reported
22+
const seenUrls = new Set();
23+
const okLinks = [];
24+
const skippedLinks = [];
25+
const brokenLinks = [];
26+
27+
checker.on('link', (result) => {
28+
if (seenUrls.has(result.url)) {
29+
return;
30+
}
31+
seenUrls.add(result.url);
32+
33+
if (result.state === LinkState.BROKEN) {
34+
brokenLinks.push(result);
35+
// Print broken links immediately so progress is visible
36+
console.log(`${BOLD}${RED}[${result.status ?? 'ERR'}] ${result.url}${RESET}`);
37+
} else if (result.state === LinkState.SKIPPED) {
38+
skippedLinks.push(result);
39+
console.log(`[SKIP] ${result.url}`);
40+
} else {
41+
okLinks.push(result);
42+
console.log(`${GREEN}[${result.status}] ${result.url}${RESET}`);
43+
}
44+
});
45+
46+
const results = await checker.check({
47+
path: './build',
48+
recurse: true,
49+
linksToSkip,
50+
});
51+
52+
// Print a summary of broken links at the end for easy identification
53+
if (brokenLinks.length > 0) {
54+
console.log(`\n${BOLD}${RED}=== BROKEN LINKS SUMMARY ===${RESET}`);
55+
for (const link of brokenLinks) {
56+
const status = link.status ?? 'ERR';
57+
const parent = link.parent ? ` (found on: ${link.parent})` : '';
58+
console.log(`${BOLD}${RED} [${status}] ${link.url}${YELLOW}${parent}${RESET}`);
59+
}
60+
console.log(`\n${BOLD}${RED}Total broken links: ${brokenLinks.length}${RESET}`);
61+
} else {
62+
console.log(`\n${BOLD}${GREEN}All links are valid!${RESET}`);
63+
}
64+
65+
const totalUnique = okLinks.length + skippedLinks.length + brokenLinks.length;
66+
const totalReported = results.links.length;
67+
console.log(`Checked ${totalUnique} unique links (${totalReported} total, ${totalReported - totalUnique} duplicates skipped).`);
68+
69+
if (!results.passed) {
70+
process.exit(1);
71+
}

0 commit comments

Comments
 (0)