Node.js 命令行工具的编写

日常开发中,编写 Node.js 命令行工具来完成一些小任务是很常见的操作。其编写也不难,和日常编写 Node.js 代码并无二致。

package.json 中的 bin 字段

一个 npm 模块,如果在 package.json 中指定了 bin 字段,那说明该模块提供了可在命令行执行的命令,这些命令就是在 bin 字段中指定的。

package.json

{
  "bin": {
    "myapp": "./cli.js"
  }
}

程序安装后会可在命令行执行 myapp 命令,实际执行的就是指定的这个 cli.js 文件。如果是全局安装,会将这个目标 js 文件映射到 prefix/bin 目录下,而如果是在项目中安装,则映射到 ./node_modules/.bin/ 目录下。

比如上面的示例,全局安装后会将 cli.js 映射到 /usr/local/bin/myapp 目录下。

# 查看项目中安装的所有可执行模块
$ ll node_modules/.bin
...
webpack -> ../webpack/bin/webpack.js
...

如果你的 npm 包只提供了一个可执行的命令,可直接将 bin 字段设置为目标文件,此时命令行中可执行的 CLI 命令名为 npm 包名(即 name 字段)。

{
    "name": "my-program",
    "version": "1.2.5",
    "bin": "./path/to/program"
}

所以上面的配置和下面这个配置是等效的。

{
    "name": "my-program",
    "version": "1.2.5",
    "bin": {
        "my-program": "./path/to/program"
    }
}

执行:

$ my-program 

CLI 命令的编写

为项目添加 README 文件是很常见的操作,每次都从零开始是没必要的。这时候就可以通过创建一个 README 模板,然后写一个 CLI 工具来生成到项目中。这样,将工具安装到全局或通过 npx 就可以方便地完成 README 文件的创建。

$ npx mkreadme
README.md created

初始化项目

$ npm init -y

修改 package.json 文件为如下内容:

{
  "name": "mkreadme",
  "bin": "./cli.js",
  "version": "0.1.0",
  "license": "MIT"
}

创建入口文件

$ touch cli.js

入口文件即 bin 字段所指向的文件,它可以是任何文件名,只需要在行首指定运行环境即可。Node.js 的 CLI 命令,期望的运行环境当然是 Node.js。

cli.js

 #!/usr/bin/env node
 // 其他代码...

然后添加我们的功能代码,从远端获取一个 README 模板文件到本地。

cli.js

#!/usr/bin/env node

const fs = require("fs");
const https = require("https");

const TEMPLATE_FILE =
  "https://raw.githubusercontent.com/wayou/readme-template/master/README.md";

const file = fs.createWriteStream("README.md");
https.get(TEMPLATE_FILE, resposne => {
  resposne.pipe(file);
  console.log("README.md created");
});

这里,我们将模板文件放远端的一个位置然后通过网络请求下载下来,而不是直接放到 npm 模块中。这样做的好处是后面可以随时更新我们的模板文件而无须重新发布这个 npm 模块。

调试

通过在当前开发目录进行 link 操作可进行本地调试。

$ npm link
link 操作的输出信息
npm WARN mkreadme@0.1.0 No description
npm WARN mkreadme@0.1.0 No repository field.

up to date in 3.435s
found 0 vulnerabilities

/Users/wayou/.nvm/versions/node/v11.14.0/bin/mkreadme -> /Users/wayou/.nvm/versions/node/v11.14.0/lib/node_modules/mkreadme/cli.js
/Users/wayou/.nvm/versions/node/v11.14.0/lib/node_modules/mkreadme -> /Users/wayou/Documents/dev/github/mkreadme

然后就可以在任何地方执行刚刚创建的 CLI 命令了。

$ mkreadme
README.md created

参数的获取

让命令支持参数可以实现更加灵活的功能。通过 process.argv 在代码中能够获取到来自命令行的输入。但需要注意它返回的参数列表中前两位是 Node.js 的路径和当前项目的路径,从第三个元素开始才是命令中用户输入的数据。

#!/usr/bin/env node

const fs = require("fs");
const https = require("https");

const TEMPLATE_FILE =
  "https://raw.githubusercontent.com/wayou/readme-template/master/README.md";

+ const [, , ...args] = process.argv;

const file = fs.createWriteStream("README.md");
+ const url = args[0] || TEMPLATE_FILE;
+ https.get(url, resposne => {
  resposne.pipe(file);
  console.log("README.md created");
});

通过添加参数的支持,我们可以让使用者手动指定一个模板地址以下载对应的模板文件。

发布

最后一步就是发布出去,这样所有人就能安装使用了。

$ npm publish --access public

安装与使用

$ npm i -g mkreadme
$ mkreadme
README.md created

除了像上面将命令安装到全局使用外,个人更加推荐的方式是通过 npxnpx 会自动查找本机是否有安装相应模块,如果没有的话,自动去远端查找并执行。通过 npx 就不用安装到本地,每次运行都可以使用远端最新的版本。

$ npx mkreadme
README.md created

后续的优化

示例中只实现了基本的功能,作为一个功能健全的实用工具,可以做以下的优化:

  • 生成时做重名判断,如果已经存在 README 文件则提示是否覆盖。
  • 文件下载和创建过程中的异常处理及提示。
  • 提供并打印帮助信息,对使用者更加友好。
  • 对输出进行格式化,高亮输出相关信息,使信息更易读。

完整的示例

上面示例中的代码可在 mkreadme 这个仓库中找到。同时也发布到了 npm,可直接使用体验该工具。

三方工具

命令行工具能够打印帮助和使用信息是很重要的,如果自己输出的话,会面临格式化这些内容的麻烦。 如果提供的参数很多,解析处理用户输入的参数也是件很麻烦的事。

像参数校验,错误提示及帮助信息的输出,这些命令行工具基本的功能已经有三方库比较成熟地解决了,比如 commander.js。通过这个库可方便地编写更加复杂的命令行工具。

至于将输出信息进行高亮加彩色进行展示,也有相应三方库比如 chalk

相关资源