func 会先把应用声明编译成可执行结构,再根据每次传入的 argv 完成一次调用。装饰器只描述模块、命令、字段和处理器之间的关系;真正的实例创建与方法调用都发生在运行时。
createApp() 面向进程应用:编译完成后,bootstrap() 执行一次并设置进程退出码。createInvoker() 同样只编译一次,但可以反复调用 invoke(),每次返回自己的结果与退出码。
-
编译应用
展开模块并校验声明,生成命令图和每个节点的 option 规则。
-
解析调用
解析 argv,匹配命令路径,并确定唯一的 handler 或 option action。
-
准备作用域
创建本次调用的 Context、依赖容器,并激活需要的模块与 provider。
-
执行管线
完成字段绑定与校验,然后调用所选入口。
-
处理与释放
处理异常,确定结果与退出码,并释放本次调用创建的实例。
编译应用
调用 createApp() 或 createInvoker() 时,func 会从根模块开始展开 imports,检查 Provider 的声明与可见性,并收集 Commands 与 options。随后,Command path、Handler path 与 alias 会被编译成一张命令图。
重复 token、无效 export、循环 import、冲突路径等应用结构问题会在这里直接报错。编译阶段只生成和校验结构,不会提前创建 Module、Command 或 Provider 实例。
模块与依赖的完整规则见模块。
解析一次调用
每次调用都会收到 shell 已经处理好的 argv 字符串数组。func 根据不同命令节点可见的 options 分别解析 argv,再用剩余的位置参数匹配命令图。因此,只要 option 在目标节点可见,它可以写在 Command path 前面或后面;-- 之后的内容则全部作为位置输入保留。
入口大致按照以下顺序确定:
- 优先选择匹配最深且唯一的具名 Command 或 Handler path。
- 没有具名入口时,尝试根级 option action。
- 再尝试
@CommandMajor、@CommandMissing或根命令的默认行为。
一次调用最终只会选中一个入口。多个 action 同时出现、同样具体的多个候选或目标节点不可见的 option 都会产生解析错误,而不会依赖注册顺序猜测。
普通字段 option 只提供数据;由 @Handler({ flag }) 或 @OptionCommand() 声明的 action 会改为执行对应入口。action 被选中时,不会校验与它无关的业务字段,因此 deploy --help 不会因为缺少 deploy 所需参数而失败。
创建本次调用的作用域
func 为每次调用创建新的 Context 和运行时依赖容器。根模块始终属于当前调用;命令匹配成功后,运行时还会激活直接声明该命令的 owner Module。仅用于连接 imports 的中间模块不会自动成为执行作用域。
class 与 factory Provider 在第一次被请求时创建,并在当前调用内复用;下一次调用会得到新的实例。value Provider 则直接返回注册时提供的值。未被当前入口使用的 Provider 不会创建。
模块会在执行入口前完成初始化,其他依赖的 onInit() 在实例首次创建时运行。option Provider 会先完成字段绑定,再进入 onInit(),因此不要在构造函数中假设 argv 已经写入字段。
执行所选入口
成功解析后,拦截器从外到内包裹本次调用:应用级 → owner Module → Command。内层执行完成后,结果再按相反方向返回给外层拦截器。
普通 Command 的核心执行顺序是:
- 创建 Command 及其构造函数依赖。
- 写入字段值与默认值,并执行 required、validator 和跨字段 constraint。
- 生成冻结的
Args快照,运行 Command 的onInit()。 - 解析 Handler 参数并调用唯一的 Handler。
Handler 可以同步返回,也可以返回 Promise;两者会进入相同的结果处理流程。Args 包含原始 argv、实际与标准命令路径、剩余位置输入和归一化 options;Context 则提供当前命令、IO、cwd、signal、结果输出方式与退出码。
参数注入见处理器参数,资源的初始化与释放规则见错误与生命周期。
异常处理与释放
运行时异常会交给当前已经激活的 onError。已经选中命令时,按 Command → direct-owner Module → root Module 的方向处理;onError 正常返回表示异常已处理,继续抛出错误则上交给外层。解析失败时尚无 Command 与 owner Module,此时错误只会进入根模块。
没有被 onError 处理的常规运行时错误会写入 stderr,并让调用以退出码 1 结束。onError 也可以通过 Context 调整退出码并控制输出流。详细传播规则见错误处理。
业务执行后,func 会依次查找本次调用中已经激活的 Command、Module 和 Provider。Command 与 Module 按作用域激活顺序逆序释放,Provider 按创建顺序逆序释放。