1'use strict'; 2 3const fs = require('fs'); 4const { extname, join, resolve } = require('path'); 5const unified = require('unified'); 6const { pathToFileURL } = require('url'); 7const DIR = resolve(process.argv[2]); 8 9console.log('Running Markdown link checker...'); 10findMarkdownFilesRecursively(DIR); 11 12function* getLinksRecursively(node) { 13 if (node.url && !node.url.startsWith('#')) { 14 yield node; 15 } 16 for (const child of node.children || []) { 17 yield* getLinksRecursively(child); 18 } 19} 20 21function findMarkdownFilesRecursively(dirPath) { 22 const entries = fs.readdirSync(dirPath, { withFileTypes: true }); 23 24 for (const entry of entries) { 25 const path = join(dirPath, entry.name); 26 27 if ( 28 entry.isDirectory() && 29 entry.name !== 'api' && 30 entry.name !== 'build' && 31 entry.name !== 'changelogs' && 32 entry.name !== 'deps' && 33 entry.name !== 'fixtures' && 34 entry.name !== 'gyp' && 35 entry.name !== 'node_modules' && 36 entry.name !== 'out' && 37 entry.name !== 'tmp' 38 ) { 39 findMarkdownFilesRecursively(path); 40 } else if (entry.isFile() && extname(entry.name) === '.md') { 41 checkFile(path); 42 } 43 } 44} 45 46function checkFile(path) { 47 const tree = unified() 48 .use(require('remark-parse')) 49 .parse(fs.readFileSync(path)); 50 51 const base = pathToFileURL(path); 52 let previousDefinitionLabel; 53 for (const node of getLinksRecursively(tree)) { 54 const targetURL = new URL(node.url, base); 55 if (targetURL.protocol === 'file:' && !fs.existsSync(targetURL)) { 56 const { line, column } = node.position.start; 57 console.error((process.env.GITHUB_ACTIONS ? 58 `::error file=${path},line=${line},col=${column}::` : '') + 59 `Broken link at ${path}:${line}:${column} (${node.url})`); 60 process.exitCode = 1; 61 } 62 if (node.type === 'definition') { 63 if (previousDefinitionLabel && 64 previousDefinitionLabel > node.label) { 65 const { line, column } = node.position.start; 66 console.error((process.env.GITHUB_ACTIONS ? 67 `::error file=${path},line=${line},col=${column}::` : '') + 68 `Unordered reference at ${path}:${line}:${column} (` + 69 `"${node.label}" should be before "${previousDefinitionLabel})"` 70 ); 71 process.exitCode = 1; 72 } 73 previousDefinitionLabel = node.label; 74 } 75 } 76} 77