制作 node 命令行!
这篇文章是为了记录使用commander
模块制作node
命令行工具的经验以作备忘,请善用ctrl + f
进行搜索。
基本
下面是几个commander
模块最基本的 概念 或者 方法,最简单的命令行可以参考 github - 基本示例。
概念
-
commander
推荐链式调用写法。 - 命令行工具有关的一般都写在项目根目录的
bin
目录下 - 在命令行入口文件开头要加上
#!/usr/bin/env node
来指定使用node
运行 - 可以直接以
node 文件名.js [子命令] [参数]
的形式使用命令行,下文会给出如何全局调用命令行
基本方法
-
version('1.0.0')
设置版本: 接受一个字符串,设置当前工具的版本。 -
description('介绍')
设置介绍: 接受一个字符串,设置为当前命令的介绍。 -
parse(process.argv)
解析参数: 参数固定为process.argv
,一般放在末尾。
添加参数
可以使用option()
指定一个参数,如下:为命令行指定一个参数-t
:
program
.version('1.0.0')
.description('这是一个命令行工具')
.option('-t, --test [测试参数]', '输入测试参数')
option()
的第二个参数是参数的介绍,而第一个参数很重要,它指定了参数的两种写法、是否必填 以及 参数的名字。下面对第一个参数-t, --test [测试参数]
进行下分析:
- 参数写法
-t, --test
- 是否必填
<>
必填 或者[]
选填, - 参数的名字
测试参数
,这个填什么都行,用于让用户更加了解。
参数使用
当用户输入了一个参数之后该如何使用它呢?这就要用到action()
方法了,这个方法指定了用户在调用了命令之后应执行的逻辑。而 用户输入的参数就包含在action
回调的第一个参数里:
//app.js
program
.command('config')
.description('配置')
.option('-t, --test [测试参数]', '输入测试参数')
.action(cmd => {
console.log(cmd)
})
// 用户输入
> node app.js config -t 123
// 输出
> {
...
test: 123,
}
要注意的是,如果这里不指定参数名-t
直接追加参数值的话,action
回调的参数cmd
的值为就会被替换成追加的参数值,如下:
// 用户输入
> node app.js config helloworld
// 输出
> helloworld
添加子命令
可以通过command()
来快速添加一个子命令,该方法接受一个字符串参数,内容为该子命令的名字,并且可以通过链式调用快速完善一个子命令。
program
.command('config')
.description('配置')
.action(cmd => {
console.log('配置')
})
全局命令行
配置全局安装非常简单,首先确保命令行的入口文件的第一行是#!/usr/bin/env node
,这个很关键。
然后在package.json
里添加bin
字段,如下:这个auto-git
就是全局安装之后你的命令行工具的名字,而后面的"bin/index.js"
就是命令行工具的入口了,这个不一定是index.js
,自己可以随便指定,但是第一行都要有#!/usr/bin/env node
。
"bin": {
"auto-git": "bin/index.js"
},
本地调试
想要本地调试也很简单,直接在 项目根目录 下执行如下命令就好了:
npm install . -g
然后就可以在任意位置使用bin
中指定的工具名字来使用命令行了,并且你修改代码后也无需重新安装即可使用。
发布后的安装
和本地调试的安装基本一样,不过.
换成了你的package
包名,然后就可以正常使用了。
npm install <你的包名> -g
这里有一点需要注意的是,package.json
里的name
字段指定的是你的包名,而 package.json
里的bin
字段才是命令行的名字。
子命令模式
commander
有一个模式叫做 子命令模式,可以把不同的子命令拆分在多个文件里来方便维护。这里写一下用法,可以参考例子 github - 子命令模式。
首先是启用子命令模式,在一般的写法中,使用command()
注册子命令之后都会链式调用一个action()
来添加调用子命令时触发的逻辑。只要 在使用command()
之后没有调用action()
,就可以启用子命令模式,如下:
//app.js
#!/usr/bin/env node
const program = require('commander')
program
.version('1.0.0')
.description('这是一个命令行工具')
.option('-t, --test [测试参数]', '输入测试参数')
.command('add', '添加')
.parse(process.argv)
可以看到,这里只使用command()
注册了子命令而并没有设置其参数和逻辑,那这些内容应该写在哪里呢?子命令的内容应写在同级目录下的<主命令文件名>-<子命令名>.js
中。例如,上面例子中add
子命令的内容就应该写在app-add.js
中,如下:
var program = require('commander')
program
.option('-t, --test [测试]', '测试参数')
.action(cmd => cmdAction(cmd))
.parse(process.argv)
function cmdAction(cmd) {
console.log('添加')
}
这样就可以正常使用子命令了,但是要注意以下两点:
- 子命令文件中,
program
链式调用的最后一定要加上parse(process.argv)
,不然子命令会无法执行且不报任何错误 - 子命令的文件只能放在同级目录下,没有这个文件的话会报下面这个错误:
error: app-add(1) does not exist, try --help
使用 json 保存配置项
使用json
来保存命令行的配置项还是比较常用的,但是类似的文章并没有找到太多,所以这里也简单写一下,可以参考例子 github - 保存配置文件。
这里用fs
和path
模块来读写json
文件,并且封装出了下面三个方法供其他文件使用:
-
init()
: 放在最开始的地方,用于检测本地有没有设置文件setting.json
,没有的话就新建该文件 -
load()
: 读取setting.json
中的数据并转化为js
对象 -
save()
: 将接受的参数保存到setting.json
const fs = require('fs')
const path = require('path')
// 设置json保存的位置
const settingFile = path.join(__dirname, 'setting.json')
// 设置的初始模板
const settingTempalte = {
default: 'default value'
}
module.exports = {
init: () => {
try {
fs.statSync(settingFile)
}
catch (e) {
console.log('设置文件不存在, 已新建')
save(settingTempalte)
}
},
load: () => {
return JSON.parse(fs.readFileSync(settingFile, 'utf8'))
},
save: (data) => {
fs.writeFileSync(settingFile, JSON.stringify(data, null, 4))
}
}
具体的调用方法可以在 例子 中找到。
跟随 package.json 文件中的版本号
因为commander
需要通过version()
来设置版本,这样在加上package.json
里的版本就要修改两个地方,如果我们想让命令行工具的版本跟随package.json
中的版本应该怎么做呢。很简单,也是使用fs
模块就可以了。
const fs = require('fs')
const packageData = fs.readFileSync('./package.json', 'utf8')
const packageVersion = JSON.parse(packageData).version
program
.version(packageVersion)
...