创建命令
MixCli基于commander,因此可以使用commander的所有功能,在commander的基础上作了增强,提供了更加友好的命令行开发体验。
创建命令
每一个MixCli命令原则上建议一个命令对应一个js文件,且该js文件导出一个返回MixCommand或MixCommand[]的函数,如下所示:
const {MixCommand} = require("mixcli");
/**
* @param {import('mixcli').MixCli} cli
*/
module.exports = (cli)=>{
const devCommand = new MixCommand();
devCommand
.name("dev")
.arguments("<name>","项目名称")
.option("-p,--port <port>","指定端口号",3000)
.option("-d,--debug" ,"调试模式",{ default:true,prompt:true })
.option("-h,--host <host>","指定主机名",{default:"localhost",prompt:true})
.action(async (name,options){
// 此处是命令的具体实现
})
// 返回要创建的命令
return devCommand;
}- 注意:
MixCommand继承自commander.Command,因此可以使用commander的所有功能,MixCommand.option用来声明命令选项,其参数与commander.Command.option基本一致,并做了少量扩展。
命令选项
MixCommand继承自commander.Command,声明命令选项的方式与commander一致。
为命令增加选项的方法,在commander中提供了两种方式:
program
// 无choices
.option('-d, --drink <size>', 'drink size')
// 需要使用choices时
.addOption(new Option('-d, --drink <size>', 'drink size').choices(['small', 'medium', 'large']))以上两种方式同样支持,详见commander介绍。
而在MixCli中,可以简化为直接这样定义:
const { MixCommand } = require("mixcli");
const devCommand = new MixCommand();
devCommand.option("-m,--mode <mode>","指定模式",{choices:["development","production","test","debug"]})MixCommand继承自commander.Command,因此可以使用commander的所有功能,MixCommand.option用来声明命令选项,其参数与commander.Command.option一致,但是MixCommand.option提供了更加友好的方式来定义命令选项。
class MixCommand extends Command{
option(flags: string, description?: string | undefined,defaultValue?:any ): this
option(flags: string, description?: string | undefined,options?:FlexOptionParams ): this{
}FlexOptionParams定义如下:
export interface FlexOptionParams extends IPromptableOptions{
required?: boolean; // A value must be supplied when the option is specified.
optional?: boolean; // A value is optional when the option is specified.
default?:PromptParamDefaultValue // 默认值
choices?:string[] // 选项值的可选值
//交互提示信息配置
prompt?:InputPromptParam
validate?:(value: any) => boolean
defaultDescription?:string // 默认值的描述
conflicts?:string | string[]
env?:string
argParser?:<T>(value: string, previous: T) => T
hideHelp?:boolean
mandatory?: boolean
implies?:{[key:string]:any}
}MixCommand的增强体现在:
- 增加了参数
prompt,用来控制命令选项使用哪一种交互提示信息。 - 重载了
Command的option方法,通过FlexOptionParams参数来声明所有命令选项的配置参数。
命令函数
MixCommand继承自commander.Command,因此可以使用commander的所有功能,MixCommand.action用来声明命令函数,其参数与commander.Command.action一致,但是MixCommand.action扩展支持多个命令函数。
基本使用
可以像commander/Command.action一样通过action方法来声明命令函数,即支持同步函数,也支持异步函数。
const {MixCommand} = require("mixcli");
module.exports = (cli)=>{
const myCommand = new MixCommand("init");
myCommand
.option("-t,--template <value>","指定模板",
{choices:["react","vue","angular"]}
)
// 声明一个同步命令函数
.action((options)=>{
console.log("同步命令函数")
})
// 声明一个异步命令函数
.action(async (options)=>{
console.log("异步命令函数")
})
})
}命令链路
MixCommand.action最大的增强在于,支持声明多个命令函数,形成执行链。
- 命令列表
MixCommand内部维护了一个action函数数组(通过MixCommand.actions可以访问)。
当每次执行MixCommand.action时,会将action函数添加到数组中。然后当运行命令时,会按照数组中的顺序依次执行。
这是MixCommand与commander.Command最大的不同。
因此基于此特性,在上面的init命令中,事实上是定义了两个action函数,当执行mycli init时分别执行了两个action函数。
- 中断执行命令
多次执行MixCommand.action时,会创建一个action函数数组,这些action会依次顺序执行。
action函数可以显式返回BREAK来中断后续action的执行。
const {MixCommand,BREAK} = require("mixcli");
module.exports = (cli)=>{
const myCommand = new MixCommand("init");
myCommand
.option("-t,--template <value>","指定模板",
{choices:["react","vue","angular"]
}).action((options)=>{
return BREAK
})
.action((options)=>{
// 此函数永远也会不得到执行,因此上一个action返回了BREAK
})
})
}- 增强模式
除了.action(async (arg1,arg2,options)=>{...})的常规签名方式外,当也可以采用增强模式。
const { MixCommand,BREAK } = require("mixcli");
module.exports = (cli)=>{
const myCommand = new MixCommand("init");
myCommand
.option("-t,--template <value>","指定模板",
{choices:["react","vue","angular"]
})
.action(async ({args,options,value,command})=>{
// args: 命令行参数
// options: 命令选项
// value: 上一个命令执行的值
// command: 当前命令对象
})
},{
at:'append' //'replace' | 'preappend' | 'append' | number
enhance:true
})
})
}- 当在
action方法的第二个参数中传入enhance=true,此时代表采用增强方式来定义命令函数。 - 增强模式下,命令函数签名为
({args,options,value,command}),其中args是命令行参数数组,options为命令选项,value为上一个命令的返回值,command指向当前命令对象。 - 启用增强模式后,可以通过
at参数来指定创建的action函数在actions函数数组中的位置,这可以决定所声明的action函数的执行时机。at取值可以是:append: 默认值,追加到最后面replace: 替换所有已经注册的action函数preappend: 添加到数组最前面,这意味着该action函数会先执行。number: 代表该action函数注册到actions数组中的位置。
扩展命令
在开发命令行应用,除了可以新增加命令外,还可以扩展已存有的命令。
查找命令
在进行命令扩展时,道德需要查找存在的命令,MixCli提供了两种方式来查找命令:
MixCli.find(命令名称):以异步的方式来获取命令。MixCli.get(命令名称):以同步命令名称来获取命令,如果命令不存在,则抛出undefined。
MixCli.find(命令名称)或MixCli.get(命令名称)中的支持获取到子命令。
比如,有如下命令:
const {MixCommand} = require("mixcli");
module.exports = (cli)=>{
cli.get("dev") // 获取dev命令对象
cli.get("dev.app") // 获取dev命令的子命令app对象
}find和get方法的区别在于:
find是异步方法,返回一个Promise,而get是同步方法- 当
MixCli在检索include参数指定的扩展包并加裁时,由于扩展包加载顺序的问题,get方法获取命令时要求命令必须是已经前置加载的。而find方法则不受此限制。所以大部份情况下,建议采用find方法来获取命令。
修改命令选项
可以修改已存在的命令选项。
const {MixCommand} = require("mixcli");
module.exports = (cli)=>{
cli.find("dev").then((devCommand)=>{
// 获取到当前命令的选项
const option = devCommand.options.find((option)=>option.name() === "mode");
// 修改命令选项
option.required = true;
// 新选取
option.choices(["development","production","test","debug"])
option.choices([
{ title:"开发",value:"development"},
{ title:"生产",value:"production"},
{ title:"测试",value:"test"},
{ title:"调试",value:"debug"}])
option.addChoice("development")
option.addChoice({title:'测试',value:"test"})
option.clearChoice()
option.removeChoice("test")
})
}更多说明见这里
增加命令选项
为已存在的命令增加命令选项。
const {MixCommand} = require("mixcli");
module.exports = (cli)=>{
cli.find("dev").then((devCommand)=>{
devCommand.option("-p,--port <port>","指定端口号",3000)
})
}增加子命令
为已存在的命令增加子命令。
const {MixCommand} = require("mixcli");
module.exports = (cli)=>{
// 1. 获取已经存在的命令
cli.find("dev").then((devCommand)=>{
const appCommand = new MixCommand();
appCommand
.name("app")
.arguments("<name>","项目名称")
.option("-p,--port <port>","指定端口号",3000)
.option("-t,--template <value>","开发模板"})
.action(async (name,options)=>{
// 此处是命令的具体实现
})
devCommand.addCommand(appCommand);
})
}命令处理函数
当为已经存在的命令增加了选项时,一般需要增加相应的处理函数。
如果是新增加最子命令,比较简单,只需要指定.action(fn)即可。
如果是新增加了命令选项,则一般会涉及要对已有的命令的action进行扩展。
比如上例中,我们为dev增加了一个选项-p,--port <port>,一般情况下,我们需要为原有的dev命令增加相应的处理逻辑。
- 方法1:增加
before/after函数
在dev命令中增加before和after两个hook函数,分别在执行dev的action函数之前和之后执行。
const {MixCommand} = require("mixcli");
module.exports = (cli)=>{
cli.find("dev").then((devCommand)=>{
devCommand
.option("-p,--port <port>","指定端口号",3000)
.before((options)=>{
// ...
})
.after(()=>{
// ...
})
})
}- 方法2:扩展
action函数
可以利用命令链路的特性实现同一个命令下的多个action函数的执行。