Skip to content

Commit 1d2f134

Browse files
author
cortex-lp
committed
Added custom mdlint to find broken links
1 parent 9170c30 commit 1d2f134

3 files changed

Lines changed: 425 additions & 6 deletions

File tree

mdlint.js

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
#!/usr/bin/env node
2+
3+
/* eslint-disable no-console */
4+
5+
/**
6+
* A simple Markdown linter CLI using markdownlint with VS Code-like defaults.
7+
*
8+
* Usage:
9+
* node mdlint.js --only MD052 content
10+
*/
11+
12+
const fs = require("fs");
13+
const path = require("path");
14+
let markdownlint;
15+
try {
16+
markdownlint = require("markdownlint"); // ensure 0.26.1 installed
17+
} catch {
18+
console.error("Please run: npm i -D markdownlint@0.26.1");
19+
process.exit(2);
20+
}
21+
22+
// Parse simple flags: --only MD052,MD013 ...
23+
function parseArgs(argv) {
24+
const args = [];
25+
const flags = {};
26+
for (let i = 0; i < argv.length; i++) {
27+
const a = argv[i];
28+
if (a === "--only") {
29+
flags.only = (argv[++i] || "").split(",").map(s => s.trim()).filter(Boolean);
30+
} else if (a.startsWith("--only=")) {
31+
flags.only = a.slice("--only=".length).split(",").map(s => s.trim()).filter(Boolean);
32+
} else {
33+
args.push(a);
34+
}
35+
}
36+
return { paths: args, flags };
37+
}
38+
39+
// Recursively collect .md files
40+
function collect(target) {
41+
const abs = path.resolve(target);
42+
if (!fs.existsSync(abs)) throw new Error(`Path does not exist: ${target}`);
43+
const st = fs.statSync(abs);
44+
if (st.isDirectory()) {
45+
const out = [];
46+
for (const de of fs.readdirSync(abs, { withFileTypes: true })) {
47+
const p = path.join(abs, de.name);
48+
if (de.isDirectory()) out.push(...collect(p));
49+
else if (de.isFile() && p.toLowerCase().endsWith(".md")) out.push(p);
50+
}
51+
return out;
52+
}
53+
return st.isFile() && abs.toLowerCase().endsWith(".md") ? [abs] : [];
54+
}
55+
56+
function buildConfig(only) {
57+
if (!only || only.length === 0) {
58+
return { default: true }; // VS Code-like defaults
59+
}
60+
// Normalize to rule code form (e.g., md052 -> MD052)
61+
const enabled = new Set(only.map(s => s.toUpperCase()));
62+
const cfg = { default: false };
63+
for (const code of enabled) cfg[code] = true;
64+
65+
// Optional: also turn on known aliases for MD052 to be extra robust
66+
if (enabled.has("MD052")) cfg["reference-links-images"] = true;
67+
68+
return cfg;
69+
}
70+
71+
function lint(files, config) {
72+
if (files.length === 0) {
73+
console.log("No Markdown files to lint.");
74+
return 0;
75+
}
76+
const result = markdownlint.sync({
77+
files,
78+
config,
79+
resultVersion: 3
80+
});
81+
82+
let issues = 0;
83+
for (const file of Object.keys(result)) {
84+
const problems = result[file];
85+
if (!problems.length) continue;
86+
console.log(`\n${file}`);
87+
for (const p of problems) {
88+
issues++;
89+
const loc = `${p.lineNumber}${p.errorRange ? ":" + p.errorRange[0] : ""}`;
90+
const msg =
91+
`${p.ruleNames.join("/")}: ${p.ruleDescription}` +
92+
(p.errorDetail ? ` [${p.errorDetail}]` : "") +
93+
(p.errorContext ? ` [Context: ${p.errorContext}]` : "");
94+
console.log(` ${loc} ${msg}`);
95+
}
96+
}
97+
if (issues === 0) console.log(`✔ No issues found in ${files.length} file(s).`);
98+
else console.log(`\n✖ ${issues} issue(s) total in ${files.length} file(s).`);
99+
return issues === 0 ? 0 : 1;
100+
}
101+
102+
(function main() {
103+
const { paths, flags } = parseArgs(process.argv.slice(2));
104+
if (paths.length === 0) {
105+
console.error("Usage: mdlint [--only MD052[,MD013...]] <file-or-folder> [...]");
106+
process.exit(1);
107+
}
108+
let files = [];
109+
for (const p of paths) {
110+
try { files = files.concat(collect(p)); }
111+
catch (e) { console.error(e.message); process.exit(1); }
112+
}
113+
const config = buildConfig(flags.only);
114+
const code = lint(files, config);
115+
process.exit(code);
116+
})();

0 commit comments

Comments
 (0)