Skip to content

创建命令

MixCli基于commander,因此可以使用commander的所有功能,在commander的基础上作了增强,提供了更加友好的命令行开发体验。

创建命令

每一个MixCli命令原则上建议一个命令对应一个js文件,且该js文件导出一个返回MixCommandMixCommand[]的函数,如下所示:

js
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;
}
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中提供了两种方式:

js
program 
    // 无choices 
    .option('-d, --drink <size>', 'drink size')    
    // 需要使用choices时     
    .addOption(new Option('-d, --drink <size>', 'drink size').choices(['small', 'medium', 'large']))
program 
    // 无choices 
    .option('-d, --drink <size>', 'drink size')    
    // 需要使用choices时     
    .addOption(new Option('-d, --drink <size>', 'drink size').choices(['small', 'medium', 'large']))

以上两种方式同样支持,详见commander介绍。

而在MixCli中,可以简化为直接这样定义:

js
const { MixCommand } = require("mixcli");
const devCommand = new MixCommand();
    devCommand.option("-m,--mode <mode>","指定模式",{choices:["development","production","test","debug"]})
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提供了更加友好的方式来定义命令选项。

ts
class MixCommand extends Command{
    option(flags: string, description?: string | undefined,defaultValue?:any ): this
    option(flags: string, description?: string | undefined,options?:FlexOptionParams ): this{
}
class MixCommand extends Command{
    option(flags: string, description?: string | undefined,defaultValue?:any ): this
    option(flags: string, description?: string | undefined,options?:FlexOptionParams ): this{
}

FlexOptionParams定义如下:

ts
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}  
}
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,用来控制命令选项使用哪一种交互提示信息。
  • 重载了Commandoption方法,通过FlexOptionParams参数来声明所有命令选项的配置参数。

命令函数

MixCommand继承自commander.Command,因此可以使用commander的所有功能,MixCommand.action用来声明命令函数,其参数与commander.Command.action一致,但是MixCommand.action扩展支持多个命令函数。

基本使用

可以像commander/Command.action一样通过action方法来声明命令函数,即支持同步函数,也支持异步函数。

ts
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("异步命令函数")
        })    
    })
}
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函数添加到数组中。然后当运行命令时,会按照数组中的顺序依次执行。

这是MixCommandcommander.Command最大的不同。

因此基于此特性,在上面的init命令中,事实上是定义了两个action函数,当执行mycli init时分别执行了两个action函数。

  • 中断执行命令

多次执行MixCommand.action时,会创建一个action函数数组,这些action会依次顺序执行。

action函数可以显式返回BREAK来中断后续action的执行。

ts
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
        })  
    })
}
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)=>{...})的常规签名方式外,当也可以采用增强模式

ts
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
        })  
    })
}
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(命令名称)中的支持获取到子命令。

比如,有如下命令:

js
const {MixCommand} = require("mixcli");
module.exports = (cli)=>{    
    cli.get("dev")      // 获取dev命令对象
    cli.get("dev.app")  // 获取dev命令的子命令app对象
}
const {MixCommand} = require("mixcli");
module.exports = (cli)=>{    
    cli.get("dev")      // 获取dev命令对象
    cli.get("dev.app")  // 获取dev命令的子命令app对象
}

findget方法的区别在于:

  • find是异步方法,返回一个Promise,而get是同步方法
  • MixCli在检索include参数指定的扩展包并加裁时,由于扩展包加载顺序的问题,get方法获取命令时要求命令必须是已经前置加载的。而find方法则不受此限制。所以大部份情况下,建议采用find方法来获取命令。

修改命令选项

可以修改已存在的命令选项。

js
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)=>{   
        // 获取到当前命令的选项
        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")
    }) 
}

更多说明见这里

增加命令选项

为已存在的命令增加命令选项。

js
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)=>{    
    cli.find("dev").then((devCommand)=>{   
       devCommand.option("-p,--port <port>","指定端口号",3000)         
    }) 
}

增加子命令

为已存在的命令增加子命令。

js
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);
    })
 
}
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命令中增加beforeafter两个hook函数,分别在执行devaction函数之前和之后执行。

js
const {MixCommand} = require("mixcli");
module.exports = (cli)=>{    
    cli.find("dev").then((devCommand)=>{   
       devCommand
        .option("-p,--port <port>","指定端口号",3000)   
        .before((options)=>{
            // ...
        })
        .after(()=>{
            // ...
        })
    }) 
}
const {MixCommand} = require("mixcli");
module.exports = (cli)=>{    
    cli.find("dev").then((devCommand)=>{   
       devCommand
        .option("-p,--port <port>","指定端口号",3000)   
        .before((options)=>{
            // ...
        })
        .after(()=>{
            // ...
        })
    }) 
}
  • 方法2:扩展action函数

可以利用命令链路的特性实现同一个命令下的多个action函数的执行。