命令行工具是一种通过命令行界面执行特定任务或操作的软件程序,用户可以在终端窗口中输入指定的命令来调用这些工具。
命令行工具在前端开发中非常常见,常见的像 Webpack、Vite、Rollup等构件工具提供了命令行工具,运行时 Nodejs、包管理器NPM也都属于命令行工具。
基于 NodeJS 的运行时,我们可以通过 JavaScript 来编写命令行工具,一个可执行的js文件就可以被当作一个命令行工具,只需要将其声明到package.json的bin属性下即可,例如:
{
"bin": {
"log": "./dist/log.js",
"split": "./dist/split.js"
},
// ......
}
这样就可以声明两个命令,log
和split
,当在项目中安装依赖时,会自动将库中声明的命令链接到node_modules/.bin
目录下。
process
process 对象是 Node.js 提供的一个全局对象,用于提供有关当前进程的信息和控制。它包含了许多有用的属性和方法,可以用来管理当前 Node.js 进程。
常用属性
- process.argv: 获取命令行参数数组。
- Process.env: 获取环境变量。
- procress.pid: 获取进程ID。
- process.platform: 获取操作系统平台。
- process.cwd(): 返回当前工作目录路径。
控制方法
- process.exit([code]): 退出当前进程,可选地指定退出码。
- process.kill(pid[, signal]): 向指定 PID 的进程发送信号(默认为 SIGTERM)。
监听事件
- exit:当进程退出时触发。
- uncatchException:当发生未捕获异常时触发。
// 输出命令行参数
console.log(process.argv);
// 输出环境变量
console.log(process.env);
// 监听 exit 事件
process.on('exit', (code) => {
console.log(`进程退出,退出码: ${code}`);
});
// 异常处理
process.on('uncaughtException', (err) => {
console.error('未捕获的异常:', err);
});
但是,使用process开发命令行工具过于原始,效率低下,我们可以通过一下优秀的工具来协助我们进行开发。
commander.js
Commander.js 是 Node.js 中一个流行的命令行界面(CLI)工具,用于解析用户输入的命令和选项。它使开发者可以方便地构建强大的命令行应用程序,并且提供了许多功能,如定义命令、添加选项、处理参数等。
- 定义命令:使用 Commander.js 可以很容易地定义各种不同的命令,并在执行时触发相应的函数。
- 添加选项:可以为每个命令设置各种选项(flags),像是
--verbose
,-f
等。这些选项会作为参数传递给对应的事件函数,在其中进行相应处理。 - 支持子命令:通过嵌套定义子级别的子程序来实现复杂 CLI 结构,帮助组织代码并提高可读性。
- 自动生成帮助文档及版本号信息: Commander.js 能够根据你所设定好的各个 options、commands 自动生成基本格式化过后的本脚本用法帮助即刻对象;默认情况下还能够生成 –version 信息;
- 与其他模块结合: 配合其他 NPM 模块例如 inquirer 可以创建出更加交互式和友好CLI总之, 通过简单地调用
require('commander').parse(process.argv);
, 使用者就能够完成一个最基础的命令行工具。
命令
最简单的命令通过program
对象进行设置,可以设计最简单的命令属性,像命令描述等
import {program} from 'commander';
import { readFileSync } from 'fs';
import parse from 'parse-json';
const pkg = parse(readFileSync('package.json', 'utf-8'))
program
.name('fapi') // 名称
.description('Generate typeScript file by swagger json.') // 描述文案
.version(pkg.version) // 版本信息, 通过-V参数查看
.option('-c, --config', 'Configure through fapi.config.js.') // 自定义选项
.action((options) => { // 命令回调函数,可以接收解析后的参数
console.log('[ fapi options ] >', options)
})
program.parse()
此时执行文件并添加--help
参数可以查看到命令介绍
命令可以设置参数,通过argument方法进行设置,获取的值会传到毁掉函数的参数重,按照参数设置的顺序排列。可以通过arguments方法一次性设置多个参数,可以通过<>
设置必须按参数,通过[]
设置可选参数。
复杂的命令也可以通过构造函数创建,比如
import { program, Argument } from 'commander'
import { readFileSync } from 'fs'
import parse from 'parse-json'
const pkg = parse(readFileSync('package.json', 'utf-8'))
program
.name('fapi')
.description('Generate typeScript file by swagger json.')
.version(pkg.version)
.addArgument(new Argument('<size>', 'drink cup size').choices(['small', 'medium', 'large']))
.addArgument(new Argument('[timeout]', 'timeout in seconds').default(60, 'one minute'))
// 省略其他
})
子命令也是很常见的形式,比如docker run -d ......
中,docker是一个主命令,run就是一个子命令,commander也提供了这种能力。通过command方法设置子命令,子命令的配置方式和主命令基本一致
import { program } from 'commander'
import { readFileSync } from 'fs'
import parse from 'parse-json'
const pkg = parse(readFileSync('package.json', 'utf-8'))
program
.name('fapi')
.description('Generate typeScript file by swagger json.')
// 设置子命令
program
.command('generate')
.description('Generate files from swagger json.')
.argument('<url>', 'swagger.json url')
.option('-m, --mock <path>', 'generate mock files and save to <path>(need use fapi mock util).', '/src/mock')
.option('-s, --service <path>', 'generate request files and save to <path>.', '/src/service')
.action((url, options) => {
// generate exec
console.log('[ generate options ] >', options)
})
program.parse()
命令支持生命周期,通过hook
方法来进行绑定,支持的生命周期有
事件名称 | 触发时机 | 参数列表 |
---|---|---|
preAction , postAction |
本命令或其子命令的处理函数执行前/后 | (thisCommand, actionCommand) |
preSubcommand |
在其直接子命令解析之前调用 | (thisCommand, subcommand) |
program
.name('fapi')
.description('Generate typeScript file by swagger json.')
.version(pkg.version)
.option('-k, --hook')
.hook('preAction', (thisCommand, actionCommand) => {
console.log(`About to call action handler for subcommand: ${actionCommand.name()}`);
console.log('arguments: %O', actionCommand.args);
console.log('options: %o', actionCommand.opts());
}).action(() => {
// ......
})
选项
option
方法用于定义选项,语法为option(param[, description[, default]])
,设置选项名称时可以顺带设置别名,比如上面命令一节中的-c, --config
,在使用时可以通过两种形式使用,这也符合命令行工具的使用习惯。
选项有两种类型:布尔选项和带参数的选项,布尔选项仅用于开关,像上面的--config
就是一个布尔选项,在启用时会拿到如下options
$ node bin/fapi.js --config
[ fapi options ] > { config: true }
布尔类型的选项可以设置取反,使用§no-
开头的选项开启后会将值设为false,比如--no-cache
选项启用时会在options中将cache置为false。如果已经开启cache选项则取反不生效
带参数的选项就是可以添加参数的选项,比如平时开发时启动服务器用到的命令
serve -p 80
serve -p80
serve --port 80
serve --port=80
参数可以设置默认值、非必填参数(解析后的值可以是布尔类型也可以是参数类型),设置方式如下
program
.option('-d, --default <param>', 'option with default', 'defaultValue')
.option('-n, --not-require [param]', 'with not require param')
当必填参数没有设置时会出现如下报错
$ node bin/fapi.js generate -d
error: option '-d, --default <param>' argument missing
commander默认会帮助开发者自动生成一些帮助信息,比如选项的信息。在前一节我们已经看到了
--help
的输出,我们也可以通过代码输出help信息,相关方法如下:
- help():展示帮助信息并退出;
- outputHelp():展示帮助信息不退出;
- helpInformation():得到字符串形式的内建的帮助信息。