字段选项既是命令属性,也定义了 CLI 对外提供的选项。func 会根据当前命令中的装饰字段创建 选项解析器,把解析值或默认值赋给实例,完成校验,然后才调用处理器。
字段选项只属于一个命令作用域。ServeCommand 声明的选项可用于 ship serve,不会自动出现在主命令或其他具名命令中。
选择合适的值类型
| 用户需求 | 输入示例 | 装饰器 | 字段值 |
|---|---|---|---|
| 是或否的开关 | --verbose 或 -v | @Flag() | boolean |
| 一个字符串、数字或布尔值 | --port 3000 | @Value() | string | number | boolean |
| 同一选项重复多次 | --include src --include tests | @ArrayValue() | string[] |
布尔标志
@Flag() 创建布尔开关。用户没有传入该选项时,字段保留属性初始值;使用长名称或单字符 别名时,字段值为 true。
import { Command, Flag, Handler } from 'func'
@Command({ name: 'serve' })
export class ServeCommand {
@Flag({ alias: 'v', description: 'Print request logs' })
verbose = false
@Handler()
run() {
console.log(this.verbose)
}
}ship serve --verbose 和 ship serve -v 都会得到 this.verbose === true 。字段标志用于向当前动作提供布尔值。如果该选项应该选择 另一个方法,请改用处理器标志,详见命令。
标量值
@Value() 接收一个值。func 可以根据 TypeScript 发出的装饰器元数据推断 String、Number 和 Boolean 。可选属性或其他无法在运行时推断的声明需要显式传入 type。
import { Command, Handler, Value } from 'func'
@Command({ name: 'serve' })
export class ServeCommand {
@Value({ description: 'Interface to bind' })
host: string = 'localhost'
@Value({ alias: 'p', description: 'Port to listen on' })
port: number = 3000
@Value({ name: 'config-file', type: String })
configFile?: string
@Handler()
run() {
console.log(this.host, this.port, this.configFile)
}
}ship serve --host 0.0.0.0 --port 4000 --config-file ./dev.json 会分别为三个字段赋予字符串、数字和字符串。属性名默认作为对外选项名。驼峰字段如果需要常见 的 kebab-case 时,应像 configFile 一样显式设置 name。
用户省略选项时,属性初始值就是默认值。默认值会直接影响应用行为;如果用户需要知道 它,帮助信息中也应注明。
重复字符串值
@ArrayValue() 会把同一选项的多次输入收集为字符串数组。如果顺序或重复次数有意义,应使用该装饰器, 不要另外设计逗号分隔格式并在业务代码中解析。
import { ArrayValue, Command, Handler } from 'func'
@Command({ name: 'build' })
export class BuildCommand {
@ArrayValue({ name: 'include', alias: 'i' })
includes: string[] = []
@Handler()
run() {
console.log(this.includes)
}
}ship build -i src -i tests 会把 ['src', 'tests'] 赋给 this.includes。ArrayValue 当前包含字符串;只有公开 CLI 确实需要其他表示时,可以在业务代码中转换。
在业务代码运行前完成校验
字段获得解析值或默认值后,func 会在处理器运行前执行校验器。校验失败会产生 runtime-print 输入错误,处理器不会运行。
import { Command, DependsOn, Enum, Exclusive, Flag, Handler, Required, Value, ValueValidate } from 'func'
@Command({ name: 'publish' })
export class PublishCommand {
@Required()
@Enum(['dev', 'prod'])
@Value({ type: String })
target?: string
@DependsOn(['token'])
@Value({ type: String })
registry?: string
@Value({ type: String })
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。
仅解析的子选项
@SubOptions() 声明只出现在 @Args().option 中的选项,func 不会把它们赋给字段。它适合兼容已有的动态选项设计,但无法直接在命令实例上看到可用数据和默认值。 普通的类型化命令应优先使用字段选项。
import { Args, Command, FuncArgs, Handler, SubOptions } from 'func'
@SubOptions([
{ name: 'format', alias: 'f', type: String },
{ name: 'raw', type: Boolean },
])
@Command({ name: 'inspect' })
export class InspectCommand {
@Handler()
run(@Args() args: FuncArgs) {
console.log(args.option.format, args.option.raw)
}
}看起来像选项但没有声明的 token 会被拒绝。剩余位置输入和完整运行时上下文见 参数注入。