/** * Verify repository-local Markdown links without making network requests. * * External URLs are intentionally skipped: CI should not fail because an * upstream website is temporarily unavailable. Local documentation links are * deterministic and catch renamed component guides or incorrect relative paths. */ import { existsSync, readdirSync, readFileSync } from 'node:fs' import { dirname, extname, resolve } from 'node:path' const root = resolve(process.argv[2] ?? new URL('../../..', import.meta.url).pathname) const ignoredDirectories = new Set([ '.agents', '.codex', '.git', 'dist', 'node_modules', 'target', 'vendor', ]) const markdownFiles = [] function collect(directory) { for (const entry of readdirSync(directory, { withFileTypes: true })) { if (entry.isDirectory() && ignoredDirectories.has(entry.name)) continue const path = resolve(directory, entry.name) if (entry.isDirectory()) collect(path) else if (extname(entry.name).toLowerCase() === '.md') markdownFiles.push(path) } } collect(root) const failures = [] const markdownLink = /\[[^\]]*\]\(([^)]+)\)/g for (const file of markdownFiles) { const source = readFileSync(file, 'utf8') for (const match of source.matchAll(markdownLink)) { const rawTarget = match[1].trim().replace(/^<|>$/g, '') if (!rawTarget || rawTarget.startsWith('#') || /^[a-z][a-z+.-]*:/i.test(rawTarget)) continue // The project does not currently use titled local links. Splitting here // still handles the conventional `(path "title")` form if one is added. const target = decodeURI(rawTarget.split(/\s+["']/)[0].split('#')[0]) if (!target) continue const destination = resolve(dirname(file), target) if (!existsSync(destination)) failures.push(`${file}: ${rawTarget}`) } } if (failures.length > 0) { console.error(`Broken local documentation links:\n${failures.join('\n')}`) process.exitCode = 1 } else { console.log(`Checked ${markdownFiles.length} Markdown files; local links are valid.`) }