作为一个开发人员,有时候使用命令行工具的效率比我们直接使用具有图形界面的软件更高,node.js 凭借其强大的功能,我们不用学习 shell 编程语法也可以轻松开发一个命令行工具

本文将以开发一个可以更改 npm 仓库地址的命令行脚本工具为例,介绍如何使用 node.js 开发一个命令行工具。

准备与思路

首先你得安装 node.js 环境,如果你还没有安装,可以参考官方文档进行安装。

安装好 node.js 后,我们要先明确开发思路,要使用 js 开发一个命令行脚本,那么肯定是要执行这个 js 文件的,而现在能运行 js 的环境一般就是浏览器和 nodejs,浏览器肯定是不行的,所以我们明确了一点,我们写的 js 文件最后需要使用 node.js 来运行,所以你得先知道你系统的 nodejs 的路径

可以使用 which 命令查看

1
which node

这里以我的电脑为例

1
2
which node
/usr/local/bin/node #输出

接着我们继续思考,如何让系统知道要使用 node.js 运行这个 js 文件呢?

这个我们可以在 的顶部添加一个 Shebang 行,Shebang 行的格式为:

  1. Shebang (#!):这是一个特殊的字符序列,用于告诉操作系统使用哪个解释器来执行该脚本。
  2. /usr/bin/env:这是一个环境变量路径,用于在系统的 PATH 环境变量中查找指定的命令。
  3. node:这是要查找并使用的命令,即 Node.js 解释器。

例如 的意思是:使用系统中找到的第一个 node 命令来执行这个脚本。

1
#!/usr/bin/env node

知道了 js 文件该怎么运行后,我们还得知道什么时候去运行 js 文件?

这个当然是我们输入一个脚本命令的时候去执行 js 文件,所以我们接下来要实现的是,如何输入一个脚本命令就让操作系统执行 js 文件呢?

这个我们就得靠 nodejs 了,我们需要配置 package.json 文件

文件中有一个 配置项,这个配置项是定义可执行文件的映射,例如
1
2
3
"bin": {
"xzm": "bin/index.js"
}

这个映射的意思是可以使用 这个命令来运行 目录下的 文件

也就是当你在 的时候,他就会自动去

接下来我们就差最后一步 js 文件的编写了,要实现切换 npm 仓库地址,我们得知道 npm 切换仓库的命令是什么

npm 切换仓库的命令是

例如设置仓库地址为官方的仓库地址

1
2
npm config get registry # 查看当前仓库地址
npm config set registry https://registry.npmjs.org/ # 设置为官方仓库地址

所以我们的 js 得实现运行 npm 命令

我们可以使用 commander、inquirer 这两个包来进行实现

commander 包可以用来解析命令行参数

inquirer 包可以用来让用户输入参数

这两个包的具体使用将放在后面进行陈述

接着为了在全局都能够使用我们的自定义脚本,我们需要使用 命令将我们编写的项目链接到全局环境

1
npm link

到现在我们的思路和流程就很清晰了

  1. 使用 npm init 初始化一个 npm 项目
  2. 在 package.json 文件中配置 bin 命令
  3. 在 bin 目录下创建 index.js 文件
  4. 在 index.js 文件中使用 commander 和 inquirer 包实现切换 npm 仓库地址的功能
  5. 使用 npm link 将项目链接到全局环境,这样才能在全局进行使用

具体实现

首先新建一个目录,并进入到该目录下,使用 npm init 初始化一个 npm 项目

1
npm init -y

会生成这样一个 json 文件

1
2
3
4
5
6
7
8
9
10
11
12
{
"name": "xzm",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": ""
}

接着我们配置 package.json 文件如下,name 属性和 bin 属性中的脚本名字和路径可以自由取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"name": "xzm",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type": "module",
"bin": {
"xzm": "bin/index.js"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}

这里先对 js 要用的函数和包进行简单的介绍

首先是导入

1
2
import { program } from "commander"; // 导入 program
import inquirer from "inquirer"; // 导入 inquirer

program 的基本使用

1
2
3
4
5
6
7
8
9
10
11
12
13
// version 函数和 description 函数
// version 函数用于当输入参数为 -v 或者 --version 时,打印版本号,例如 xzm -v 或者 xzm --version
// description 函数可以定义命令的描述
program.version("0.0.1", "-v, --version").description("星凪工具"); // 定义命令行参数

// command 函数用于注册命令,第一个参数是命令名,第二个参数是命令描述,第三个参数是命令的回调函数
// action 函数用于定义命令的回调函数,也就是命令输入后需要执行的操作
program
.command("get")
.description("获取当前npm的镜像源")
.action(() => {
console.log("获取当前npm的镜像源");
});

inquirer 的基本使用

1
2
3
4
5
6
7
8
9
10
11
12
13
// inquirer.prompt 方法用于让用户输入参数,第一个参数是一个数组,数组的每一项是一个对象,对象有 type、message、name、default、choices 等属性,具体可以参考官方文档
// 下面代码是让用户输入npm源地址,并获取用户输入的结果
inquirer
.prompt([
{
type: "input",
message: "请输入npm源地址",
name: "registry",
},
])
.then((answer) => {
console.log(answer);
});

exec 函数的基本使用,exec 函数是 nodejs 内置的模块,可以用来执行 shell 命令

1
2
3
4
5
6
7
8
9
// exec 函数用于执行 shell 命令,这个是node.js的内置模块,第一个参数是命令,第二个参数是回调函数,回调函数有三个参数,分别是err、stdout、stderr,分别表示错误信息、标准输出、标准错误输出
// 下面代码是执行 npm get registry 命令,获取当前npm的镜像源
exec("npm get registry", (err, stdout, _) => {
if (err) {
console.log(`exec error ${err}`);
return;
}
console.log(stdout.replace("\n", ""));
});

然后我们安装要用到的两个第三方库 commander 和 inquirer

执行 命令安装依赖

安装依赖后,新建一个 bin 目录并在该目录下新建一个 js 文件,这里以 index.js 为例

然后编写 js 文件内容如下

代码都添加了详细的注释

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
#!/usr/bin/env node
import { program } from "commander";
import { fileURLToPath } from "node:url";
import { exec } from "node:child_process";
import fs from "node:fs";
import inquirer from "inquirer";

// 获取package.json的文件路径
const packageJsonPath = new URL("../package.json", import.meta.url);
// 获取package.json的文件内容
const packageJson = JSON.parse(
fs.readFileSync(fileURLToPath(packageJsonPath), "utf-8")
);

// 定义命令行参数,当输入参数为 -v 或者 --version 时,打印版本号,例如 xzm -v 或者 xzm --version
program.version(packageJson.version, "-v, --version").description("星凪工具");

// 注册 get 命令
program
.command("get") // 命令名为 get
.alias("g") // 命令别名为 g
.description("获取当前npm的镜像源") // 命令描述
.action(() => {
// 当输入命令 get 或者 g 时,执行该函数
// exec方法可以执行shell命令,第一个参数是命令,第二个参数是回调函数,回调函数有三个参数,分别是err、stdout、stderr,分别表示错误信息、标准输出、标准错误输出
// 这里执行了npm get registry命令,获取当前npm的镜像源
exec("npm get registry", (err, stdout, _) => {
if (err) {
console.log(`exec error ${err}`);
exec(`echo exec error ${err}`);
return;
}
console.log(stdout.replace("\n", ""));
});
});

// 注册 set 命令
program
.command("set") // 命令名为 set
.description("设置npm源") // 命令描述
.action(() => {
// 当输入命令 set 时,执行该函数
inquirer // 调用inquirer包的prompt方法,让用户选择npm镜像源
.prompt([
{
type: "rawlist",
message: "请选择镜像源",
name: "registry",
default: "npm官方源",
prefix: "☆☆☆☆",
suffix: "****",
choices: [
{
key: "官方源",
value: "https://registry.npmjs.org/",
name: "官方源",
},
{
key: "淘宝源",
value: "https://registry.npm.taobao.org/",
name: "淘宝源",
},
{
key: "cnpm源",
value: "http://r.cnpmjs.org/",
name: "腾讯源",
},
{
key: "阿里源",
value: "https://npm.aliyun.com/",
name: "阿里源",
},
],
},
])
.then((answer) => {
// 获取用户选择的镜像源
const registry = answer.registry;
// 执行npm config set registry命令,设置npm镜像源
exec(`npm config set registry ${registry}`, (err, stdout, stderr) => {
if (err) {
console.log(`exec error ${err}`);
return;
}
console.log(`设置成功,当前镜像源为: ${registry}`);
});
});
});

// 解析命令行参数
// 比如你输入xzm get,则会执行get命令,get命令会执行npm get registry命令,获取当前npm的镜像源
program.parse(process.argv);

最后我们在命令行中执行 npm link 将项目链接到全局环境

1
npm link

然后使我们在 package.json 中自定义的命令例如 xzm 来验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
xzm
# 输出
Usage: xzm [options] [command]

星凪工具

Options:
-v, --version output the version number
-h, --help display help for command

Commands:
get|g 获取当前npm的镜像源
set 设置npm源
help [command] display help for command

输入 xzm get

1
2
xzm get
# https://registry.npmjs.org/

输入 xzm 的意思是执行对应的 js 文件,get 是命令名,会被 js 文件解析并执行对应的操作

这样就完成了,如果你想把写成的包分享给别人,可以登录 npm 账号后,使用 npm publish 命令发布到 npm 仓库,别人就可以使用 npm 来下载你发布的包到全局下,然后就可以使用了

1
2
npm login   # 先登录或者注册
npm publish # 发布包

我将这个项目发布到了 npm 仓库,你可以直接使用 npm 来安装进行体验

1
npm install -g xzm  # 将工具安装到全局环境

如果不想使用了,可以使用 npm unlink xzm 命令或者 npm uninstall -g xzm 来将项目从全局环境中移除

1
2
npm unlink xzm
npm uninstall -g xzm

总结

nodejs 是非常强大的,基于 exec 函数我们可以执行各种各样的 shell 命令,还可以执行其他语言的一些脚本,比如你可以使用 python 写一个脚本,然后使用 exec 函数来运行这个 python 文件

这样我们就可以实现各种各样的功能,只有你想不到没有 nodejs 做不到