EN

字段选项

定义命令标志、标量值、重复值、默认值、别名和校验。

更新于

字段选项多数时候不会作为单独命令捕获触发,而是用于修饰一个命令的选项参数。func 已经完成了对于 Options 的解析和校验,我们只需要在命令中为相关属性添加装饰器即可。

选择合适的值类型

用户需求输入示例装饰器字段值
是或否的开关--verbose 或 -v@Flag()boolean
一个字符串或有限数字--port 3000@Value()string | number
同一选项重复多次--include src --include tests@ArrayString() / @ArrayNumber()string[] | number[]

布尔标志

@Flag() 创建布尔开关。用户没有传入该选项时,字段保留属性初始值;使用长名称或任一单字符别名时,字段值为 true

ship serve --verboseship serve -vship serve -V 都会得到 this.verbose === true

src/commands/serve.command.ts
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 发出的装饰器元数据推断 StringNumber,因此每个值字段都必须显式把属性声明为 stringnumber,只有初始值并不足够。布尔选项必须使用 @Flag(),从而让存在形式与反向形式保持明确的 CLI 语义。

src/commands/serve.command.ts
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 一样直接传入名称。

用户省略选项时,属性初始值就是默认值。默认值会直接影响应用行为。

需要把单个字符串输入转换成 URLDate 或领域对象时,我们可以使用自定义函数对数据进行变换,如果希望统一处理,也可使用 createValueDecorator() 创建自己的字段装饰器,详见自定义装饰器

重复值

@ArrayString()@ArrayNumber() 分别把同一选项的多次输入收集为 string[]number[]。TypeScript 装饰器元数据只能识别到 Array,因此由装饰器名称表达元素解析类型,由属性声明确认字段是数组。

src/commands/build.command.ts
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 会在处理器运行前执行校验器。校验失败会抛出对应的输入错误,处理器不会运行。

src/commands/publish.command.ts
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 作用域的动作选项通过 @OptionCommanddeprecated 参数标记。

将 Options 作为命令使用

Options 也可以作为命令使用,但这是一种特殊场景。请前往通过 Module 提供动作选项阅读更多。