/** * Discovers cele2e commands by parsing --help output. * Used by completion coverage tests to verify registry ↔ help sync. */ import { execSync } from 'node:child_process'; import { resolve } from 'node:path'; const CLI = resolve(import.meta.dir, '../..', 'src/cli/index.ts'); function runHelp(args = ''): string { try { return execSync(`bun ${CLI} ${args} --help 2>&1`, { encoding: 'utf-8' }); } catch (e: unknown) { return (e as { stdout?: string }).stdout?.toString() ?? ''; } } export function parseCommandsFromHelp(text: string): string[] { const commands: string[] = []; let inCommandsSection = false; for (const line of text.split('\n')) { if (/^\s*Commands?:\s*$/i.test(line)) { inCommandsSection = true; continue; } // Any UNINDENTED heading ends the list. This used to name three headings // literally (`Options:`, `Examples:`, `Usage:`), none of which the help // actually prints — it writes `Options for \`run\`:` — so the section never // closed and every 2-space-indented word after it was read as a command. // Nothing showed that, because option lines start with `-` and the only // other candidate block happened to sit after `Examples:`. It surfaced the // day a command grew a verb list: `reset` was reported as a top-level // command that the registry was missing. Keying on the help's real // structure fixes the class rather than that instance. if (inCommandsSection && /^\S.*:\s*$/.test(line)) { inCommandsSection = false; continue; } if (!inCommandsSection) continue; // Match " command-name" at 2-3 spaces indent, followed by space/bracket/end const match = line.match(/^ {2,3}([a-z][a-z0-9-]+)(\s|$)/); if (match) commands.push(match[1]); } return commands; } export interface CommandNode { name: string; subcommands: string[]; } /** * Discovers top-level commands from --help output. * cele2e doesn't implement per-command --help pages, so subcommand * discovery is not attempted — use the COMMANDS registry for that. */ export function discoverCommands(): Map { const tree = new Map(); for (const name of parseCommandsFromHelp(runHelp())) { tree.set(name, { name, subcommands: [] }); } return tree; }