Commander.js:Node.js 命令行工具开发
什么是 Commander.js?
Commander.js 是 Node.js 生态中最流行的命令行界面(CLI)开发框架。它以声明式的方式让你快速定义命令、选项(options)、参数(arguments),并自动生成美观的帮助信息。无论是构建简单的脚本工具,还是带有子命令的复杂应用,Commander.js 都能让你的代码结构清晰、易于维护。
核心优势:
- 零依赖,轻量高效
- 链式 API,可读性强
- 自动生成
--help文档 - 支持子命令(git 风格)
- 完善的 TypeScript 类型支持
快速开始
安装 Commander.js
确保你已经安装了 Node.js(建议 v14 以上)。在项目根目录执行:
npm init -y # 初始化 package.json
npm install commander # 安装 commander
建议在 package.json 中添加 "type": "module" 以便使用 ES 模块语法,本教程将统一采用 ESM 写法。
第一个命令行工具
创建一个文件 index.mjs,输入以下代码:
#!/usr/bin/env node
import { Command } from 'commander';
const program = new Command();
program
.name('mycli')
.description('我的第一个 CLI 工具')
.version('1.0.0');
program.parse(process.argv);
赋予文件执行权限(Linux/macOS):
chmod +x index.mjs
现在运行 ./index.mjs --help,你将看到自动生成的帮助信息。
定义命令与选项
创建独立命令
使用 .command() 定义子命令,.action() 绑定回调函数:
program
.command('greet <name>')
.description('向指定用户打招呼')
.action((name) => {
console.log(`你好,${name}!`);
});
<name> 表示必填参数,如果希望参数可选,使用 [name]。在 action 回调中可直接获取传入的参数值。
添加选项 (option)
选项用于控制命令的行为,支持多种类型:
program
.option('-d, --debug', '启用调试模式')
.option('-p, --port <number>', '指定端口号', '3000') // 默认值
.option('-c, --config <path>', '配置文件路径')
.action((options) => {
console.log(options.debug); // 布尔值
console.log(options.port); // 字符串(默认 '3000')
console.log(options.config); // 用户传递的值或 undefined
});
Commander 会自动解析选项,并将结果注入到 action 回调函数的最后一个参数中(通常命名为 options)。布尔型选项只需指定标志,带值选项需要使用 < > 标记。
设置必选选项与默认值
让选项强制必填:
.requiredOption('-t, --token <string>', 'API 令牌(必填)')
提供默认值并限制可选值:
.option('-e, --env <environment>', '运行环境', 'development')
.addOption(new Option('-e, --env <environment>').choices(['dev', 'test', 'prod']))
使用 new Option 创建选项对象可以更精细地控制行为。
变长参数与处理函数
通过 ... 语法接收多个参数:
program
.command('build <source...>')
.description('构建指定文件')
.action((sources) => {
console.log('构建文件列表:', sources); // 数组
});
你还可以在 action 中获得选项和命令本身:
action((sources, options, command) => { ... })
处理参数与交互
解析命令行参数
调用 program.parse(process.argv) 是启动解析的标准方式。你也可以手动传递参数数组:
program.parse(['node', 'mycli.js', 'build', 'src/index.js'], { from: 'user' });
parse 方法会自动匹配命令、验证必填选项并执行对应的 action。
交互式提示
虽然 Commander.js 本身不提供交互式输入功能,但它可以很好地与 Inquirer.js 配合。你可以在 action 中发起提问:
import inquirer from 'inquirer';
program
.command('init')
.action(async () => {
const answers = await inquirer.prompt([
{ type: 'input', name: 'projectName', message: '项目名称:' }
]);
console.log(`正在创建项目:${answers.projectName}`);
});
这样便能构建出既有命令行参数,又有优雅交互的工具。
高级特性
子命令
子命令允许你构建像 git commit、npm install 这样的多层命令结构。可以将子命令定义在独立的文件中:
program
.command('service <action>')
.description('服务管理')
.command('start')
.argument('<serviceName>')
.action((serviceName) => { /* ... */ })
.command('stop')
.argument('<serviceName>')
.action((serviceName) => { /* ... */ });
为大型项目推荐使用 .command() 的第二个参数传入 Command 实例,实现命令分离。
自动化帮助信息
只要定义了命令和选项,Commander 就会自动处理 --help。你可以自定义帮助页脚:
program.addHelpText('after', `
示例:
$ mycli greet World
$ mycli build ./src
`);
before 或 after 参数控制文本添加位置。
版本和自定义帮助
.version('1.0.0', '-v, --version', '输出当前版本号')
如果你的 CLI 需要显示自定义信息,可以覆写 .helpInformation() 方法。
生命周期钩子
在命令执行前后插入逻辑:
program
.command('deploy')
.hook('preAction', (thisCommand, actionCommand) => {
console.log('部署前检查...');
})
.hook('postAction', () => {
console.log('部署完成');
});
适用于全局配置校验、日志记录等场景。
项目实战:构建一个文件生成器 CLI
需求分析
我们将创建一个名为 maker 的 CLI 工具,它包含两个子命令:
maker component <name>:生成一个 Vue 组件模板文件maker page <name> --route:生成页面文件,并可选是否添加路由配置
实现代码
新建 maker.mjs:
#!/usr/bin/env node
import { Command } from 'commander';
import fs from 'node:fs';
import path from 'node:path';
const program = new Command();
program.name('maker').description('前端代码生成器').version('0.1.0');
const componentTemplate = (name) =>
`<template>
<div class="${name.toLowerCase()}-container">
<h2>${name}</h2>
</div>
</template>
<script setup>
// ${name} component logic
</script>
<style scoped>
.${name.toLowerCase()}-container {
padding: 1rem;
}
</style>`;
const pageTemplate = (name, hasRoute) =>
`<template>
<div>Page: ${name}</div>
</template>
<script setup>
definePageMeta({
${hasRoute ? `path: '/${name.toLowerCase()}'` : '// no route'}
})
</script>`;
program
.command('component <name>')
.description('创建 Vue 组件')
.action((name) => {
const content = componentTemplate(name);
const dir = path.join(process.cwd(), 'components');
fs.mkdirSync(dir, { recursive: true });
fs.writeFileSync(path.join(dir, `${name}.vue`), content);
console.log(`组件 ${name}.vue 已生成在 components/ 目录`);
});
program
.command('page <name>')
.option('-r, --route', '添加路由配置')
.description('创建页面')
.action((name, options) => {
const content = pageTemplate(name, options.route);
const dir = path.join(process.cwd(), 'pages');
fs.mkdirSync(dir, { recursive: true });
fs.writeFileSync(path.join(dir, `${name}.vue`), content);
console.log(`页面 ${name}.vue 已生成${options.route ? '并包含路由' : ''}`);
});
program.parse(process.argv);
测试与使用
运行:
node maker.mjs component HelloWorld
node maker.mjs page About --route
检查项目目录下的 components/ 和 pages/ 文件夹,对应的模板文件已经创建。
总结与最佳实践
- 命名规范:使用
.name()显式设置工具名,便于帮助信息展示。 - 错误处理:在 action 中使用
try...catch并调用process.exit(1)优雅退出。 - 命令拆分:当项目变大时,将子命令抽离为独立模块,通过
.addCommand()加载。 - TypeScript:Commander 提供完整的类型定义,推荐结合 TS 开发以获得更好的智能提示。
- 发布到 npm:在
package.json中配置"bin"字段,即可通过npm link全局使用你的 CLI。
Commander.js 让 Node.js 命令行开发变得前所未有的简单。从一个小脚本到大型工具链,它都能提供严谨且富有表现力的 API,帮助你专注于业务逻辑本身。立即动手,打造你自己的命令行工具箱吧!