// Facts Commander owns after parsing: the active path and which tokens became option values. import type { Command, Option } from "commander"; const activeErrorCommandByRoot = new WeakMap(); const lazyCommands = new WeakSet(); export function markCommanderLazyCommand(command: Command): void { lazyCommands.add(command); } function getCommandHierarchy(command: Command): Command[] { const hierarchy: Command[] = []; for (let current: Command | null = command; current; current = current.parent ?? null) { hierarchy.unshift(current); } return hierarchy; } function requiresFollowingValue(token: string, options: readonly Option[]): boolean { if (token.includes("=")) { return false; } const exact = options.find((option) => option.short === token || option.long === token); if (exact) { return exact.required; } if (!token.startsWith("-") || token.startsWith("--")) { return false; } const shortGroup = token.slice(1); for (let index = 0; index < shortGroup.length; index += 1) { const option = options.find((candidate) => candidate.short === `-${shortGroup[index]}`); if (!option) { return false; } if (option.required || option.optional) { return option.required && index === shortGroup.length - 1; } } return false; } /** Match an argv token in its actual Commander role rather than its spelling alone. */ export function hasCommanderOptionToken( command: Command, argv: readonly string[], tokens: ReadonlySet, kind: "flag" | "value", ): boolean { const hierarchy = getCommandHierarchy(command); const args = argv.slice(2); let commandIndex = 0; for (let index = 0; index < args.length; index += 1) { const arg = args[index]; if (arg === undefined || arg === "--") { break; } const nextCommand = hierarchy[commandIndex + 1]; if (nextCommand && (arg === nextCommand.name() || nextCommand.aliases().includes(arg))) { commandIndex += 1; continue; } // The root program enables positional options, so after dispatch Commander // parses only the active command's options; ancestor flags there never reach pre-action. if (requiresFollowingValue(arg, hierarchy[commandIndex]?.options ?? [])) { const value = args[index + 1]; if (kind === "value" && value && tokens.has(value)) { return true; } index += 1; continue; } if (kind === "flag" && tokens.has(arg.split("=")[0] ?? arg)) { return true; } } return false; } /** Return the registered command path for the exact Commander node handling an error or action. */ export function getCommanderCommandPath(command: Command): string[] { const commandPath: string[] = []; for (let current: Command | null = command; current?.parent; current = current.parent) { commandPath.unshift(current.name()); } return commandPath; } function getRootCommand(command: Command): Command { let root = command; while (root.parent) { root = root.parent; } return root; } /** Resolve lazy help before classifying a possible child on Commander's active command node. */ export function getCommanderSubcommandFact( command: Command, args: readonly string[], ): { kind: "defer" } | { kind: "unknown"; name: string } | undefined { const helpRequested = args.includes("-h") || args.includes("--help"); if (helpRequested && lazyCommands.has(command)) { return { kind: "defer" }; } const firstArgument = command.args[0]; const matchesChild = command.commands.some( (child) => child.name() === firstArgument || child.aliases().includes(firstArgument ?? ""), ); if ( command.registeredArguments.length > 0 || firstArgument === undefined || firstArgument.startsWith("-") || matchesChild ) { return undefined; } return command.commands.length > 0 ? { kind: "unknown", name: firstArgument } : undefined; } /** Scope the exact Commander node synchronously emitting an error. */ export function setCommanderErrorCommand(command: Command): () => void { const root = getRootCommand(command); const previous = activeErrorCommandByRoot.get(root); activeErrorCommandByRoot.set(root, command); return () => { if (previous) { activeErrorCommandByRoot.set(root, previous); } else { activeErrorCommandByRoot.delete(root); } }; } /** Return the active parse-error path without replacing Commander's configured output handler. */ export function getCommanderErrorCommandPath(program: Command): string[] | undefined { const command = activeErrorCommandByRoot.get(getRootCommand(program)); return command ? getCommanderCommandPath(command) : undefined; } /** Return visible children of the non-root command synchronously emitting a parse error. */ export function getCommanderErrorCommandNames(program: Command): string[] | undefined { const command = activeErrorCommandByRoot.get(getRootCommand(program)); return command && getCommanderCommandPath(command).length ? command .createHelp() .visibleCommands(command) .flatMap((child) => child.aliases().concat(child.name())) : undefined; }