Commander.js
Commander.js
Author: huyikai
Commander.js 是一个 Node.js 命令行界面的解决方案,用于解析命令行参数、显示错误信息并实现一个有帮助的系统。它提供了丰富的选项定义和解析功能,以及对子命令的支持。
安装
快速开始
编写代码来描述你的命令行界面。
Commander 负责将参数解析为选项和命令参数,为问题显示使用错误,并实现一个有帮助的系统。
Commander 是严格的,并且会针对无法识别的选项显示错误。
两种最常用的选项类型是布尔选项,和从参数中获取值的选项。
示例代码:split.js
这是一个使用子命令并带有帮助描述的更完整的程序。在多命令程序中,每个命令(或命令的独立可执行文件)都有一个操作处理程序。
示例代码:string-util.js
更多示例可以在 examples 目录中找到。
声明 program 变量
为简化使用,Commander 提供了一个全局对象。本文档的示例代码均按此方法使用:
如果程序较为复杂,用户需要以多种方式来使用 Commander,如单元测试等。创建本地 Command 对象是一种更好的方式:
选项
Commander 使用.option()
方法来定义选项,同时可以附加选项的简介。每个选项可以定义一个短选项名称(-后面接单个字符)和一个长选项名称(—后面接一个或多个单词),使用逗号、空格或|
分隔。
解析后的选项可以通过Command
对象上的.opts()
方法获取,同时会被传递给命令处理函数。
对于多个单词的长选项,选项名会转为驼峰命名法(camel-case),例如--template-engine
选项可通过program.opts().templateEngine
获取。
选项及其选项参数可以用空格分隔,也可以组合成同一个参数。选项参数可以直接跟在短选项之后,也可以在长选项后面加上 =
。
--
可以标记选项的结束,后续的参数均不会被命令解释,可以正常使用。
默认情况下,选项在命令行中的顺序不固定,一个选项可以在其他参数之前或之后指定。
当.opts()
不够用时,还有其他相关方法:
.optsWithGlobals()
返回合并的本地和全局选项值
.getOptionValue()
和.setOptionValue()
操作单个选项的值
.getOptionValueSource()
和.setOptionValueWithSource()
包括选项值的来源
常用选项类型,boolean 型选项和带参数选项
有两种最常用的选项,一类是 boolean 型选项,选项无需配置参数,另一类选项则可以设置参数(使用尖括号声明在该选项后,如--expect <value>
)。如果在命令行中不指定具体的选项及参数,则会被定义为undefined
。
示例代码:options-common.js
多个布尔短选项可以在破折号之后组合在一起,并且可以跟一个取值的单一选项。
例如 -d -s -p cheese
可以写成 -ds -p cheese
甚至 -dsp cheese
。
具有预期选项参数的选项是贪婪的,并且无论值如何,都会消耗参数。
所以 --id -xyz
读取 -xyz
作为选项参数。
通过program.parse(arguments)
方法处理参数,没有被使用的选项会存放在program.args
数组中。该方法的参数是可选的,默认值为process.argv
。
选项的默认值
选项可以设置一个默认值。
示例代码:options-defaults.js
其他的选项类型,取反选项,以及可选参数的选项
可以定义一个以no-
开头的 boolean 型长选项。在命令行中使用该选项时,会将对应选项的值置为false
。当只定义了带no-
的选项,未定义对应不带no-
的选项时,该选项的默认值会被置为true
。
如果已经定义了--foo
,那么再定义--no-foo
并不会改变它本来的默认值。
示例代码:options-negatable.js
选项的参数使用方括号声明表示参数是可选参数(如--optional [value]
)。该选项在不带参数时可用作 boolean 选项,在带有参数时则从参数中得到值。
示例代码:options-boolean-or-value.js
带有可选选项参数的选项不是贪婪的,并且会忽略以破折号开头的参数。因此对于--id -5
,id
表现为布尔选项,但如果需要,你可以使用组合形式,例如 --id=-5
。
关于可能有歧义的用例,请见可变参数的选项。
必填选项
通过.requiredOption()
方法可以设置选项为必填。必填选项要么设有默认值,要么必须在命令行中输入,对应的属性字段在解析时必定会有赋值。该方法其余参数与.option()
一致。
示例代码:options-required.js
变长参数选项
定义选项时,可以通过使用...
来设置参数为可变长参数。在命令行中,用户可以输入多个参数,解析后会以数组形式存储在对应属性字段中。在输入下一个选项前(-
或--
开头),用户输入的指令均会被视作变长参数。与普通参数一样的是,可以通过--
标记当前命令的结束。
示例代码:options-variadic.js
关于可能有歧义的用例,请见可变参数的选项。
版本选项
.version()
方法可以设置版本,其默认选项为-V
和--version
,设置了版本后,命令行会输出当前的版本号。
版本选项也支持自定义设置选项名称,可以在.version()
方法里再传递一些参数(长选项名称、描述信息),用法与.option()
方法类似。
其他选项配置
大多数情况下,选项均可通过.option()
方法添加。但对某些不常见的用例,也可以直接构造Option
对象,对选项进行更详尽的配置。
示例代码:options-extra.js, options-env.js, options-conflicts.js, options-implies.js
自定义选项处理
选项的参数可以通过自定义函数来处理,该函数接收两个参数,即用户新输入的参数值和当前已有的参数值(即上一次调用自定义处理函数后的返回值),返回新的选项参数值。
自定义函数适用场景包括参数类型转换,参数暂存,或者其他自定义处理的场景。
可以在自定义函数的后面设置选项参数的默认值或初始值(例如参数用列表暂存时需要设置一个初始空列表)。
示例代码:options-custom-processing.js
命令
通过.command()
或.addCommand()
可以配置命令,有两种实现方式:为命令绑定处理函数,或者将命令单独写成一个可执行文件(详述见后文)。子命令支持嵌套(示例代码)。
.command()
的第一个参数为命令名称。命令参数可以跟在名称后面,也可以用.argument()
单独指定。参数可为必选的(尖括号表示)、可选的(方括号表示)或变长参数(点号表示,如果使用,只能是最后一个参数)。
使用.addCommand()
向program
增加配置好的子命令。
例如:
使用.command()
和addCommand()
来指定选项的相关设置。当设置hidden: true
时,该命令不会打印在帮助信息里。当设置isDefault: true
时,若没有指定其他子命令,则会默认执行这个命令(样例)。
命令参数
如上所述,子命令的参数可以通过.command()
指定。对于有独立可执行文件的子命令来说,参数只能以这种方法指定。而对其他子命令,参数也可用以下方法。
在Command
对象上使用.argument()
来按次序指定命令参数。该方法接受参数名称和参数描述。参数可为必选的(尖括号表示,例如<required>
)或可选的(方括号表示,例如[optional]
)。
示例代码:argument.js
在参数名后加上...
来声明可变参数,且只有最后一个参数支持这种用法。可变参数会以数组的形式传递给处理函数。例如:
有一种便捷方式可以一次性指定多个参数,但不包含参数描述:
其他参数配置
有少数附加功能可以直接构造Argument
对象,对参数进行更详尽的配置。
示例代码:arguments-extra.js
自定义参数处理
选项的参数可以通过自定义函数来处理(与处理选项参数时类似),该函数接收两个参数:用户新输入的参数值和当前已有的参数值(即上一次调用自定义处理函数后的返回值),返回新的命令参数值。
处理后的参数值会传递给命令处理函数,同时可通过.processedArgs
获取。
可以在自定义函数的后面设置命令参数的默认值或初始值。
示例代码:arguments-custom-processing.js
处理函数
命令处理函数的参数,为该命令声明的所有参数,除此之外还会附加两个额外参数:一个是解析出的选项,另一个则是该命令对象自身。
示例代码:thank.js
如果你愿意,你可以跳过为处理函数声明参数直接使用 command。 this
关键字设置为运行命令,可以在函数表达式中使用(但不能从箭头函数中使用)。
示例代码:action-this.js
处理函数支持async
,相应的,需要使用.parseAsync
代替.parse
。
使用命令时,所给的选项和命令参数会被验证是否有效。凡是有未知的选项,或缺少所需的命令参数,都会报错。
如要允许使用未知的选项,可以调用.allowUnknownOption()
。默认情况下,传入过多的参数并不报错,但也可以通过调用.allowExcessArguments(false)
来启用过多参数的报错。
独立的可执行(子)命令
当.command()
带有描述参数时,就意味着使用独立的可执行文件作为子命令。
Commander 会尝试在入口脚本的目录中搜索名称组合为 command-subcommand
的文件,如以下示例中的 pm-install
或 pm-search
。搜索包括尝试常见的文件扩展名,如.js
。
你可以使用 executableFile
配置选项指定自定义名称(和路径)。
你可以使用 .executableDir()
为子命令指定自定义搜索目录。
你可以在可执行文件里处理(子)命令的选项,而不必在顶层声明它们。
示例代码:pm
如果该命令需要支持全局安装,请确保有对应的权限,例如755
。
生命周期钩子
可以在命令的生命周期事件上设置回调函数。
示例代码:hook.js
钩子函数支持async
,相应的,需要使用.parseAsync
代替.parse
。一个事件上可以添加多个钩子。
支持的事件有:
事件名称 | 触发时机 | 参数列表 |
---|
preAction , postAction | 本命令或其子命令的处理函数执行前/后 | (thisCommand, actionCommand) |
preSubcommand | 在其直接子命令解析之前调用 | (thisCommand, subcommand) |
自动化帮助信息
帮助信息是 Commander 基于你的程序自动生成的,默认的帮助选项是-h,--help
。
示例代码:pizza
如果你的命令中包含了子命令,会默认添加help
命令,它可以单独使用,也可以与子命令一起使用来提示更多帮助信息。用法与shell
程序类似:
自定义帮助
可以添加额外的帮助信息,与内建的帮助一同展示。
示例代码:custom-help
将会输出以下的帮助信息:
位置参数对应的展示方式如下:
beforeAll
:作为全局标头栏展示
before
:在内建帮助信息之前展示
after
:在内建帮助信息之后展示
afterAll
:作为全局末尾栏展示
beforeAll
和afterAll
两个参数作用于命令及其所有的子命令。
第二个参数可以是一个字符串,也可以是一个返回字符串的函数。对后者而言,为便于使用,该函数可以接受一个上下文对象,它有如下属性:
error
:boolean 值,代表该帮助信息是否由于不当使用而展示
command
:代表展示该帮助信息的Command
对象
在出错后展示帮助信息
默认情况下,出现命令用法错误时只会显示错误信息。可以选择在出错后展示完整的帮助或自定义的帮助信息。
默认行为是在出现未知命令或选项错误后建议正确拼写。你可以禁用此功能。
使用代码展示帮助信息
.help()
:展示帮助信息并退出。可以通过传入{ error: true }
来让帮助信息从 stderr 输出,并以代表错误的状态码退出程序。
.outputHelp()
:只展示帮助信息,不退出程序。传入{ error: true }
可以让帮助信息从 stderr 输出。
.helpInformation()
:得到字符串形式的内建的帮助信息,以便用于自定义的处理及展示。
.name
命令名称出现在帮助中,也用于定位独立的可执行子命令。
你可以使用 .name()
或在 Command 构造函数中指定程序名称。对于 program ,Commander 会使用传递给 .parse()
的完整参数中的脚本名称。但是,脚本名称会根据程序的启动方式而有所不同,因此你可能希望明确指定它。
使用 .command()
指定时,子命令会获得名称。如果你自己创建子命令以与 .addCommand()
一起使用,则使用 .name()
或在 Command 构造函数中设置名称。
.usage
通过这个选项可以修改帮助信息的首行提示,例如:
帮助信息开头如下:
.description 和 .summary
description 出现在命令的帮助中。当列为程序的子命令时,你可以选择提供更短的 summary 以供使用。
.helpOption(flags, description)
每一个命令都带有一个默认的帮助选项。你可以改变 flags
和 description
参数。传入 false
则会禁用内建的帮助信息。
.addHelpCommand()
如果一个命令拥有子命令,它也将有一个默认的帮助子命令。使用.addHelpCommand()
和.addHelpCommand(false)
可以打开或关闭默认的帮助子命令。
也可以自定义名字和描述:
其他帮助配置
内建帮助信息通过Help
类进行格式化。如有需要,可以使用.configureHelp()
来更改其数据属性和方法,或使用.createHelp()
来创建子类,从而配置Help
类的行为。
数据属性包括:
helpWidth
:指明帮助信息的宽度。可在单元测试中使用。
sortSubcommands
:以字母序排列子命令
sortOptions
:以字母序排列选项
可以得到可视化的参数列表,选项列表,以及子命令列表。列表的每个元素都具有_term_
和_description_
属性,并可以对其进行格式化。关于其使用方式,请参考.formatHelp()
。
示例代码:configure-help.js
自定义事件监听
监听命令和选项可以执行自定义函数。
零碎知识
.parse() 和 .parseAsync()
.parse
的第一个参数是要解析的字符串数组,也可以省略参数而使用process.argv
。
如果参数遵循与 node 不同的约定,可以在第二个参数中传递from
选项:
node
:默认值,argv[0]
是应用,argv[1]
是要跑的脚本,后续为用户参数;
electron
:argv[1]
根据 electron 应用是否打包而变化;
user
:来自用户的所有参数。
例如:
解析配置
当默认的解析方式无法满足需要,Commander 也提供了其他的解析行为。
默认情况下,程序的选项在子命令前后均可被识别。如要只允许选项出现在子命令之前,可以使用.enablePositionalOptions()
。这样可以在命令和子命令中使用意义不同的同名选项。
示例代码:positional-options.js
当启用了带顺序的选项解析,以下程序中,-b
选项在第一行中将被解析为程序顶层的选项,而在第二行中则被解析为子命令的选项:
默认情况下,选项在命令参数前后均可被识别。如要使选项仅在命令参数前被识别,可以使用.passThroughOptions()
。这样可以把参数和跟随的选项传递给另一程序,而无需使用--
来终止选项解析。
如要在子命令中使用此功能,必须首先启用带顺序的选项解析。
示例代码:pass-through-options.js
当启用此功能时,以下程序中,--port=80
在第一行中会被解析为程序的选项,而在第二行中则会被解析为一个命令参数:
默认情况下,使用未知选项会提示错误。如要将未知选项视作普通命令参数,并继续处理其他部分,可以使用.allowUnknownOption()
。这样可以混用已知和未知的选项。
默认情况下,传入过多的命令参数并不会报错。可以使用.allowExcessArguments(false)
来启用这一检查。
作为属性的遗留选项
在 Commander 7 以前,选项的值是作为属性存储在命令对象上的。
这种处理方式便于实现,但缺点在于,选项可能会与Command
的已有属性相冲突。通过使用.storeOptionsAsProperties()
,可以恢复到这种旧的处理方式,并可以不加改动地继续运行遗留代码。
TypeScript
如果你使用 ts-node,并有.ts
文件作为独立可执行文件,那么需要用 node 运行你的程序以使子命令能正确调用,例如:
createCommand()
使用这个工厂方法可以创建新命令,此时不需要使用new
,如
createCommand
同时也是Command
对象的一个方法,可以创建一个新的命令(而非子命令),使用.command()
创建子命令时内部会调用该方法,具体使用方式可参考 custom-command-class.js。
Node 选项,如 —harmony
要使用--harmony
等选项有以下两种方式:
- 在子命令脚本中加上
#!/usr/bin/env node --harmony
。注:Windows 系统不支持;
- 调用时加上
--harmony
参数,例如node --harmony examples/pm publish
。--harmony
选项在开启子进程时仍会保留。
调试子命令
一个可执行的子命令会作为单独的子进程执行。
如果使用 node inspector 的node -inspect
等命令来调试可执行命令,对于生成的子命令,inspector 端口会递增 1。
如果想使用 VSCode 调试,则需要在launch.json
配置文件里设置"autoAttachChildProcesses": true
。
显示错误
你可用于针对自己的错误情况调用 Commander 错误处理。(另请参阅下一节有关退出处理的内容)
除了错误消息,你还可以选择指定 exitCode
(与 process.exit
一起使用)和 code
(与 CommanderError
一起使用)
重写退出和输出
默认情况下,在检测到错误、打印帮助信息或版本信息时 Commander 会调用process.exit
方法。其默认实现会抛出一个CommanderError
,可以重写该方法并提供一个回调函数(可选)。
回调函数的参数为CommanderError
,属性包括 Number 型的exitCode
、String 型的code
和message
。子命令完成调用后会开始异步处理。正常情况下,打印错误信息、帮助信息或版本信息不会被重写影响,因为重写会发生在打印之后。
Commander 默认用作命令行应用,其输出写入 stdout 和 stderr。
对于其他应用类型,这一行为可以修改。并且可以修改错误信息的展示方式。
示例代码:configure-output.js
其他文档
请参考关于如下话题的其他文档:
支持
当前版本的 Commander 在 LTS 版本的 Node.js 上完全支持。并且至少需要 v16。
(使用更低版本 Node.js 的用户建议安装更低版本的 Commander)
社区支持请访问项目的 Issues。
企业使用 Commander
作为 Tidelift 订阅的一部分。
Commander 和很多其他包的维护者已与 Tidelift 合作,面向企业提供开源依赖的商业支持与维护。企业可以向相关依赖包的维护者支付一定的费用,帮助企业节省时间,降低风险,改进代码运行情况。