创建如下目录结构,mycli 是根目录文件名:
mycli
└─bin
└─cli.js
使用 npm init -y 初始化 package.json:
# 根目录下执行
npm init -y
初始化完成后的 package.json 的配置和平常的对比,多了一个 bin 的配置。
意思是,定义了一个叫 mycli 的命令,执行这个命令会运行 bin/cli.js 这个文件。
{
more···
"bin": {
"mycli": "bin/cli.js"
},
more···
}
使用 npm link 命令,让 mycli 成为一个全局命令。
给 bin/cli.js 添加一些代码:
#! /usr/bin/env node
console.log("hello");
现在,使用 mycli 命令,会打印出 hello。
修改 bin/cli.js 的代码为:
#! /usr/bin/env node
console.log(process.argv);
process.argv 可以获取执行脚本时的命令行参数,效果如下:
这次执行 mycli 时增加了 --help 参数。
打印的结果里,有三个参数,第一个是执行脚本的 node.exe 程序,第二个是脚本所在的位置,这两个是固定参数。
第二个之后的全都是用户传入的参数,可以看到第三个就是传入的 --help 参数,在编写逻辑时,可以通过下标获取对应的参数。
命令参数处理
由于自己手动编码处理命令参数特别麻烦,需要考虑很多情况,比如,用户使用了哪些参数,没有使用哪些参数。
此时,借助别人封装好的方法来处理,是更为明智的选择。使用 commander 可以优雅的处理命令参数,安装方法如下:
# 根目录下执行
npm install commander
安装完成后,修改 bin/cli.js 代码:
#! /usr/bin/env node
const { program } = require("commander");
program.parse();
虽然只添加了两行代码,但是我们的 cli 工具却多了不止两个功能,看一下运行示例:
添加 --help 之后,会打印出我们的 cli 可以使用哪些参数的提示。commander 会帮我们实现一个默认的 --help 执行逻辑。
当使用未定义的 -f 参数时,commander 也会自动的发出没有这个参数选项的错误提示。
使用 program.option() 方法,给 cli 添加新的可执行参数。
#! /usr/bin/env node
const { program } = require("commander");
program.option("-f, --first <char>", "创建的第一个参数");
program.parse();
现在,可以向我们的 cli 传入 -f 或 --firsr 参数。<char> 是这个命令对应传入的值,尖括号代表使用这个命令时,必须传入的参数。
可以看到,现在 --help 多出了 -f 命令相关的提示。使用 -f 参数,如果不传 <char>,会输出报错提示,只有输入正确的参数和值才能正常执行。
处理自定义指令选项
在使用 vue-cli 这个命令行工具时,可以使用 vue create myproject 这样的命令创建 vue 项目。下边使用 commander 实现一个类似这样的指令。
#! /usr/bin/env node
const { program } = require("commander");
program.option("-f, --first <char>", "创建的第一个参数");
program
.command("create <project> [other...]")
.alias("crt")
.description("创建项目")
.action((project, args) => {
console.log(project);
console.log(args);
});
program.parse();
这里使用了一个 program.command() 的方法,这个方法可以链式调用。
command()规定指令的名字和传入参数,<project>是添加的必传参数,[other...]是其他参数。alias()给create指令增加一个别名。description()给指令添加--help时的提示内容。action()传入一个函数。执行指令时,会调用这个函数。
测试一下这个新参数:
测试通过,上方的设置均正常。
逻辑代码模块化拆分
把全部代码逻辑写在一个 bin/cli.js 文件夹内是不太优雅的,当需要实现的功能变多之后,代码会变得非常多且难以阅读,得对代码拆分优化一下。
拆分后的代码目录结构如下:
mycli
├─bin
│ └─cli.js
├─lib
│ └─core
│ ├─action.js
│ ├─command.js
│ └─help.js
├─package-lock.json
└─package.json
bin/cli.js:
#! /usr/bin/env node
const { program } = require("commander");
const myHelp = require("../lib/core/help");
const myCommand = require("../lib/core/command");
myHelp(program);
myCommand(program);
program.parse();
lib/core/help.js:
const myHelp = function (program) {
program.option("-f, --first <char>", "创建的第一个参数");
};
module.exports = myHelp;
lib/core/command.js:
const myAction = require("./action");
const myCommand = function (program) {
program
.command("create <project> [other...]")
.alias("crt")
.description("创建项目")
.action(myAction);
};
module.exports = myCommand;
lib/core/action.js:
const myAction = function (project, args) {
console.log(project);
console.log(args);
};
module.exports = myAction;
拆分之后,记得测试是否还能正常执行 cli 相关命令。
命令行问答交互
借助 Inquirer.js 可以快速的实现命令行问答交互,使用下方命令安装:
# 根目录下执行
npm install inquirer@8
修改 lib/core/action.js 代码:
let inquirer = require("inquirer");
const myAction = function (project, args) {
inquirer
.prompt([
{
type: "list",
name: "food",
choices: ["苹果", "梨", "橙子"],
message: "你喜欢吃下面那种水果",
},
])
.then((answer) => {
console.log(answer);
});
};
module.exports = myAction;
当我们执行 mycli create xxx 时,就会弹出一个命令行问答交互,这里实现了一个单选。
type是交互类型,有输入、单选、多选等,具体请查看文末参考链接的文档。name是用户提交答案的key。choices是选项值。message是你提出的问题。
当用户选择完之后,使用 then() 方法继续编写相关逻辑,answer 为回答结果。可以看出用的是 Promise 语法,用 async/await 来接收 answer 也是 ok 的。
等待提示交互
在使用 cli 时,会出现一些等待过程,比如,下载一些数据包。在这个过程中,需要给用户一些有好交互,让用户知道我们的 cli 还在正常工作,并不是挂了。
使用 ora 这个第三方模块可以实现等待提示交互,安装方法如下:
# 根目录下执行
npm install ora@5
修改 lib/core/action.js 代码:
const inquirer = require("inquirer");
const ora = require("ora");
const myAction = function (project, args) {
inquirer
.prompt([
{
type: "list",
name: "food",
choices: ["苹果", "梨", "橙子"],
message: "你喜欢吃下面那种水果",
},
])
.then((answer) => {
const spinner = ora();
spinner.start();
spinner.text = "正在执行中...";
setTimeout(() => {
spinner.succeed("结束");
}, 3000);
});
};
module.exports = myAction;
测试一下效果:
- 引入
ora。 - 执行
ora()方法,生成一个spinner。 - 执行
spinner.start()开始执行等待交互效果。 spinner.text设置等待时显示的文本。- 执行
spinner.succeed()、spinner.fail()、spinner.warn()、spinner.info()等方法,显示不同的结束效果,具体看文末参考文档。
命令行样式输出
许多命令行工具在执行的时候 console.log() 打印的文本都是有颜色的。
使用 chalk 这个库可以快速给我们的 console.log() 增加颜色样式,安装命令如下:
# 根目录下执行
npm install chalk@4
修改 lib/core/action.js 代码:
const inquirer = require("inquirer");
const ora = require("ora");
const chalk = require("chalk");
const myAction = function (project, args) {
inquirer
.prompt([
{
type: "list",
name: "food",
choices: ["苹果", "梨", "橙子"],
message: "你喜欢吃下面那种水果",
},
])
.then((answer) => {
const spinner = ora();
spinner.start();
spinner.text = "正在执行中...";
setTimeout(() => {
spinner.succeed("结束");
console.log(chalk.blue.bgRed.bold("Hello world!"));
}, 3000);
});
};
module.exports = myAction;
执行效果如下:
- 引入
chalk。 - 使用
chalk.blue.bgRed.bold()等方法添加样式,具体看文末参考文档。
总结
虽然这个 cli 并没有实现什么具体功能,但是 cli 的大部分公共需求已经解决,通过这个流程,可以做出自己喜欢的 cli 工具。