字段选项多数时候不会作为单独命令捕获触发,而是用于修饰一个命令的选项参数。func 已经完成了对于 Options 的解析和校验,我们只需要在命令中为相关属性添加装饰器即可。
选择合适的值类型
| 用户需求 | 输入示例 | 装饰器 | 字段值 |
|---|---|---|---|
| 是或否的开关 | --verbose 或 -v | @Flag() | boolean |
| 一个字符串或有限数字 | --port 3000 | @Value() | string | number |
| 同一选项重复多次 | --include src --include tests | @ArrayString() / @ArrayNumber() | string[] | number[] |
布尔标志
@Flag() 创建布尔开关。用户没有传入该选项时,字段保留属性初始值;使用长名称或任一单字符别名时,字段值为 true。
ship serve --verbose、ship serve -v 和 ship serve -V 都会得到 this.verbose === true。
import { Command, Flag, Handler } from 'func'
@Command('serve')
export class ServeCommand {
@Flag({ alias: 'v', aliases: ['V'], description: 'Print request logs' })
verbose = false
@Flag({ aliases: ['c', 'C'], description: 'Use colored output', negatable: true })
color = true
@Handler()
run() {
console.log(this.verbose, this.color)
}
}注意,如果我们输入 ship serve --color=false 仍旧会收到 this.color === true,如果你希望得到反向值,可以使用 { negatable: true } 进行标注,这会自动生成 --no-<name> 选项。此时用户输入 ship serve --no-color 会得到 this.color === false。
所有短别名都是正向形式:ship serve --no-color -C 会得到 true。正向和反向形式同时出现时,以最后一个为准;都未出现时保留属性初始值。
字段标志用于向当前动作提供布尔值。如果该选项应该选择另一个方法,请改用处理器标志,详见命令。
标量值
@Value() 接收一个值。func 根据 TypeScript 发出的装饰器元数据推断 String 和 Number,因此每个值字段都必须显式把属性声明为 string 或 number,只有初始值并不足够。布尔选项必须使用 @Flag(),从而让存在形式与反向形式保持明确的 CLI 语义。
import { Command, Handler, Value } from 'func'
@Command('serve')
export class ServeCommand {
@Value({ description: 'Interface to bind' })
host: string = 'localhost'
@Value({ aliases: ['p', 'P'], description: 'Port to listen on' })
port: number = 3000
@Value('config-file')
configFile?: string
@Handler()
run() {
console.log(this.host, this.port, this.configFile)
}
}ship serve --host 0.0.0.0 -P 4000 --config-file ./dev.json 会分别为三个字段赋予字符串、数字和字符串。属性名默认作为对外选项名。驼峰字段如果需要常见的 kebab-case,应像 configFile 一样直接传入名称。
用户省略选项时,属性初始值就是默认值。默认值会直接影响应用行为。
需要把单个字符串输入转换成 URL、Date 或领域对象时,我们可以使用自定义函数对数据进行变换,如果希望统一处理,也可使用 createValueDecorator() 创建自己的字段装饰器,详见自定义装饰器。
重复值
@ArrayString() 和 @ArrayNumber() 分别把同一选项的多次输入收集为 string[] 和 number[]。TypeScript 装饰器元数据只能识别到 Array,因此由装饰器名称表达元素解析类型,由属性声明确认字段是数组。
import { ArrayNumber, ArrayString, Command, Handler } from 'func'
@Command('build')
export class BuildCommand {
@ArrayString({ name: 'include', aliases: ['i', 'I'] })
includes: string[] = []
@ArrayNumber('port')
ports: number[] = []
@Handler()
run() {
console.log(this.includes, this.ports)
}
}ship build -i src -I tests --port 3000 --port -1.5 会把 ['src', 'tests'] 赋给 this.includes,并把 [3000, -1.5] 赋给 this.ports。重复值会保留输入顺序。
校验器
字段获得解析值或默认值后,func 会在处理器运行前执行校验器。校验失败会抛出对应的输入错误,处理器不会运行。
import { Command, DependsOn, Enum, Exclusive, Flag, Handler, Required, Value, ValueValidate } from 'func'
@Command('publish')
export class PublishCommand {
@Required()
@Enum(['dev', 'prod'])
@Value()
target?: string
@DependsOn(['token'])
@Value()
registry?: string
@Value()
token?: string
@Exclusive(['json'])
@Flag()
table = false
@Flag()
json = false
@ValueValidate((value) => Number(value) > 0 || 'retry must be positive')
@Value()
retry: number = 1
@Handler()
run() {}
}@Required()拒绝undefined。已定义的属性默认值会满足该规则,因此必须由用户提供的选项不要设置默认值。@Enum(values)只接受列表内的标量值;对于数组,则要求每一项都在列表内。@DependsOn(['token'])只在装饰的选项被显式传入时要求同时提供--token。@Exclusive(['json'])会拒绝两个选项都被显式传入的调用。@ValueValidate(fn)接收归一化值和所有选项值。返回false会产生通用错误,返回字符串会显示自定义信息,没有返回值表示校验通过。
依赖和互斥校验器接收的是不带 -- 的公开长选项名,不是 TypeScript 属性名或短 alias。
看起来像选项但没有声明的 token 都会被拒绝。如果你需要处理剩余位置输入 (比如动态路径) 和完整运行时上下文可参考 参数注入。
标记废弃选项
将 @Deprecated() 与 @Flag、@Value、@ArrayString 或 @ArrayNumber 放在同一属性上,可以让选项继续工作,同时在结构化帮助中标明,并在用户显式传入时向 stderr 输出不影响退出码的提示。可以传入 @Deprecated('Use --format instead.') 这样的迁移说明。默认值不会触发提示;把该装饰器放在普通属性上也不会产生可观察效果。
Module 作用域的动作选项通过 @OptionCommand 的 deprecated 参数标记。
将 Options 作为命令使用
Options 也可以作为命令使用,但这是一种特殊场景。请前往通过 Module 提供动作选项阅读更多。