编写一个基于node的CLI

最近因为需要给其他同事提供一些方便开发的工具,就写了一个基于 node 的 CLI,下面给大家分享下怎么自己去动手写一个基于 node 的 CLI。

原理

当前端工程安装到全局目录下之后,可以直接通过别名来执行 package.json 当中 bin 下面的 js 文件,我们就是通过编写这个 js 文件来实现相应命令。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"name": "testnode-cli",
"version": "1.0.0",
"description": "CLI测试",
"main": "index.js",
"bin": {
"testnode": "./bin/index.js"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git@github.com:yangzq007/testnode-cli.git"
},
"author": "yangzq007@126.com",
"license": "ISC",
"dependencies": {
"commander": "^2.15.1"
}
}

上面所展示的示例当中 testnode 字段是别名,命令头也是由这里决定的,执行的入口是 ./bin/ 下的 index.js 文件

编写

1.创建一个 CLI 项目文件夹,并在项目目录当中创建一个如上的 package.json 文件(可以使用 npm init 命令来创建)

2.然后在该文件夹目录下运行 npm install 命令初始化该工程

3.添加 package.json 文件当中 bin 字段对应的 js 文件

4.编写 js 脚本文件实现命令(文件内容如下所示)

5.使用命令 npm install -g 将当前项目安装到全局

6.愉快测试和使用自己编写的命令

如下是一个简单的 js 脚本内容,编写安装完毕后,当你在终端输入 testnode 时,就会输出对应的 log

1
2
3
4
5
6
7
#!/usr/bin/env node

function show() {
console.log("testnode-cli is comming!");
}

show();

添加参数

我们虽然可以在 show 方法中添加我们的处理逻辑,但是我们的脚本只能执行这样一个命令,怎么给它加上参数执行多条命令呢,比如 testnode showtestnode deletetestnode add 等来执行不同的操作,那我们需要给它添加参数处理的逻辑

如下是一个简单的参数处理的逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#!/usr/bin/env node

var params = {};
process.argv.slice(2).forEach( function (item) {
switch (item) {
case "show":
config.show = true;
break;
case "delete":
config.delete = true;
break;
case "add":
config.add = true;
break;
case "-l":
config.log = true;
break;
}
});

function show () {
console.log("testnode-cli is comming!");
}

function delete () {
console.log("do some delete");
config.log && console.log("delete logging");
}

function add () {
console.log("do some add");
config.log && console.log("add logging");
}

config.show && show();
config.delete && delete();
config.add && add();

或者我们可以使用 commander 插件来帮我们处理参数,这样效率会更高,commander 在开头的 package.json 文件当中已经引入,实现大致如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#!/usr/bin/env node

function show () {
console.log("testnode-cli is comming!");
}

function delete () {
console.log("do some delete");
}

function add (name)) {
console.log(`do some add, name:${name}`);
}

var program = require('commander');

program
.version(require('../package.json').version);

program
.command('show') //命令
.description('展示相关描述') //命令描述
.action(function() {
show(); //命令动作
}).on('--help',function() {
console.log('展示相关信息'); //选项
});

program
.command('delete')
.description('删除相关文件')
.action(function() {
delete();
});

program
.command('add [name]') //带参数的处理
.description('添加相关文件')
.action(function(name) {
add(name);
});

program.parse(process.argv);

commander 更详细的用法介绍参考 https://github.com/tj/commander.js

命令实现

上面的操作已经基本实现了一个 CLI 的结构,我们剩下要做的就是实现命令的具体内容了,我们可以通过一些现有的工具来方便实现我们的操作。

1
2
3
//我们可以使用execSync来创建一个阻塞node的事件循环的shell客户端来执行我们常用的shell命令
const execSync = require("child_process").execSync;
execSync("ls");

脚手架实现

我们常用的前端脚手架的实现思路一般是在远端创建一个模板工程,在创建我们自己的工程时将远端的模板工程克隆到本地,然后通过 CLI 实现对模板工程相应关键信息的修改来转换成对应的开发工程。

为了方便实现替换,我们可能需要在模板当中对一些关键信息使用特殊样式的字符替换,例如用&{projectName}&来占位项目名称,这样在替换时就方便检索和替换,避免出现误操作。

相关的文件处理和文字处理可能还需要特殊的工具,这里不再详细描述。

解释器声明

你可能注意到脚本文件的头一行有个声明,这个是表示用 node 来执行这个文件,如果没有这句声明,就会用默认的文本编辑器打开相应的 js 脚本文件。

windows

1
#! node

linux/unix/mac

1
#!/usr/bin/env node

#!/usr/bin node#!/usr/bin/env node 的区别

这个在 unix 类的操作系统才有意义。#!/usr/bin node 是告诉操作系统执行这个脚本的时候,调用 /usr/bin 下的 node 解释器,#!/usr/bin/env node 这种用法是为了防止操作系统用户没有将 node 装在默认的 /usr/bin 路径里。当系统看到这一行的时候,首先会到 env 设置里查找 node 的安装路径,再调用对应路径下的解释器程序完成操作。