写一个命令行工具是很多前端开发者接触Node.js后想做的第一件事,因为它足够实用:文件批量重命名、项目模板生成、日志格式化,这些重复劳动都可以浓缩成终端里的一行命令。这篇文章带你从零实现一个完整的CLI工具,读完之后你不仅能跑起来自己的第一个版本,还能理解参数解析、交互问答、终端配色这些核心机制,最终把它发布到npm上供别人安装使用。

一、从一个最小可运行的CLI开始
先抛开各种框架,用最原始的方式理解CLI的本质。所谓命令行工具,就是一个可执行的脚本文件,操作系统在终端里运行它,并把用户输入的参数传给它。在Node.js环境下,用户输入的内容全部存放在process.argv这个数组里:第0项是Node的可执行文件路径,第1项是当前脚本的路径,从第2项开始才是用户真正传入的参数。
新建一个目录,创建index.js文件,写入如下内容:
#!/usr/bin/env node
// 打印出所有参数,观察 process.argv 的结构
console.log(process.argv);
const args = process.argv.slice(2);
if (args[0] === 'hello') {
console.log('你好,' + (args[1] || '世界'));
}第一行的#!/usr/bin/env node叫做shebang,它告诉类Unix系统这个文件要用node来执行,是CLI工具必不可少的一行。然后在命令行里执行node index.js hello 张三,你会看到参数被正确识别并输出了问候语。这就是CLI的最小形态。
接下来要让这个脚本变成一个真正的命令。打开package.json,添加一个bin字段:
{
"name": "mycli",
"version": "1.0.0",
"bin": {
"mycli": "./index.js"
}
}配置好之后,在项目目录下执行npm link,系统会把mycli这个命令链接到全局。此时你在任意位置打开终端,直接输入mycli hello 张三就能运行了。注意在Windows环境下,npm会自动生成一个.cmd包装脚本,无需你额外处理跨平台问题。
二、用commander处理复杂的命令与参数
手写process.argv解析在参数少的时候够用,但一旦涉及选项、默认值、子命令,自己维护解析逻辑会变得非常痛苦。比如用户可能输入mycli copy --force -d ./dist,你还得处理--force和-f的别名关系、处理参数缺失时的错误提示。这时候就该请出成熟的参数解析库了,commander是其中最流行的一个,Git官方的命令行工具也在用它。
安装依赖并改造代码:
npm install commander
#!/usr/bin/env node
const { program } = require('commander');
program
.name('mycli')
.description('一个示例命令行工具')
.version('1.0.0');
// 定义子命令 hello
program
.command('hello')
.description('打招呼')
.argument('<name>', '对方的名字')
.option('-e, --excited', '使用感叹号结尾')
.action((name, options) => {
const suffix = options.excited ? '!' : '';
console.log(`你好,${name}${suffix}`);
});
// 定义子命令 copy
program
.command('copy')
.description('复制目录')
.requiredOption('-s, --src <path>', '源目录')
.requiredOption('-d, --dest <path>', '目标目录')
.action((options) => {
console.log(`从 ${options.src} 复制到 ${options.dest}`);
});
program.parse();这段代码里出现了几个关键概念。command定义子命令,类似git里的git commit这种结构;argument声明位置参数,尖括号表示必填,方括号表示可选;option声明选项,requiredOption则强制用户必须传入;action里编写具体执行逻辑,解析好的参数会以对象形式传进来。
commander最大的好处是帮你自动生成了帮助信息。用户输入mycli --help,会看到格式整齐的命令列表和说明,输入错误命令时也会有友好提示,这些都不需要你写一行额外代码。相比之下,如果只用process.argv,这些体验都要自己造轮子,得不偿失。
顺便提一句选择问题:如果只是极简场景、不想引入依赖,yargs的子集或者手写解析都没问题;但只要工具有两个以上子命令,建议直接上commander,它的API稳定、文档完善,社区生态也最成熟。
三、加入交互式问答与终端美化
现代CLI工具的体验远不止接收参数,很多工具在运行时会向用户提问,比如Vue CLI创建项目时会问你用哪个模板、要不要启用TypeScript。这类交互在Node.js中通过inquirer库实现,它提供了单选、多选、输入、确认等多种问答形式,还支持方向键选择和搜索过滤。
npm install inquirer chalk
const inquirer = require('inquirer');
const chalk = require('chalk');
inquirer
.prompt([
{
type: 'list',
name: 'framework',
message: '选择你喜欢的前端框架',
choices: ['Vue', 'React', 'Svelte', 'Solid'],
},
{
type: 'confirm',
name: 'useTs',
message: '是否使用 TypeScript?',
default: true,
},
])
.then((answers) => {
console.log(chalk.green.bold(`你选择了 ${answers.framework}`));
if (answers.useTs) {
console.log(chalk.yellow('将启用 TypeScript 支持'));
}
});inquirer的prompt方法接收问题数组,每个问题通过type指定类型。上面的代码运行后,终端会显示一个可以用方向键移动的选项列表,用户选择后结果汇集在answers对象里。这种交互方式比让用户记住一堆参数友好得多,特别适合配置类、向导类的工具。
chalk则负责给输出上色。终端支持ANSI转义序列,chalk本质上就是帮你拼接这些不可见的控制字符,让文字显示为红色、绿色、加粗等样式。在实际工具中,约定俗成的做法是:成功信息用绿色、警告用黄色、致命错误用红色,这样用户扫一眼输出就能判断执行状态。除了chalk,也可以考虑ora库,它能在执行耗时任务时显示旋转的loading动画,进一步提升体验。
需要提醒一点:如果你的CLI在Windows的老版本cmd上运行,部分颜色可能不被支持,chalk会自动检测环境并在不支持时降级为无色输出,这一点它处理得比较贴心。
四、发布到npm让全世界用上你的工具
本地开发完成后,最后一步是发布。发布前先确认几件事:package.json里的name没有被占用,version遵循语义化版本规范,bin字段路径正确,另外建议加上files字段声明只发布必要文件,避免把测试数据一起传上去。
{
"name": "mycli",
"version": "1.0.0",
"description": "一个示例命令行工具",
"bin": {
"mycli": "./index.js"
},
"files": [
"index.js",
"commands/"
],
"engines": {
"node": ">=16"
}
}然后在npm官网注册账号,终端里执行npm login登录,再执行npm publish,几十秒后你的工具就上线了。别人只需执行npm install -g mycli全局安装,就能在任何目录使用mycli命令。后续更新时修改版本号再publish即可,如果是修补小bug就升第三位,新增功能升第二位,破坏性变更升第一位。
还有两个实践建议:一是发布前先用npm pack命令查看将要上传的压缩包内容,确认没有多余文件;二是在仓库里写一份清晰的README,说明安装方式和常用命令,因为用户决定要不要装一个工具,往往就看文档开头那几十秒的印象。
五、进阶方向与值得注意的坑
掌握了基础流程后,还有一些进阶能力可以让工具更专业。比如用fs-extra处理文件操作,用axios调用远程API,用execa调用系统命令,用update-notifier提示用户升级新版本。如果工具逻辑复杂,可以把每个子命令拆成独立模块放在commands目录下,主文件只做注册,结构会清爽很多。
坑方面有几点值得注意。首先是shebang必须是文件第一行,前面不能有空行,否则Linux下会报错;其次全局安装后如果命令找不到,检查一下npm的全局bin目录是否在系统PATH里;最后是ESM的问题,如果你在package.json里设置了"type": "module",则要用import语法且shebang写法保持不变,两种模块体系混用是新手最常踩的雷区。
从一行console.log到发布一个有交互、有配色的完整工具,整个过程的核心其实只有三件事:解析输入、执行逻辑、输出结果。把这些机制想明白之后,剩下的就是按照你的需求不断填充功能。不妨从一个解决自己日常痛点的小工具开始,动手写起来,CLI的世界比想象中要容易进入得多。
JavaScript CLI命令行工具Node.js修改时间:2026-09-15 00:00:50