EN

命令

定义面向用户的 func 命令、别名,以及同一命令作用域内的多个动作。

更新于 2周前

命令是用户在可执行文件名之后输入的固定单词,用来指定业务领域或动作。在 git commit 中,git 是可执行文件,commit 是 命令。在 func 中,这个命令由带有 @Command({ name: 'commit' }) 的类建模。

func 只使用第一个输入 token 匹配具名命令。如果 token 匹配 name 或 alias,本次调用就归该命令处理; 以连字符开头的 token 交给主命令作用域。确定作用域后,该类的处理器和字段会解析其余 token。

分清一次调用中的每个部分

用户输入命令作用域其余部分的含义
ship statusstatus没有额外输入,因此运行默认处理器。
ship config getconfigget 是 config 命令内的处理器路径。
ship deploy --env proddeploy—env prod 是 deploy 命令的值选项。
ship --help主命令—help 可以选择主命令中的一个处理器。

主命令是 func 独有的作用域,处理没有具名命令的调用。它与未知命令行为一起在 核心概念中介绍。

根据命令格式选择装饰器

写下完整的命令示例,区分其中的固定语法和可变数据,就可以确定各部分应使用的 func 装饰器:

类别 表现 评分
@Command 固定的顶层单词表示业务领域或动作。 ship deploy
@Handler({ path }) 固定的嵌套单词用于选择当前命令中的一个动作。 ship config profile get
@Flag / @Value 具名数据需要类型、默认值、别名、描述或校验。 ship deploy --env prod
@Args().inputs 剩余位置 token 是可变用户数据,而不是固定语法。 ship search alice team-a

添加最小可用命令

最小命令只需要一个类和一个默认处理器。name 是用户实际输入的命令名; description 是帮助处理器可以通过 @Regs() 读取的元数据。

src/commands/status.command.ts
import { Command, Handler } from 'func'

@Command({
  name: 'status',
  description: 'Print service status',
})
export class StatusCommand {
  @Handler()
  run() {
    console.log('All systems operational')
  }
}

创建文件并添加装饰器后,还要把该类加入根模块的命令列表:

src/commands/index.ts
import { ErrorHandler } from './error.command'
import { Major } from './major.command'
import { Missing } from './missing.command'
import { StatusCommand } from './status.command'

export const commands = [Major, StatusCommand, Missing, ErrorHandler]
点击终端以聚焦

func 匹配到 StatusCommand 后会创建实例,并调用默认处理器 run()。每个命令至少需要一个处理器,且只能定义一个默认处理器。

只在确实节省输入时添加别名

别名是同一命令的另一个名称,不会创建新命令,也不会调用其他处理器:

TypeScript
@Command({
  name: 'status',
  alias: 's',
  description: 'Print service status',
})
export class StatusCommand {}

ship statusship s 现在都会选择 StatusCommand。频繁使用的命令可以采用容易辨认的缩写,例如用 g 表示 greet 。很少使用的命令,或容易与其他命令混淆的缩写,不适合用作别名。所有已注册命令的 name 和 alias 都不能重复。

命令别名可以超过一个字符;-h 这样的选项别名则必须是单个字符。装饰器中的值不需要带开头的连字符,func 会为选项补上它。

把相关动作放入同一个命令

固定的顶层动词或资源名适合定义为命令。同一资源有多个动作时,可以共用一个命令, 通过处理器路径区分具体动作,例如 projectproject createproject member add

src/commands/project.command.ts
import { Command, Handler } from 'func'

@Command({
  name: 'project',
  alias: 'p',
  description: 'Manage projects and members',
})
export class ProjectCommand {
  @Handler()
  list() {
    console.log('List projects')
  }

  @Handler({ path: ['create'] })
  create() {
    console.log('Create project')
  }

  @Handler({ path: ['member', 'add'] })
  addMember() {
    console.log('Add project member')
  }

  @Handler({ flag: 'help', alias: 'h', description: 'Print project help' })
  help() {
    console.log('Project usage')
  }
}
  • ship project 运行默认的 list() 处理器。
  • ship project create 运行 create() 路径处理器。
  • ship p member add 通过命令别名和最长匹配路径运行 addMember()
  • ship project --help 运行由处理器标志选择的 help 方法。

处理器标志用来选择互斥动作;字段 @Flag() 只向已选中的处理器提供布尔值。 func 的匹配顺序为:路径处理器、处理器标志、默认处理器。路径不能声明 alias, 也不能与处理器 flag 同时使用。

组织多个命令

一个应用可以注册多个具名命令,通常每个顶层业务领域对应一个类。较大的业务领域可以放入独立的 @FuncModule,由根模块 import。不要把无关的操作都写在一个命令类中。

一次 CLI 调用只会选中一个顶层命令。对于 ship build deploy,func 会选中 build,而 deploy 只是剩余输入。固定的嵌套动作应使用处理器路径; 需要连续执行多个业务操作时,可以定义一个专用命令,并在处理器中调用共享服务。

下一步可以用字段选项接收标志和值,或通过 示例了解常见用户需求应如何定义命令。