创建如下目录结构,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
工具。