-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcheck-links.js
129 lines (111 loc) · 3.67 KB
/
check-links.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
// check-links.js
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import fetch from 'node-fetch';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
// Read a markdown file and extract all markdown links
const extractLinks = (content) => {
const linkRegex = /\[([^\]]+)\]\(([^)]+)\)/g;
const links = [];
let match;
while ((match = linkRegex.exec(content)) !== null) {
links.push({
text: match[1],
url: match[2],
full: match[0]
});
}
return links;
};
// Check if a local file exists
const checkLocalFile = (linkPath, filePath) => {
if (linkPath.startsWith('/docs/')) {
// Remove hash fragment before checking file existence
const [baseUrl] = linkPath.split('#');
const localPath = path.join(process.cwd(), baseUrl);
try {
fs.accessSync(localPath, fs.constants.F_OK);
console.log(` ✅ ${linkPath}`);
return true;
} catch (err) {
console.log(` ❌ ${linkPath} → File not found`);
return false;
}
}
return null; // not a local file
};
// Check external URL
const checkExternalUrl = async (url) => {
try {
const response = await fetch(url, {
method: 'HEAD',
timeout: 5000
});
if (response.ok) {
console.log(` ✅ ${url}`);
return true;
} else {
console.log(` ❌ ${url} → Status: ${response.status}`);
return false;
}
} catch (error) {
console.log(` ❌ ${url} → Error: ${error.message}`);
return false;
}
};
// Process a single markdown file
const processFile = async (filePath) => {
const relativePath = path.relative(process.cwd(), filePath);
console.log(`\nFILE: ${relativePath}`);
const content = fs.readFileSync(filePath, 'utf8');
const links = extractLinks(content);
if (links.length === 0) {
console.log(' No hyperlinks found!\n');
return true;
}
console.log(` ${links.length} links found:`);
let allValid = true;
for (const link of links) {
if (link.url.startsWith('/docs/')) {
const isValid = checkLocalFile(link.url, filePath);
if (!isValid) allValid = false;
} else if (link.url.startsWith('http')) {
const isValid = await checkExternalUrl(link.url);
if (!isValid) allValid = false;
} else if (link.url.startsWith('#')) {
// Skip same-file anchors
console.log(` ➡️ ${link.url} (same-file anchor)`);
} else {
console.log(` ⚠️ ${link.url} → Skipped (not a local or HTTP link)`);
}
}
console.log('');
return allValid;
};
// Process all markdown files in docs directory
const processDirectory = async (dir) => {
let allValid = true;
const files = fs.readdirSync(dir);
for (const file of files) {
const filePath = path.join(dir, file);
const stat = fs.statSync(filePath);
if (stat.isDirectory()) {
const isValid = await processDirectory(filePath);
if (!isValid) allValid = false;
} else if (file.endsWith('.md')) {
const isValid = await processFile(filePath);
if (!isValid) allValid = false;
}
}
return allValid;
};
// Start processing
console.log('📝 Checking markdown links...\n');
processDirectory('docs').then(allValid => {
if (!allValid) {
console.log('❌ Some links are invalid!');
process.exit(1);
}
console.log('✅ All links are valid!');
});