前期准备
新建一个文件夹 , 打开终端输入 npm init xxx -y ,
在package.json 中添加可执行命令
{ "bin":{ "jef":"./index.js" } }
创建执行文件 index.js, 添加 #!/usr/bin/env node
这时候就可以通过在控制台输入 jef 执行index.js文件。
简单使用
#!/usr/bin/env node
const { Command } = require("commander");
const program = new Command();
program
.name("string-split")
.description("cli to split string")
.version("0.0.1");
program
.command("split")
.description("split a as string")
.argument("<string>", "string to split")
.option("-s, --separator <char>", "separator character")
.action((str, options)=>{
const { separator = "," } = options;
console.log(`the result is:`, str.split(separator));
});
program.parse();
使用 .option() 方法定义 option
// 参数
option(flags: string, description?: string, defaultValue?: string | boolean | string[]): this;
option<T>(flags: string, description: string, fn: (value: string, previous: T) => T, defaultValue?: T): this;
/** @deprecated since v7, instead use choices or a custom function */
option(flags: string, description: string, regexp: RegExp, defaultValue?: string | boolean | string[]): this;
flag可以有简写和完整写法, 使用 逗号, 或者 空格 或者 | 隔开。
option 的值可以是 布尔值 或者 字符串 或者 数组。
默认是布尔值。 使用<xxx>包裹值是字符串, 使用[xxx]包裹 可以是布尔值,也可以是字符串。
<xxx...> , [xxx...] option的值是一个数组。
解析过的 option 会传入action的处理函数中, 也可以通过调用 command 实例的 opts 方法访问。
// 使用 --no-xxx 设置 xxx 默认值 true
program
.command("chip")
.option("--no-sauce","remove sauce")
.option("--cheese","cheese flavour")
.action((_, options)=>{
console.log(options.opts());
// input : jef chip
// { sauce: true }
});
也可以通过 addOption 方法 和 Option类 添加option.
const { Command, Option } = require("commander");
const program = new Command();
program
.addOption(new Option("-s, --secret").hideHelp())
通过 command 方法添加指令
// 参数
command(nameAndArgs: string, opts?: CommandOptions): ReturnType<this['createCommand']>;
连续调用command 添加副指令。
可以在指令名的后面添加指令的参数 ( <xxx>, [xxx] ), 也可以通过 argument 方法添加指令的参数。
<xxx...> [xxx...]的作用和options中的类似。
action 方法调用处理函数
// 参数
action(fn: (...args: any[]) => void | Promise<void>): this;
使用异步的处理函数时, 调用program的 parseAsync方法。
async function run()
async function main() {
program
.command('run')
.action(run);
await program.parseAsync(process.argv);
}
使用version 方法指定版本。
program.version("0.0.1")
// input: jef -V
// 0.0.1
帮助信息自动生成, 通过 -h ,--help 指令展示。
也可以调用 addHelpText 方法在添加信息。
type AddHelpTextPosition = 'beforeAll' | 'before' | 'after' | 'afterAll';
addHelpText(position: AddHelpTextPosition, text: string): this;
addHelpText(position: AddHelpTextPosition, text: (context: AddHelpTextContext) => string): this;
https://github.com/tj/commander.js
简单强大的命令行字符串解析工具。和commander.js 很像。
用法查看官方文档
// 内部一些函数
const removeBrackets = (v: string) => v.replace(/[<[].+/, '').trim()
// 提取字符串中 [ 或者 < 之前的内容
// "abc [sd] sfsf" => abc , "ccc <ds>ffs" => ccc
内部有三个主要的类
CAC , Command 和 Option
import CAC from './CAC'
import Command from './Command'
const cac = (name = '') => new CAC(name)
export default cac
export { cac, CAC, Command }
// 简单用法
const cli = cac("xxx");
cli
.command('rm <dir>', 'Remove a dir')
.option('-r, --recursive', 'Remove recursively')
.action((dir, options) => {
console.log('remove ' + dir + (options.recursive ? ' recursively' : ''))
})
cli.parse();
// cli.command 方法返回 Command 实例
class CAC extends EventEmitter {
/**
* 省略代码
*/
command(rawName: string, description?: string, config?: CommandConfig) {
const command = new Command(rawName, description || '', config, this)
command.globalCommand = this.globalCommand
this.commands.push(command)
return command
}
}
class Command {
/**
* 省略代码
*/
// command 类的 option 方法
option(rawName: string, description: string, config?: OptionConfig) {
const option = new Option(rawName, description, config)
this.options.push(option)
return this
}
}
// option 类 实现
class Option {
/** Option name */
name: string
/** Option name and aliases */
names: string[]
isBoolean?: boolean
// `required` will be a boolean for options with brackets
required?: boolean
config: OptionConfig
negated: boolean
constructor(
public rawName: string,
public description: string,
config?: OptionConfig
) {
this.config = Object.assign({}, config)
// You may use cli.option('--env.* [value]', 'desc') to denote a dot-nested option
rawName = rawName.replace(/\.\*/g, '')
this.negated = false
this.names = removeBrackets(rawName)
.split(',')
.map((v: string) => {
let name = v.trim().replace(/^-{1,2}/, '')
if (name.startsWith('no-')) {
this.negated = true
name = name.replace(/^no-/, '')
}
return camelcaseOptionName(name)
})
.sort((a, b) => (a.length > b.length ? 1 : -1)) // Sort names
// Use the longest name (last one) as actual option name
this.name = this.names[this.names.length - 1]
if (this.negated && this.config.default == null) {
this.config.default = true
}
if (rawName.includes('<')) {
this.required = true
} else if (rawName.includes('[')) {
this.required = false
} else {
// No arg needed, it's boolean flag
this.isBoolean = true
}
}
}