// CLI dispatcher — recursively walks CLICommand tree and wires up yargs import yargs from "yargs"; import { hideBin } from "yargs/helpers"; export class CLI { prog; description; constructor(opts) { this.prog = opts.prog; this.description = opts.description; } registerCommand(y, CommandClass) { const cmd = new CommandClass(); const subcommands = CommandClass._subcommands; if (subcommands?.length) { // Branch command with subcommands y.command(cmd.name, cmd.help, (sub) => { for (const Sub of subcommands) { this.registerCommand(sub, Sub); } return sub.demandCommand(1, `Please specify a ${cmd.name} subcommand`); }, () => { }); } else { // Leaf command y.command(cmd.name, cmd.help, (sub) => cmd.addArguments(sub), async (args) => { const code = await cmd.execute(args); process.exit(code); }); } } bootstrap(commands) { const y = yargs(hideBin(process.argv)) .scriptName(this.prog) .usage(`${this.description}\n\n$0 [options]`) .option("verbose", { alias: "v", type: "boolean", description: "Enable verbose output", default: false, }) .demandCommand(1, "Please specify a command") .strict() .help(); for (const Cmd of commands) { this.registerCommand(y, Cmd); } y.parse(); } }