#!/usr/bin/env node import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs'; import { extname, join } from 'node:path'; import { pathToFileURL } from 'node:url'; import { option, parseArgs } from './ops-utils.mjs'; const forbiddenPatterns = [ { name: 'TBD', pattern: /\bTBD\b/i }, { name: 'TODO', pattern: /\bTODO\b/i }, { name: 'fill-in', pattern: /fill in|fill-in/i }, { name: 'angle-token', pattern: /<[^>\n]+>/ }, { name: 'Chinese pending marker', pattern: /待定|占位/ }, ]; function usage() { return `Usage: node scripts/check-runbook-placeholders.mjs --paths [path...] Scans Markdown runbooks for unresolved placeholder markers. `; } function collectMarkdownFiles(path) { if (!existsSync(path)) { throw new Error(`Path not found: ${path}`); } const stat = statSync(path); if (stat.isFile()) return extname(path) === '.md' ? [path] : []; if (!stat.isDirectory()) return []; const files = []; for (const entry of readdirSync(path)) { files.push(...collectMarkdownFiles(join(path, entry))); } return files; } function requestedPaths(argv) { const args = parseArgs(argv); if (args.flags.has('help')) return { help: true, paths: [] }; const first = option(args, 'paths'); const paths = [first, ...args.positionals].filter(Boolean); if (paths.length === 0) { throw new Error('Missing --paths [path...]'); } return { help: false, paths }; } export function scanFiles(paths) { const files = paths.flatMap(collectMarkdownFiles); const findings = []; for (const file of files) { const lines = readFileSync(file, 'utf8').split(/\r?\n/); lines.forEach((line, index) => { for (const forbidden of forbiddenPatterns) { if (forbidden.pattern.test(line)) { findings.push(`${file}:${index + 1} ${forbidden.name}: ${line.trim()}`); } } }); } return { files, findings }; } export async function main(argv = process.argv.slice(2)) { const { help, paths } = requestedPaths(argv); if (help) { process.stdout.write(usage()); return; } const { files, findings } = scanFiles(paths); if (findings.length > 0) { process.stderr.write(`${findings.join('\n')}\n`); throw new Error(`Runbook placeholder scan failed: ${findings.length} finding(s)`); } process.stdout.write(`Runbook placeholder scan passed (${files.length} file(s)).\n`); } if (import.meta.url === pathToFileURL(process.argv[1]).href) { main().catch((error) => { process.stderr.write(`${error.message}\n`); process.exit(1); }); }