基于Commander.js的CLI工具子命令在Windows Git Bash中失效
我正在开发一款名为solo的CLI工具,用于自动化工作中的常规流程,技术栈选用TypeScript、Node.js搭配Commander.js。该工具最终将支持跨平台,但目前处于早期原型阶段,主要适配Windows环境,我在Windows上仅通过Git Bash使用它。
我希望将部分命令按逻辑分组,实现如下调用方式:
solo monorepo <subcommand> [options] solo git <subcommand> [options] solo aws <subcommand> [options] # etc
目前我在实现单个子命令的最小示例时遇到了问题。
我尝试定义demo命令及其inner子命令,期望通过solo demo inner调用,通过solo help demo inner查看帮助,但该功能无法正常工作。无论执行何种相关命令,要么触发demo命令的帮助信息,要么仅执行demo命令本身。
代码
index.ts
import chalk from 'chalk'; import { Command } from 'commander'; import { allCommands } from './commands'; import { CliCommandMetadata, createCommandExample, describeCliOption } from './commands/cli-option'; export function createCli(): Command { const program = new Command(); program .name(`solo`) .description(`CLI tool for automating routine processes.`) .option(`--verbose`, `Provide verbose output.`) .version(`0.0.1`); const cmd = program .command('demo') .description('Demo command') .addHelpText('after', `DEMO CMD`); // .action(() => { // console.log('demo cmd'); // }); const subCmd = cmd .command('inner') .description('Inner command') .addHelpText('after', `INNER CMD`) .action(() => { console.log('inner cmd'); }); return program; } //function registerCommand(programCommand: Command, commandMetadata: CliCommandMetadata) { // let command = programCommand // .command(commandMetadata.name) // .description(commandMetadata.description); // // command = Object // .values(commandMetadata.options) // .reduce( // (command, option) => command.option(...describeCliOption(option)), // command, // ); // // command // .addHelpText('after', formatExample(createCommandExample(commandMetadata))) // .action((...args: [str: any, options: any]) => { // commandMetadata.impl.apply(null, args); // }); // // function formatExample(...lines: string[]): string { // return ` //Example: //${lines.join('\n')} //`; // } //}
package.json
{ "name": "solo", "version": "0.0.1", "description": "CLI tool for automating routine processes.", "main": "index.js", "bin": { "solo": "./dist/index.js" }, "scripts": { "start": "ts-node index.ts", "start-alt": "node -r ts-node/register index.ts", "tsc": "tsc", "build": "npx tsc" }, "dependencies": { "axios": "^1.7.5", "chalk": "^4.1.2", "cli-table3": "^0.6.5", "commander": "^12.1.0", "semver": "^7.6.3", "ts-node": "^10.9.2", "typescript": "^5.5.4" }, "devDependencies": { "@types/node": "^22.5.0", "@types/semver": "^7.5.8" } }
失效场景演示
以下是我尝试调用子命令或查看帮助时的输出:
无论执行何种操作,要么显示demo命令的帮助信息,要么仅触发demo命令本身。
$ solo demo inner > solo@0.0.1 start > ts-node index.ts demo Usage: solo demo [options] [command] Demo command Options: -h, --help display help for command Commands: inner Inner command help [command] display help for command DEMO CMD
$ solo demo help inner > solo@0.0.1 start > ts-node index.ts demo Usage: solo demo [options] [command] Demo command Options: -h, --help display help for command Commands: inner Inner command help [command] display help for command DEMO CMD
$ solo help > solo@0.0.1 start > ts-node index.ts help Usage: solo [options] [command] CLI tool for automating routine processes. Options: --verbose Provide verbose output. -V, --version output the version number -h, --help display help for command Commands: solo-check-health Self-check the health of the CLI tool. Validates the presence and the correctness of the config. demo Demo command help [command] display help for command
$ solo help demo > solo@0.0.1 start > ts-node index.ts help Usage: solo [options] [command] CLI tool for automating routine processes. Options: --verbose Provide verbose output. -V, --version output the version number -h, --help display help for command Commands: solo-check-health Self-check the health of the CLI tool. Validates the presence and the correctness of the config. demo Demo command help [command] display help for command
$ solo demo help > solo@0.0.1 start > ts-node index.ts demo Usage: solo demo [options] [command] Demo command Options: -h, --help display help for command Commands: inner Inner command help [command] display help for command DEMO CMD
$ solo demo inner help > solo@0.0.1 start > ts-node index.ts demo Usage: solo demo [options] [command] Demo command Options: -h, --help display help for command Commands: inner Inner command help [command] display help for command DEMO CMD
问题出在两个核心点:npm脚本未完整传递参数,以及CLI缺少参数解析逻辑。
1. 修复npm脚本的参数传递
当前start脚本仅执行ts-node index.ts,后续的命令参数(如demo inner)未被传递给ts-node。修改package.json中的脚本,让它接收并传递所有参数:
"scripts": { "start": "ts-node index.ts \"$@\"", "start-alt": "node -r ts-node/register index.ts \"$@\"", // 其他脚本保持不变 }
修改后,执行solo demo inner时所有参数都会被正确传递给ts-node。
2. 添加CLI参数解析逻辑
createCli函数创建了Command实例,但未调用parse方法解析命令行参数。在index.ts文件末尾添加执行逻辑:
// 在index.ts文件末尾添加 const cli = createCli(); cli.parse(process.argv);
Commander.js必须调用parse方法才能处理传入的命令行参数,否则无法识别子命令。
3. 验证修复效果
修改后执行以下命令测试:
npm start demo inner:应输出inner cmdnpm start help demo inner:应显示inner子命令的帮助信息- 若要直接使用
solo命令,先执行npm link将工具链接到全局,之后即可直接用solo demo inner调用。
内容的提问来源于stack exchange,提问作者Igor Soloydenko

