EN

命令

定义面向用户的 Func 命令路径、别名,以及同一命令类中的多个动作。

更新于

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

path 既可以是一个字符串,也可以是字符串数组。多个命令可以共享前缀,func 会把所有 Command path 和 Handler path 编译成命令图,并按最长路径选择唯一入口。

按常见调用举例

用户输入命中的路径其余部分的含义
ship statusCommand status没有额外输入,运行默认处理器。
ship config profile set devCommand + Handler setdev 是处理器收到的位置输入。
ship deploy --env prodCommand deploy—env prod 是 deploy 的值选项。
ship --helpModule action --help根模块可以提供全局动作选项。

主命令和缺失命令在概念中有更加详细的介绍;关于模块的作用域动作见共享字段。需要 --help 时,请参考帮助

这些概念有些复杂,我们不必一次全部弄懂,作为最简单的入门思维方式,只需要知道 status 命令是由一个使用 @Command('status') 装饰器的类驱动运行即可,这是固定的一对一关系。我们先跟随下方的起步项目看看 status 命令的源码和效果。

添加最小可用命令

最小命令只需要一个类和一个默认处理器。path 是用户实际输入的命令路径;description 称作命令的元数据,这里用于描述命令的用途。

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

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

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

src/app.module.ts
import { StatusCommand } from './status.command'

@Module({
  commands: [StatusCommand],
})
export class AppModule {}
点击终端以聚焦

func 匹配到 StatusCommand 后会创建实例,并且开始在类中寻找 @Handler() 标注,随后开始运行 StatusCommand.run() 方法。简单来说 @Handler() 就是是命令的处理器,也就是默认运行入口。每个命令至少需要 1 个处理器,后续你也可以了解如何为单个类添加多个处理器。

命令的别名

别名是命令的其他调用途径,它不会创建新节点,也不会调用其他处理器,只提供便捷入口。

TypeScript
@Command({
  path: 'status',
  aliases: ['s', 'stat'],
  description: 'Print service status',
})
export class StatusCommand {}

现在我们运行 ship sship stat 现在都等同于 ship status,因为这些都是 status 命令的别名。

多个别名使用 aliases;单数 alias 与其合并并去重。命令别名可以超过一个字符,且只需要在同一父路径中保持唯一;-h 这样的选项别名则必须是非数字的单个字符。

命令路径

当多个命令共享一个固定前缀时,你可以将前缀固定在 Command 上作为路径,只有连续命中路径才会激活当前类。比如这里 config profile 默认一同出现,我们直接使用组合路径作为 Command 的命中条件。

在路径中,别名只代表最后一段,这里输入 ship config profileship config p 是相等的。

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

@Command({
  path: ['config', 'profile'],
  aliases: ['p'],
  description: 'Manage saved profiles',
})
export class ProfileCommand {
  @Handler()
  list() {
    console.log('List profiles')
  }

  @Handler('set')
  set() {
    console.log('Set profile')
  }
}

注意,这里我们也通过 @Handler('set') 额外扩展了新的路径,这是一个新的语法,使用多个处理器分别处理不同的子路径:

  • 默认处理器 @Handler()ship config profile
  • 子处理器 @Handler('set')ship config profile set

你可能已经发现,子处理器与路径命令都是在做类似的事情,只不过它们处理的维度不同,如果你的业务逻辑庞大,建议使用 Command 路径进行分类,如果尾端分叉的只是细微逻辑,则可直接调用子处理。

多处理器

同一资源有多个紧密相关的动作时,可以共用一个命令类,通过处理器路径区分具体动作,例如 projectproject createproject member add

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

@Command({
  path: 'project',
  aliases: ['p', 'proj'],
  description: 'Manage projects and members',
})
export class ProjectCommand {
  @Handler()
  list() {
    console.log('List projects')
  }

  @Handler('create')
  create() {
    console.log('Create project')
  }

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

  @Handler({ flag: 'version', alias: 'v', description: 'Print version' })
  version() {
    console.log('1.0.0')
  }
}
  • ship project 运行默认的 list() 处理器。
  • ship project create 运行 create() 路径处理器。
  • ship p member add 通过命令别名和最长路径匹配运行 addMember()
  • ship project -v 通过短别名运行 version 方法。

处理器标志用来选择互斥动作;字段 @Flag() 只向已选中的处理器提供布尔值。处理器标志同时接受 aliasaliases,但每个别名都必须是非数字的单个字符。path 不能声明 aliases,也不能与处理器 flag 同时使用。

废弃命令

@Command 中设置 deprecated: true 可以标记整个命令,也可以传入字符串提供迁移说明。废弃命令仍然可以执行,会在结构化帮助中标明,并在实际分派时向 stderr 输出不影响退出码的提示。

在带有 @Handler({ flag }) 的方法上使用 @Deprecated(),可以只废弃该动作选项。默认 Handler 和 path Handler 不能使用 @Deprecated();这类场景应废弃其所属命令。

组织多个命令

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

类别 表现 评分
@Command 一段或多段固定单词形成可复用的命令路径。 ship deploy / ship config profile
@Handler(path) 固定的后续单词用于选择当前命令类中的一个动作。 ship project member add
@Flag / @Value 具名数据需要类型、默认值、别名、描述或校验。 ship deploy --env prod
@Args().inputs 剩余路径是动态可变数据,而不是固定语法。 ship search alice team-a

一个应用可以注册多个命令,通常每个业务领域对应一个类。较大的业务领域可以放入独立的 @Module,由根模块 import。共享前缀并不要求这些命令位于同一个类中;当不同动作需要不同依赖或团队边界时,可以拆成多个 Command。

一次 CLI 调用只会执行一个 Command 或动作入口。需要连续执行多个业务操作时,可以定义一个专用命令,并在处理器中调用共享 Provider。

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