Nodejs 的出现让前端具备了服务端的能力,还有一个特别重要的用途是用来开发很多提升效率的 CLI 工具,比如初始化项目的 脚手架,项目打包运行的工具库等。CLI 是 Command-Line Interface 的缩写,命令行界面的意思。
基于Nodejs开发CLI工具,首先就离不开 Commander 库。它是 Nodejs 进行 CLI 开发的完整解决方案。
Commander安装
npm install commander@5.0.0
注意: 本文基于
commander@5.0.0版本。
引入
常规方式:
const program = require('commander');
复杂程序中,Commander 可能通过多样化的方式使用,可以通过创建本地 Commander的方式使用:
// 方式A
const Command = require('commander');
const program = new Command();
// 方式B
// 5.0.0 版本新增
const { createCommand } = require('commander');
const program = createCommand();
版本
const program = require('commander');
// 写死版本
program.version('0.0.1');
const program = require('commander');
// 动态读取 package 的版本
program.version(require('../package').version);
可以通过
$ comd-cli -V显示版本号。
options
option() 方法用于 定义 Commander 的参数选项 options。
基本API:
program.option('-n, -name', 'description')
n: 参数名称的短标示(flag),用单个字符表示。name: 参数名称。n和name之间除了用,,还可以用空格或者|分割。description: 参数描述。
示例:
const program = require('commander');
program
.option('-d, --debug', 'debugging')
.option('-s, --small', 'small size')
.parse(process.argv);
console.log(program.opts());
//执行日志如下👇
$ comd-cli
{ debug: undefined, small: undefined}
$ comd-cli -d
{ debug: true, small: undefined}
$ comd-cli -ds
{ debug: true, small: true}
program.opts()获取命令所有参数信息。- 不带对应标示,参数的默认值是
undefined。 - 带对应标示,参数值为
boolean类型 -true。 -d -s可以简写为-ds。$comd-cli --debug等同于$comd-cli -d, 一般都使用更短的标示。- 类似
--template-engine这种命名的名称,会被自动转为驼峰templateEngine。
后续示例都假定通过
$comd-cli这个CLI执行。
非 boolean 类型参数
如果希望参数不是一个 boolean 类型,而是具体的 vaue。可以通过增加 <type> 或者 [type] 实现:
const program = require('commander');
program
.option('-d, --debug ', 'if debugging')
.option('-s, --size <type>', 'size type')
.option('-l, --limit [type]', 'file size limit')
.parse(process.argv);
console.log(program.opts());
//执行日志如下👇
$ comd-cli -s
error option '-s, --size <type>' argument missing
$ comd-cli -s small
{ debug: undefined, size: 'small', limit: undefined }
$ comd-cli -l
{ debug: undefined, size: 'small', limit: true }
$ comd-cli -l 1m
{ debug: undefined, size: 'small', limit: '1m' }
- 被
<>包裹的拓展参数type为必输值。 - 被
[]包裹的拓展参数type为非必输,默认值为true。 type是由用户自主命名。
自定义默认值
如前所述,参数的默认值都为 true。你可以在 options() 方法中添加第三个参数作为默认值
const program = require('commander');
program
.option('-d, --debug ', 'if debugging', '')
.option('-s, --size <type>', 'size type', 'smal')
.option('-l, --limit [type]', 'file size limit', '1M')
.parse(process.argv);
console.log(program.opts());
//执行日志如下👇
$ comd-cli
{ debug: undefined, size: 'small', limit: '1M' }
$ comd-cli -s large
{ debug: undefined, size: 'large', limit: '1M' }
- 对于带拓展
type的参数,如上例的size和limit,可设置默认值。 - 无拓展
type的参数,如上例的debug,设置默认值无效。 - 默认值可以是对象,数组等。
变更默认值
可以通过 --no-name 达到修改 name 参数的默认值的效果。
const program = require('commander');
program
.option('-d, --debug ', 'if debugging')
.option('-s, --size <type>', 'size type', 'small')
.option('--no-debug', 'change debug')
.option('--no-size', 'Remove size')
.parse(process.argv);
console.log(program.opts());
//执行日志如下👇
$ comd-cli
{ debug: true, size: true}
$ comd-cli -d
{ debug: false, size: true}
$ comd-cli -s big
{ debug: false, size: 'big'}
$ comd-cli --no-debug
{ debug: false, size: true}
$ comd-cli --no-size
{ debug: false, size: undefined}
--no-name参数会把name命名的参数的默认值变为true,不管它是boolean还是value。- 命令带
--no-name参数执行,boolean类型的name会变成false,value值会变成undefined。
自定义option处理
当 option() 方法的第三个参数是一个函数(回调函数)的时候,该函数会自动对参数值做处理。
const program = require('commander');
const func = (dummyValue, previous) => {
console.log(`dummyValue: ${dummyValue} ; previous: ${previous}`);
return dummyValue.split(',')
}
program
.option('-d, --debug ', 'if debugging')
.option('-s, --size [type]', 'size type',func,['1'])
.parse(process.argv);
console.log(program.opts());
//执行日志如下👇
$ comd-cli -s 2,3,4
dummyValue: 2,3,4 ; previous: 1
{ debug: undefined, size: [ '2', '3', '4' ] }
- 这个时候,默认值可做为
option()方法的第四个参数。 - 回调函数参数:
dummyValue用户指定的值;previous: 当前值,上述示例是默认值。 - 回调函数返回值作为
option最新值。
必需参数
如果一个命令行必须有某个参数才可以执行,可以通过 requiredOption() 方法处理。
const { program } = require('commander');
program
.option('-d, --debug ', 'if debugging')
.requiredOption('-s, --size <type>', 'size type');
.parse(process.argv);
//执行日志如下👇
$ comd-cli
error: required option '-s, --size <type>' not specified
参数传递
你可以使用 -- 来表示选项的结束,剩余的参数将被使用而不会被解释。这对于将选项传递给另一条命令特别有用。
const program = require('commander');
console.log(process.argv);
program
.option('-d, --debug ', 'if debugging')
.option('-s, --size [type]', 'size type','small')
.parse(process.argv);
console.log(program.opts());
console.log(program.args);
//执行日志如下👇
$ comd-cli -d -- -s big git react
{ debug: true, size: 'small' }
[ '-s', 'big', 'git', 'react' ]
- 注意,
--后面的-s big便不会被option处理。 - 一般,我们通过
parse(process.argv)方法把其他没有被options处理的参数变为program.args对象使用。
Commands
Commander 提供了两种用于添加命令的方式,分别对应一下两个 API:
command(): 通过用户自己定义命令名称,和一些命令参数来添加一个命令。addCommand(): 用于添加一个已经初始化好的子命令。复杂场景下,往往会定义一个方法来生成命令,就需要使用该方式。
其实,用 addCommand() 添加的子命令,也是通过 command() 方式初始化的。
addCommand()方式是在5.0.0版本才引入的。
命令的执行也有两种方式:
- 给命令添加一个
action事件处理程序。需要5.0.0及以上版本 - 通过一个单独的可执行文件。
下面,我们通过以下三种情况来了解:
command()+action handlercommand()+ 可执行文件addCommand()使用示例
command()+ action handler
一定注意,5.0.0及以上版本才支持的方式:
program
.command('clone <required> [optional]')
.alise('c')
.useage('<command> <fileName>')
.desciption('clone command descption')
.action((required, optional) => {
console.log('command called')
})
//执行日志如下👇
$ comd-cli clone fileName
command called
clone: 命令名称。required: 必须参数。optional: 其他可选参数。alise('c'): 给命令一个简短的别名c。usage(): 命令的使用格式。使用--help会显示。desciption(): 设置命令描述信息。action(handler): 命令执行程序,handler是一个回调函数,可以获取到命令参数。
command()+ 可执行文件
这种方式是早期版本便一直支持的方式:
// 假设该入口脚本文件目录为:example/comdCli.js
program
.command('clone <required> [optional]','clone command descption')
.alise('c')
//执行日志如下👇
$ comdCli clone fileA
Error: 'comdCli-clone' does not exist
- 命令描述作为
command()方法的第二个参数。这个时候就会通过可执行文件执行命令。 Commander会自动使用program-subCommand(上例中是comdCli-clone) 的名字搜索入口脚本文件目录下的可执行文件来执行子命令。- 不需要
description和action。usage和alise同上。
addCommand() 使用示例
addCommand() 只是 5.0.0 版本增加的一个用于增加一个已经初始化好的子命令到 program 的方法:
const Command = require('commander');
const program = new Command();
program
.command('tea')
.action(() => {
console.log('brew tea');
});
function makeHeatCommand() {
const heat = new Command();
heat
.command('jug')
.action(() => {
console.log('heat jug');
});
return heat;
}
// 看这里
program.addCommand(makeHeatCommand());
program.parse(process.argv);