refactor(cmdline): make command providers ordinary

This commit is contained in:
Turtle
2026-08-10 21:49:11 +08:00
parent 668bdb3d8e
commit 09e2d2ddc1
46 changed files with 400 additions and 742 deletions
+10 -14
View File
@@ -13,30 +13,28 @@ dsh 启动器交给它所引导应用的那条命令行。启动器只解析属
没有命令行的嵌入宿主提供空列表;这是诚实的答案,而不是缺失的值。
## 启动行,以及它的应用所读取的服务
## 普通提供方与注入配置
应用从自己的**启动行**读取这些参数:这是一个在 Loader 行与插件中都注入 `cmdlineArgs`,并调用 `runStartup(ctx, service, program, plan)` 的插件:
任何应用插件都可以注入 `cmdlineArgs`、解析它,再发布一个普通的应用自有服务。`parseCmdline(ctx, program, plan)` 只适配 commander;返回值与服务都归调用方持有:
```ts ignore
export const name = 'web-startup'
export const inject = ['cmdlineArgs']
export function apply(ctx: Context): void {
runStartup(ctx, 'webStartup', webCommand(), planWebStartup)
const values = parseCmdline(ctx, webCommand(), planWebStartup)
if (values !== undefined) ctx.provide('webStartup', values)
}
```
Loader 行的注入同时也是发现声明,因此无需组合包 manifest 字段:
它的 Loader 行不携带启动器标记,也没有特殊类型:
```yaml
- id: web-startup
name: '@deepseek-ai/dsh-web-app/startup'
inject: [cmdlineArgs]
```
启动器只用该注入来拒绝那些没有命令行所有者却带有应用参数的组合,以及拒绝存在多个所有者的组合。Loader 只挂载一次整套组合,并让每一行等待自身的注入激活。
应用用 flag 配置的每一行随后读取启动行解析出的取值,各自点名自己取用的键,以及回退时使用的值:
所有由这些取值配置的行都使用普通服务注入,并在惰性配置中直接访问该服务:
```yaml
- id: webserver
@@ -47,9 +45,7 @@ Loader 行的注入同时也是发现声明,因此无需组合包 manifest 字
port: !!js ctx.webStartup.port ?? 3080
```
`runStartup` 解析参数,向 `plan` 索取取值,并把它们作为服务提供出去。遇到 `--help`、`--version`、解析错误,或 `plan` 发出的 `program.error(...)` 时,它输出 commander 的文本并请求退出:什么也不会被提供,因此依赖启动服务的行不会激活。
`plan` 会收到启动上下文,以及所有注入该服务的行的选项,用于那些必须顾及组合本身的取值。此时 Include 仍保留着嵌套表达式的原始形态,因此需要组合回退值的 plan 可以基于服务提供前的启动上下文插值相关行配置;随附的例子是 `/api` 栅栏 authority。
`parseCmdline` 解析不可变参数,再向 `plan` 索取应用自有取值。遇到 `--help`、`--version`、解析错误,或 `plan` 发出的 `program.error(...)` 时,它输出 commander 文本、请求退出并返回 `undefined`;提供方什么也不发布,因此依赖行不会激活。
### 注入如何排列配置求值
@@ -57,9 +53,9 @@ Loader 会把一行的 `!!js` 插值推迟到该行声明的注入全部激活
`enableRow(ctx, id)` 打开某个组合包以禁用状态交付、只有部分调用才需要的行(`dsh web --dev` 及其客户端插件重载链路)。该激活是内存中的覆盖:它不会改写行所配置的 `disabled` 值,并会在已挂载条目的配置重新应用后继续生效。Loader 会对启用后的行应用普通的注入顺序。
### 一条命令行,一个所有者
### 共享不可变参数
一套组合有且只有一个命令行所有者。叠加在另一应用之上的应用会禁用下层的启动行,并提供保留下来的各行所注入的全部启动服务。
`get()` 不会消费或修改 argv。多个插件可以解析同一份快照,并分别提供服务。启动器不会检查组合中的命令行所有者;没有读取方的 profile 只会忽略自己的应用参数。
树外插件会带来自己的一份 commander 副本,因此 commander 的控制流错误按结构识别,而不是按类身份识别;按身份判断会把已经打印出来的 help 重新抛成致命的加载失败。
@@ -74,5 +70,5 @@ Loader 会把一行的 `!!js` 插值推迟到该行声明的注入全部激活
## 已知限制与延期工作
- **启动器的 flag 必须写在应用参数之前**:切分按位置进行,启动器不认识的第一个 token 就是内层参数的起点,因此写在某个应用 flag 之后的 `--patch` 属于应用。启动器的解析器会消耗掉一个 `--`,因此必须以字面量 `--` 存活到应用的参数需要写成 `-- --`。
- **启动服务没有声明所有者**:读取行点名它,由 `cmdlineArgs` 消费方提供它;这两种注入之间没有静态关联,因此交付了读取行却缺少对应启动行的组合包会在结算时失败(出现指向该服务的待处理条目),而不是在加载时失败。
- **应用自有服务没有静态声明的提供方**:消费行通过普通注入点名它;缺少提供方的组合包会在结算时失败,由待处理条目点名该服务,而不是在加载时失败。
- **用户 patch 若整体替换某行的 `config`,会连同其中的表达式一起丢掉**:flag 胜过的是表达式旁写着的那个值,而不是用户用字面量替换掉表达式之后的结果;保留表达式才能保留 flag 的优先级。