#!/usr/bin/env node import { readFileSync } from "node:fs"; const lines = readFileSync("src/index.ts", "utf8").split(/\r?\n/); const docs = readFileSync("COMMANDS.md", "utf8"); function hasDocRef(commandText) { return ( docs.includes(`\`${commandText}\``) || docs.includes(` ${commandText} \``) || docs.includes(`\`${commandText} `) ); } const varToCommand = new Map(); const commandSet = new Set(); let pendingParentVar = null; let currentChainBase = null; for (const line of lines) { const varAssign = line.match(/^\s*const\s+(\w+)\s*=\s*program\s*$/); if (varAssign) { pendingParentVar = varAssign[1]; continue; } const lineRef = line.match(/^\s*(\w+)\s*$/); if (lineRef) { const mapped = varToCommand.get(lineRef[1]); if (mapped) currentChainBase = mapped; continue; } const dotCommand = line.match(/^\s*\.command\(\s*(?:"([^"]+)"|'([^']+)')/); if (dotCommand) { const raw = (dotCommand[1] ?? dotCommand[2]).trim(); if (pendingParentVar) { varToCommand.set(pendingParentVar, raw); commandSet.add(raw); currentChainBase = raw; pendingParentVar = null; } else if (currentChainBase) { commandSet.add(`${currentChainBase} ${raw}`); } else { commandSet.add(raw); } continue; } const childCommand = line.match(/^\s*(\w+)\.command\(\s*(?:"([^"]+)"|'([^']+)')/); if (childCommand) { const varName = childCommand[1]; const raw = (childCommand[2] ?? childCommand[3]).trim(); const base = varToCommand.get(varName); if (base) commandSet.add(`${base} ${raw}`); else if (currentChainBase) commandSet.add(`${currentChainBase} ${raw}`); else commandSet.add(raw); currentChainBase = base ?? currentChainBase ?? raw; pendingParentVar = null; continue; } // keep context across chained calls if (/^\s*\./.test(line)) { continue; } pendingParentVar = null; currentChainBase = null; } const fullText = lines.join("\n"); const optionMatches = [...fullText.matchAll(/\.option\(\s*(?:"([^"]+)"|'([^']+)')/g)]; const options = [...new Set( optionMatches .map((m) => m[1] ?? m[2]) .filter((opt) => opt.includes("--") && !opt.includes("<")) .map((opt) => opt.match(/--[^\s,>]+/)?.[0]) .filter(Boolean) )]; const commandList = [...commandSet]; const missingCommands = commandList.filter((cmd) => !hasDocRef(cmd)); const missingOptions = options.filter((opt) => !docs.includes(opt)); if (missingCommands.length || missingOptions.length) { console.error("DOCS PARITY CHECK FAILED"); if (missingCommands.length) { console.error("Missing command entries:"); for (const cmd of missingCommands) console.error(` - ${cmd}`); } if (missingOptions.length) { console.error("Missing long options:"); for (const opt of missingOptions) console.error(` - ${opt}`); } process.exit(1); } console.log(`DOCS PARITY CHECK OK: ${commandSet.size} commands, ${options.length} options`);