一、commanderJs学习笔记

216 阅读8分钟

前言

最近封装了一个移动端上的模板,再想能不能像vue-cli一样,对项目模块的一些初始化操作、git代码提交的规范。经查询得知commanderJs主要用于node命令行的解决方法,对文档常用的属性方法进行的总结跟笔记。

正文

1.创建目录

mkdir module-cli
cd module-cli
mkdir example
npm init
npm install commander

2.api学习笔记

官方文档:点击访问

2.1选项

为简化使用,Commander 提供了一个全局对象program。
如果程序较为复杂,用户需要以多种方式来使用Commander,创建本地 Command 对象。
我们先来认识Commander的基础配置option。
创建一个option.js,执行node .\option.js -d --small -p test

const { program } = require('commander');
program
  .option('-d, --debug', 'output extra debugging')
  .option('-s, --small', 'small pizza size')
  .option('-p, --pizza-type <type>', 'flavour of pizza');

program.parse(process.argv);

const options = program.opts();
if (options.debug) console.log(options);
console.log('pizza details:');
if (options.small) console.log('- small pizza size');
if (options.pizzaType) console.log(`- ${options.pizzaType}`);

输出结果为:

{ debug: true, small: true, pizzaType: 'test' }
pizza details:
- small pizza size
- test

首先我们来看option这个函数入参的定义:

  1. 简写:例如-d-s-p,并且会做贪婪匹配,例如-dsp会同时匹配 -d -s -p
    全称:在简写的基础上可以更语义化命名,需使用--开头,如同简写一起时,需与简写空格分开。
    默认为布尔值,如命名开头为no,例 --no-debug,则默认值为true,调用时--no-debug则为false。
    声明结尾增加,如果在命令行中不指定具体的选项及参数,则会被定义为undefined,例--pizza-type ,命令行不指定选项或者不增加入参,则为undefined
    如声明结尾<number...>,在输入下一个选项前(---开头),用户输入的指令均会被视作变长参数,例.option('-n, --number <numbers...>', 'specify numbers'),调用命令时 -n 1 2 3,则该值为[ '1', '2', '3' ]
  2. 选项描述,说明该选项的具体用户,一般会在命令输出帮助时候(-h,-help)时,有所帮助
  3. 可接受一个正则、函数、或者默认值的入参
  4. 如第三参数为正则、函数,则第四位入参为默认值

以上基本满足比较简单的应用了,commander还提供复杂场景下的选项配置,例

  1. requiredOption函数,通过.requiredOption()方法可以设置选项为必填。必填选项要么设有默认值,要么必须在命令行中输入,对应的属性字段在解析时必定会有赋值。该方法其余参数与.option()一致。
  2. 直接构造Option对象,对选项进行更详尽的配置,通过new Option('-t, --timeout ', 'timeout in seconds')可配置选项模式,选项描述。后可通过链式调用
    default:(value: unknown, description?: string): this, 命令行未输入指令时给予默认值,入参为默认值及描述,描述会在help命令中输出
    preset:(arg: unknown): this,同上入参(预设值)。区别在于命令行要输入指令时才有效,如果指定不指定 [type]类型,则该指令值始终为预设值。
    conflicts:(names: string | string[]): this,接受一个字符或者字符数组的冲突指令名称入参,执行该指令时,如果命令行有冲突指令名称入参,则会抛出一个error的错误。
    implies:(optionValues: OptionValues): this,接受一个Record<string, any>的入参,为其他指令设置默认值。例如指令-a依赖-b指令的值,然而再命令行中并无输入-b,这时候就可以利用implies来给-b设置默认参
    env:(name: string): this,接受一个字符串入参,作为环境变量的key,再PORT=9001 node options-env.js命令执行时,会去取node前PORT=9001中的值。
    argParser:<T>(fn: (value: string, previous: T) => T): this,入参为一个函数,函数内接受当前value值,previous则为default方法内的默认值。
    makeOptionMandatory:(mandatory?: boolean): this,入参为布尔值,用来表示该选项是否必须有值。
    hideHelp:(hide?: boolean): this,入参为布尔值,用来表示该选项是否屏蔽help的输出。
    choices:(values: readonly string[]): this,接收一个字符串数组,用来指定该指令可接受哪一些值,非数组内的值会抛出错误,如不指定<type>类型则默认为布尔值,会一直报错。
    name:无入参,函数返回当前指令的name,长名称。
    attributeName:无入参,函数返回当前指令的驼峰式name,可用于对象,长名称。例如--disable-server则返回disableServer
    isBoolean:无入参,函数返回当前是布尔的值。
2.2 命令

通过.command().addCommand()可以配置命令,一种是命令配置函数来进行处理,另一种:是运行一个可执行文件。
.command有两个参数,第一个参数必填 空格分隔。第一个字符串为命令名称,紧随其后的为参数名定义,<>代表参数必选的,[]代表参数可选的,<key...>[key...]...代表变长参数,将后续参数汇总成一个,只能是最后一个参数。
.addCommand有两个参数,第一个参数为Command对象,通过new Command生成,第二个参数则为可选项,对象为hidden再help隐藏,isDefault默认命令,noHelp再help隐藏

2.2.1 命令参数

上诉我们已经知道配置一个命令了,现在我们设置命令的参数,一共有四种方式。

  1. .command方法入参时候,再命令名后空格设置,例 .command('init <a>[b][c...]'),对于执行可执行文件时,只能通过这种方法来设置参数。
  2. .argument方法接收两个入参,第一个为参数名称,同<>[]...规则,第二个则为参数描述。
  3. .arguments方法接受一个入参,为参数名称字符串列表,通过空格分隔,同<>[]...规则,无参数描述。
  4. addArgument方法接收一个Argument对象入参,通过new commander.Argument('<drink-size>', 'drink cup size')来传递,具体Argument的方法可参考Options对象的链式调用。
    default:(value: unknown, description?: string): this, 命令行未输入指令时给予默认值,入参为默认值及描述,描述会在help命令中输出
    argParser:<T>(fn: (value: string, previous: T) => T): this,入参为一个函数,函数内接受当前value值,previous则为default方法内的默认值。
    choices:(values: readonly string[]): this,接收一个字符串数组,用来指定该指令可接受哪一些值,非数组内的值会抛出错误,如不指定<type>类型则默认为布尔值,会一直报错。
    argRequired(): this,使参数必传。
    argOptional(): this,使参数可选。
2.2.2 独立可执行(子)命令

当我们想配置一个外部可执行文件时,我们可以再.command方法增加描述,则会被视为执行外部可执行。默认是需要 当前文件名 +子命令name,例如执行的文件是node index.js,子命令name为install,则会去寻找当前目录的index-install.js
1. executableFile作为.command的第三个入参,入参为ExecutableCommandOptions类型对象,可配置executableFile属性来匹配执行子命令的可执行文件地址,isDefault属性配置是否为默认子命令,hidden noHelp用来隐藏输出help信息。

2.2.3 命令的生命周期

可以在命令中设置钩子,做一些前置、后置的处理。
钩子函数支持async,相应的,需要使用.parseAsync代替.parse。一个事件上可以添加多个钩子

支持的事件有:

事件名称触发时机参数列表
preActionpostAction本命令或其子命令的处理函数执行前/后(thisCommand, actionCommand)
preSubcommand在其直接子命令解析之前调用(thisCommand, subcommand)

preAction、postAction对应的回调参数 thisCommand为当前挂载钩子的命令,actionCommand则是当前执行的命令。
preSubcommand对应的回调参数thisCommand为当前文件默认主命令,subcommand则为当前执行的子命令。

2.2.4 其他补充
  1. alias(alias: string): this:为命令设置别名,再自动生成帮助中只会显示第一个设置的别名
  2. name(str: string): this:设置当前命令名称
2.3 自定义帮助

由于官方文档描述的很详细了,此处不做阐述。

2.4 零碎知识
  1. .parse()和.parseasync() 是解析参数、命令必须调用的方法。
    第一个参数是要解析的字符串数组,也可以省略参数而使用process.argv。 如果参数遵循与 node 不同的约定,可以在第二个参数中传递from选项:
  • node:默认值,argv[0]是应用,argv[1]是要跑的脚本,后续为用户参数;
  • electronargv[1]根据 electron 应用是否打包而变化;
  • user:来自用户的所有参数。

例如:

program.parse(process.argv); // 指明,按 node 约定
program.parse(); // 默认,自动识别 electron
program.parse(['-f', 'filename'], { from: 'user' });
  1. 重写退出,再调用program.parse函数中包一层try...catch...,可以再catch中捕获CommanderError错误信息,主要是再命令exit之前拦截,再exit之前打印的错误信息不会重写。
  2. 自定义事件监听,再program.on可以增加对应option:key或者command:key对应的回调事件,回调事件中使用this来获取program相关信息;例: program.on(option:verbose, function () { process.env.VERBOSE = this.opts().verbose; });
2.5 调试
  1. 运行ts文件,需使用ts-node,执行命令为node -r ts-node/register pm.ts