yargs 简易指南:撸一个CLI
Posted crper
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了yargs 简易指南:撸一个CLI相关的知识,希望对你有一定的参考价值。
前言
最近在写一个覆盖公司产品全站页面性能测试的项目,随着代码量逐渐上升,
单纯的脚本执行的姿势,使维护成本上去了,代码散落各地,调用要打一大串。
所以直接搞成CLI改善使用体验和降低维护成本。
这篇文章只聚焦CLI入口的姿势及经验分享。
成品图
![在这里插入图片描述](https://img-blog.csdnimg.cn/dac78e0df4994d999bd556b64b97a337.png)
![在这里插入图片描述](https://img-blog.csdnimg.cn/749b44bc00b24555a63b25e60aa328c6.png)
资料及思路
搞CLI的前提就是正确的解析终端命令行传递的命令及参数,
node生态里面,有挺多解析库的, 比如commander,minimist 这些,
但是有一个是我之前调研过感觉不错的,刚好这次可以用上,就是yargs;
前置知识储备
- 知道linux风格的命令行规范,比如可选参数,子命令等
- node解析参数的原理
采用的解析库
yargs : API风格是链式调用,具备完善的command
和复杂参数的组合,以及对应的hanlder
【钩子响应】,
有完整的typescript提示,在写的过程中,直接跳转看类型定义可以减少看文档的次数。
这玩意让CLI代码的可读性直接拉升,哪怕你有同名参数,但是结合不同的command
及handler
就可以很好的独立开来!
需求
- 伪CLI风格调用
- 不是真的发布CLI,结合package.json的
scripts
调用,适用于整个工程使用
- 不是真的发布CLI,结合package.json的
- 提供多功能命令【
command
】,独立不同功能- 命令及参数支持别名,类型定义,是否必填,描述等
- 参数错误捕获
- 参数的自定义校验及必填
- 命令执行过程中断
代码实现
接下来请看代码注释,更加清晰的介绍可以看下官网的API介绍,这里不过多介绍!
const yargs = require('yargs/yargs');
const process = require('process');
const hideBin = require('yargs/helpers');
const path = require('path');
const measureRun, measureRunAllModule = require('./run'); // 功能实现
const genData2File = require('./gen'); // 功能实现
const clean = require('./clean'); // 功能实现
// 检测终端输入的中断快捷键信号【cmd+c】,强行退出进程
process.on('SIGINT', () =>
process.kill(process.pid);
);
const argv = yargs(hideBin(process.argv))
.strict()// 严格模式,参数错误直接抛出异常
.command(
// 提供子命令
command: 'measure', // 子命令全称
aliases: ['m'], // 子命令别名
desc: '测量文件', // 子命令描述
builder: function (yargs)
// 此处返回命令参数组合
return yargs
.check((argv) =>
// .check支持手动校验接受的参数
if (argv.all) return true;
if (typeof argv.sourceFile !== 'string' || !argv.sourceFile)
throw new Error('CLI出错啦,源文件路径必须是字符串且不能为空!\\n');
return true;
)
.options(
// 子命令参数
sourceFile:
alias: 'path', // 子命令参数别名
describe: '测量路径', // 子命令参数描述
string: true, // 接受类型是字符串
,
recursive:
alias: 'r',
describe: '递归查找文件,一般用于测试整个模块使用',
boolean: true, // 接受类型是布尔值,默认会隐形转换
,
docker:
alias: 'd',
describe: '该参数启用的话,测量是基于docker运行【无头浏览器运行】,数据包含视频录制',
boolean: false,
,
open:
alias: 'o',
describe: '该参数启用的话,会尝试调用本地浏览器打开测量后的数据网页',
boolean: false,
,
'allow-ignore':
alias: 'keep',
describe: '该参数启用的话,忽略文件机制会生效',
boolean: true,
default: true,
,
'all-module':
alias: 'all',
describe: '该参数启用的话,会依次执行所有模块,最终输出所有模块报告',
boolean: false,
,
'result-dir-name':
describe: '该参数可以把报告数据源的归纳到[resultDirName参数]目录,一般结合all参数使用',
string: '',
,
number:
alias: 'n',
describe: '配置测试走几轮,精度会高一些,但是每轮测试的时长会增加',
number: true,
default: 1,
,
)
.usage('$0 measure <path>') // 辅助指南,终端输出的可以看到
.usage('$0 measure [--path=<path>] [--docker=<true|false>]')
.example([
// 辅助指南,终端输出的可以看到
['$0 measure "src/pages/desk/measures.js"', '测试该测量文件'],
['$0 m src/pages/desk/measures.js -r', '测试该测量文件及下层文件【递归往下找】'],
['$0 m src/pages/project/components/sprint/plan/measure.js --allow-ignore=false', '强行测量处于忽略清单的文件'],
]);
,
handler: function (argv)
// 响应句柄,这里处理参数通过校验后接收到的对象,然后你自己丢到你自己实现的功能函数引用即可!
// 支持async await , promise这类异步调用
if (argv.all)
measureRunAllModule();
else
const entryPath = path.resolve(process.cwd(), argv?.sourceFile);
const arg =
sourceFile: entryPath,
r: argv?.r ?? false,
n: argv?.n,
d: argv?.d,
open: argv?.o,
docker: argv?.docker,
allowIgnore: argv?.keep,
resultDirName: argv?.resultDirName,
;
measureRun(arg);
,
)
.command(
command: 'generate',
aliases: ['g'],
desc: '生成数据',
builder: function (yargs)
return yargs
.options(
format:
describe: '生成报表的数据格式',
array: true,
choices: ['json', 'excel'], // 数组支持多个值
default: 'excel', // 也能设置默认值
,
date:
describe: '指定日期的报表【检索数据源】返回,日期格式【2022-08-21 , 2022/08-21】都可以',
string: true,
default: null,
,
dateRangeType:
array: true,
choices: ['d', 'M', 'w', 'y'],
describe: '指定日期的报表检索区间,默认是传递日期的当天内\\n M 是月份,w 是周,y是年,d是当天',
default: 'd',
,
all:
alias: 'a',
describe: '检索所有数据源并生成报表[优先级比指定日期低,全量资源开销大]',
boolean: true,
default: false,
,
list:
alias: 'l',
describe: '检索所有数据源并在终端展示',
boolean: true,
default: false,
,
)
.usage('$0 g [--date] [--format=<json|excel>]')
.example([
['$0 generate', '执行数据源生成报表,默认就是格式就是excel,基于最近一周内最新的一份有效数据作为数据源'],
['$0 g --list', '检索所有数据源并在终端展示'],
['$0 g --format json ', '执行数据源生成报表,格式为json'],
['$0 g --date 2022-08-22 ', '检索特定日期当天内的数据源并生成报表'],
['$0 g --date 2022-08-22 --dateRangeType m', '检索特定日期当月内的数据源并生成报表'],
['$0 g --all ', '检索所有数据源并生成报表'],
]);
,
handler: (argv) =>
genData2File(
date: argv?.date,
all: argv?.all,
format: argv?.format,
dateRangeType: argv?.dateRangeType,
list: argv?.l,
);
,
)
.command(
command: 'clean',
aliases: ['c'],
desc: '删除数据源文件',
builder: function (yargs)
return yargs
.options(
src:
alias: 'path',
describe: '删除的资源路径',
string: true,
,
)
.demandOption(['src', 'path'], '请传入要删除的路径!相对于项目根路径,比如: sitespeed-result/docker-test/2022-08-22') // 强制某个参数为必填,不如check灵活
.usage('$0 clean --path ')
.example([
['$0 clean --path sitespeed-result/docker-test/2022-08-18/12-25-46_1580d24f-4d01-4155-ad22-bd3b8305f73d', '清除指定的数据源'],
]);
,
handler: async (argv) =>
clean(argv?.path);
,
)
.fail(function (msg, err, yargs)
// 此处捕获yargs执行异常或者抛出的异常【throw new Error】
console.error(yargs.help());
console.error('\\n\\n\\n=====命令执行错误,信息如下=====\\n\\n', msg);
process.exit(1);
)
.showHelpOnFail(false, '命令指定 --help 查看有效的选项') // 当命令执行错误的时候,自动调用一下帮助命令并输出到终端
.version(false) // cli版本设置,此处关闭
.wrap(null) // 设置为null,就是自适应,固宽的话,
.locale('zh_CN') // yargs提供多语言支持,配置对应地区,核心错误这些有对应的语言文本
.help('help', '查看命令行帮助').argv;
module.exports = argv;
官方还提供了从配置文件读取的操作,包括命令的智能推断等,这些没搞。
有兴趣的可以官网自行了解,并不难。
- http://yargs.js.org/docs/#api-reference-configkey-description-parsefn
- http://yargs.js.org/docs/#api-reference-getcompletionargs-done
package.json
"scripts":
"prepare": "husky install",
"commit": "git-cz",
"help": "npm run cli -- --help",
"cli": "node ./node-scripts/cli.js",
"m:all": "npm run cli -- m --all",
"m:dev": "npm run cli -- m --open ",
"m:docker": "npm run cli -- m --open -d",
"g": "npm run cli -- g ",
"clean": "npm run cli -- c"
,
这样,结合scripts
就能达到类似CLI的效果
总结
yargs我用下来觉得最大的亮点就是组织性很强,这样命令的维护成本会很低。
比如minimist 这种就只有纯粹的命令解析,所有判定逻辑需要自己去兜住,校验,同名参数隔离等!
有不对之处请留言,会及时修正,谢谢阅读。
以上是关于yargs 简易指南:撸一个CLI的主要内容,如果未能解决你的问题,请参考以下文章
我不断收到错误:找不到模块'webpack-CLI/bin/config-yargs'
如何修复:错误:找不到模块'webpack-cli/bin/config-yargs'?