refactor: 重命名 command checker 为 cmd checker 并适配跨平台测试
将 type/configKey 从 "command" 统一为 "cmd",源码目录 runner/command/ → runner/cmd/, spec 目录 command-checker/ → cmd-checker/,测试全部改用 bun -e 替代 Unix 系统命令, 归档 cmd-checker-enhancement 变更并同步 delta spec 到主 spec。
This commit is contained in:
@@ -1,2 +0,0 @@
|
||||
schema: spec-driven
|
||||
created: 2026-05-13
|
||||
@@ -1,112 +0,0 @@
|
||||
## Context
|
||||
|
||||
当前 command checker 使用 `"command"` 作为 type 和 configKey,对应源码目录 `src/server/checker/runner/command/`、测试目录 `tests/server/checker/runner/command/`、spec 目录 `openspec/specs/command-checker/`。
|
||||
|
||||
测试中使用了 `true`、`false`、`sleep`、`bash`、`yes | head` 等 Unix 系统命令,在纯 Windows 环境(无 Git Bash)下无法运行。probes.example.yaml 中的示例命令(`uname -a`、`ls /tmp`、`date`)同样不跨平台。
|
||||
|
||||
项目未上线,无向前兼容负担。
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
|
||||
- 将 type/configKey 从 `"command"` 统一重命名为 `"cmd"`,包括源码目录、测试目录、spec 目录、YAML 配置键名
|
||||
- 测试改用 `bun -e "..."` 替代系统命令,确保 Windows/macOS/Linux 三平台通过
|
||||
- probes.example.yaml 提供跨平台示例
|
||||
|
||||
**Non-Goals:**
|
||||
|
||||
- 不加 shell 模式(现有 exec + args 已覆盖所有 shell 场景)
|
||||
- 不加重试机制(失败是拨测指标)
|
||||
- 不精简 resolve() 中 intervalMs/timeoutMs(收益小,接口改动大)
|
||||
- 不加 successExitCodes 别名(已有 expect.exitCode)
|
||||
|
||||
## Decisions
|
||||
|
||||
### D1: type 与 configKey 统一为 `cmd`
|
||||
|
||||
YAML 配置形态变为:
|
||||
|
||||
```yaml
|
||||
defaults:
|
||||
cmd:
|
||||
maxOutputBytes: "100MB"
|
||||
|
||||
targets:
|
||||
- name: "test"
|
||||
type: cmd
|
||||
cmd:
|
||||
exec: "bun"
|
||||
args: ["-e", "console.log('hello')"]
|
||||
```
|
||||
|
||||
**理由:** `cmd` 简洁,且 type 与 configKey 保持一致(与 HTTP checker 的 `http`/`http` 对称)。
|
||||
|
||||
**替代方案:** 只改 type 不改 configKey → 会出现 `type: cmd` + `command: {...}` 的不一致,否决。
|
||||
|
||||
### D2: 内部属性名统一为 `cmd`
|
||||
|
||||
`ResolvedCommandTarget` 接口中的 `command` 属性名也改为 `cmd`:
|
||||
|
||||
```typescript
|
||||
// Before
|
||||
interface ResolvedCommandTarget {
|
||||
command: ResolvedCommandConfig;
|
||||
type: "command";
|
||||
}
|
||||
// t.command.exec
|
||||
|
||||
// After
|
||||
interface ResolvedCommandTarget {
|
||||
cmd: ResolvedCommandConfig;
|
||||
type: "cmd";
|
||||
}
|
||||
// t.cmd.exec
|
||||
```
|
||||
|
||||
**理由:** 内外一致,避免 configKey 是 `cmd` 但内部属性是 `command` 的割裂。
|
||||
|
||||
### D3: 源码目录重命名 `runner/command/` → `runner/cmd/`
|
||||
|
||||
所有 import 路径同步更新。测试目录 `tests/server/checker/runner/command/` → `tests/server/checker/runner/cmd/`。
|
||||
|
||||
**理由:** 目录名与 type/configKey 保持一致,降低认知负担。
|
||||
|
||||
### D3: 跨平台测试命令替换表
|
||||
|
||||
| 原命令 | 替换为 |
|
||||
|---|---|
|
||||
| `true` | `bun -e "process.exit(0)"` |
|
||||
| `false` | `bun -e "process.exit(1)"` |
|
||||
| `echo hello` | `bun -e "console.log('hello')"` |
|
||||
| `sleep 10` | `bun -e "await Bun.sleep(10000)"` |
|
||||
| `bash -c "echo error >&2"` | `bun -e "process.stderr.write('error\n')"` |
|
||||
| `bash -c "yes \| head -1000"` | `bun -e "process.stdout.write('y\n'.repeat(1000))"` |
|
||||
|
||||
**理由:** `bun` 是项目唯一运行时依赖,三平台均可用,无需额外安装。
|
||||
|
||||
### D4: probes.example.yaml 示例策略
|
||||
|
||||
示例命令改用 `bun -e "..."` 或跨平台命令(如 `bun --version`),不再使用 `uname`、`ls /tmp` 等 Unix 专属命令。
|
||||
|
||||
### D5: spec 目录重命名
|
||||
|
||||
`openspec/specs/command-checker/` → `openspec/specs/cmd-checker/`,与 type 名称对齐。
|
||||
|
||||
### D6: 不加 shell 模式
|
||||
|
||||
用户需要管道/重定向时,用现有参数即可:
|
||||
|
||||
```yaml
|
||||
cmd:
|
||||
exec: "/bin/bash"
|
||||
args: ["-c", "df -h | grep /dev/sda1"]
|
||||
```
|
||||
|
||||
shell 模式本质是语法糖——内部仍然是 `Bun.spawn([shell, "-c", exec])`。增加代码复杂度(shell 检测、参数推断、互斥校验)但收益有限。
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- [全量重命名可能遗漏引用] → 通过全局搜索 `"command"` 字面量确保无遗漏,CI 类型检查兜底
|
||||
- [测试中 `bun -e` 启动开销比原生命令大] → 拨测场景不敏感,测试可接受毫秒级差异
|
||||
- [probes.example.yaml 示例不如 Unix 命令直观] → 加注释说明用途,保持可读性
|
||||
@@ -1,34 +0,0 @@
|
||||
## Why
|
||||
|
||||
`command` 作为 checker type 名称过长,且测试依赖 Unix 系统命令导致 Windows 环境无法运行。需要统一重命名为 `cmd` 并实现跨平台测试适配。
|
||||
|
||||
## What Changes
|
||||
|
||||
- **BREAKING** type 字面量 `"command"` → `"cmd"`,configKey `"command"` → `"cmd"`
|
||||
- **BREAKING** YAML 配置中 `type: command` → `type: cmd`,`command:` 块 → `cmd:` 块
|
||||
- **BREAKING** `defaults.command` → `defaults.cmd`
|
||||
- 源码目录 `runner/command/` → `runner/cmd/`
|
||||
- spec 目录 `command-checker/` → `cmd-checker/`
|
||||
- 测试全部改用 `bun -e "..."` 替代系统命令(true/false/sleep/bash)
|
||||
- probes.example.yaml 更新为跨平台示例
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
(无)
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `probe-config`: `type: command` → `type: cmd`,`command` 分组 → `cmd` 分组,`defaults.command` → `defaults.cmd`,所有校验中的 `"command"` 字面量更新
|
||||
- `command-checker`: type/configKey 重命名为 `cmd`,spec 目录重命名为 `cmd-checker`
|
||||
- `checker-runner-abstraction`: registry 注册的 type 从 `"command"` 变为 `"cmd"`,`supportedTypes` 返回 `["http", "cmd"]`
|
||||
- `windows-test-compat`: 测试命令全面改用 `bun -e "..."`,probes.example.yaml 使用跨平台示例
|
||||
|
||||
## Impact
|
||||
|
||||
- 后端:`src/server/checker/runner/command/` 整个目录重命名及内部所有 `"command"` 字面量
|
||||
- 配置:probes.example.yaml、probe-config.schema.json 中的 type 枚举和分组名
|
||||
- 测试:`tests/server/checker/runner/command/` 目录重命名及测试命令替换
|
||||
- 前端:无影响(动态显示 type 值)
|
||||
- 数据库:stored_targets.type 列值变更(项目未上线,无迁移负担)
|
||||
@@ -1,43 +0,0 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: CheckerRegistry 注册中心
|
||||
系统 SHALL 在 `src/server/checker/runner/registry.ts` 中提供 `CheckerRegistry` 类,支持 `register(checker)`、`get(type)` 和 `supportedTypes`。重复注册同一 type SHALL 抛出错误。registry 内部 SHALL 存储 `CheckerDefinition`(使用默认泛型参数),对外提供类型擦除后的接口。
|
||||
|
||||
#### Scenario: 查询支持的 type 列表
|
||||
- **WHEN** 注册了 "http" 和 "cmd" 两个 checker 后查询 `registry.supportedTypes`
|
||||
- **THEN** 返回的数组 SHALL 包含 `["http", "cmd"]`(按注册顺序)
|
||||
|
||||
### Requirement: Command checker 提供契约片段
|
||||
系统 SHALL 支持 checker 提供自身 TypeBox 配置契约片段,用于描述该 checker 的 defaults 分组、target 领域分组和 expect 分组。
|
||||
|
||||
#### Scenario: Cmd checker 提供契约片段
|
||||
- **WHEN** Cmd checker 被注册
|
||||
- **THEN** registry SHALL 能提供 Cmd defaults、Cmd target 和 Cmd expect 的 TypeBox 契约片段
|
||||
|
||||
### Requirement: 配置解析通过 registry 委托 checker
|
||||
系统 SHALL 在 `config-loader.ts` 的配置加载流程中通过 `checkerRegistry` 发现已注册 checker,组合公共 TypeBox 契约与 checker 契约,并将 checker 专属语义校验和解析委托给对应 checker。
|
||||
|
||||
#### Scenario: 配置解析委托 checker
|
||||
- **WHEN** config-loader 解析一个 type 为 "cmd" 的 target
|
||||
- **THEN** config-loader SHALL 调用 `checkerRegistry.get("cmd")` 获取对应 checker,并委托该 checker 执行语义校验和 resolve
|
||||
|
||||
### Requirement: Command text 断言位于 Cmd 目录
|
||||
系统 SHALL 在 checker 专用目录中提供 text 断言函数。
|
||||
|
||||
#### Scenario: Command text 断言位于 Cmd 目录
|
||||
- **WHEN** Cmd checker 需要对 stdout/stderr 执行文本规则校验
|
||||
- **THEN** SHALL 调用 `runner/cmd/text.ts` 中的 `checkTextRules(text, rules, phase)`
|
||||
|
||||
### Requirement: Command 专用 expect
|
||||
系统 SHALL 在 checker 专用目录中提供 exitCode 断言函数。
|
||||
|
||||
#### Scenario: Command 专用 expect
|
||||
- **WHEN** Cmd checker 需要校验退出码
|
||||
- **THEN** SHALL 调用 `runner/cmd/expect.ts` 中的 `checkExitCode()`
|
||||
|
||||
### Requirement: Checker 接口定义
|
||||
系统 SHALL 在 `src/server/checker/runner/types.ts` 中定义面向扩展的泛型 `CheckerDefinition<TResolved extends ResolvedTargetBase = ResolvedTargetBase>`,包含 `type`、`configKey`、TypeBox 配置契约、启动期语义校验、`resolve`、`execute`、`serialize` 成员。
|
||||
|
||||
#### Scenario: type 与 configKey 默认一致
|
||||
- **WHEN** checker 定义 `type: "cmd"`
|
||||
- **THEN** checker 的 `configKey` SHALL 默认使用 `"cmd"`,对应 target 的 `cmd` 分组和 defaults.cmd 分组
|
||||
@@ -1,33 +0,0 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: command target 配置
|
||||
系统 SHALL 支持 `type: cmd` 的 target 配置,通过 `cmd.exec` 和 `cmd.args` 描述本地命令,并使用 cmd 专用字段配置工作目录、环境变量和输出限制。
|
||||
|
||||
#### Scenario: 解析 cmd target
|
||||
- **WHEN** YAML 中 target 配置 `type: cmd`、`cmd.exec: "pgrep"` 和 `cmd.args: ["nginx"]`
|
||||
- **THEN** 系统 SHALL 将其解析为 cmd checker,并保留 exec、args、cwd、env、maxOutputBytes、interval、timeout 和 expect 配置
|
||||
|
||||
#### Scenario: cmd target 缺少 exec
|
||||
- **WHEN** YAML 中 target 配置 `type: cmd` 但缺少 `cmd.exec`
|
||||
- **THEN** 系统 SHALL 以配置错误退出,并提示该 target 缺少 cmd.exec 字段
|
||||
|
||||
#### Scenario: cwd 相对配置文件目录解析
|
||||
- **WHEN** cmd target 配置 `cmd.cwd: "scripts"` 且配置文件位于 `/opt/checker/probes.yaml`
|
||||
- **THEN** 系统 SHALL 将 cwd 解析为 `/opt/checker/scripts`
|
||||
|
||||
#### Scenario: cmd 不使用 shell
|
||||
- **WHEN** cmd target 配置 `exec` 和 `args`
|
||||
- **THEN** 系统 MUST 直接执行该程序和参数,不通过 shell 解释整段命令字符串
|
||||
|
||||
#### Scenario: env 默认继承并允许覆盖
|
||||
- **WHEN** cmd target 配置 `cmd.env: {LANG: "C"}` 且当前进程环境包含 `PATH`
|
||||
- **THEN** 系统 SHALL 继承当前进程的全部环境变量,并将 `LANG` 覆盖为 `"C"`
|
||||
|
||||
### Requirement: command checker 执行
|
||||
系统 SHALL 按 cmd target 配置执行本地命令,记录执行耗时、退出码、stdout 和 stderr,并在执行失败时产生结构化错误信息。
|
||||
|
||||
### Requirement: command expect 校验
|
||||
系统 SHALL 支持 cmd 专用 expect,包括 `exitCode`、`stdout` 和 `stderr`,并按 exitCode、duration、stdout、stderr 的阶段顺序快速失败。
|
||||
|
||||
### Requirement: command checker 启动期配置校验
|
||||
系统 SHALL 在启动期对 cmd checker 的配置契约和语义执行严格校验。Cmd target 的 `cmd` 分组 SHALL 只允许 `exec`、`args`、`cwd`、`env`、`maxOutputBytes` 字段;Cmd expect SHALL 只允许 `exitCode`、`maxDurationMs`、`stdout`、`stderr` 字段。未知字段、非法类型和不可编译正则 MUST 导致启动期配置错误。`expect.exitCode` SHALL 保留原有有限整数数组语义,不限制到特定平台范围。
|
||||
@@ -1,25 +0,0 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: YAML 配置文件格式
|
||||
target MUST 使用 `type` 字段声明 checker 类型,HTTP 领域字段 MUST 放在 `http` 分组,cmd 领域字段 MUST 放在 `cmd` 分组。
|
||||
|
||||
#### Scenario: 最简 command 配置文件解析
|
||||
- **WHEN** 系统读取只包含一个 `type: cmd` target 和 `cmd.exec` 的 YAML 配置文件
|
||||
- **THEN** 系统 SHALL 使用内置默认值填充未指定的字段(interval=30s, timeout=10s, cmd.cwd 为配置文件所在目录, cmd.maxOutputBytes=100MB)
|
||||
|
||||
### Requirement: 配置校验
|
||||
|
||||
#### Scenario: command target 缺少 exec
|
||||
- **WHEN** YAML 中某个 target 配置 `type: cmd` 但缺少 `cmd.exec`
|
||||
- **THEN** 系统 SHALL 以错误退出,提示该 target 缺少 cmd.exec 字段
|
||||
|
||||
#### Scenario: 动态 env 字段允许
|
||||
- **WHEN** YAML 中 `cmd.env` 包含任意环境变量名称,且对应值为字符串
|
||||
- **THEN** 系统 SHALL 接受这些动态 env 名称
|
||||
|
||||
### Requirement: expect 配置增强
|
||||
系统 SHALL 支持 typed target 的领域专用 expect 配置,包括 HTTP 的 `status`、`headers`、`body` 和 cmd 的 `exitCode`、`stdout`、`stderr`。
|
||||
|
||||
#### Scenario: 解析 command expect 配置
|
||||
- **WHEN** YAML 配置文件中 cmd target 的 expect 包含 exitCode、stdout 和 stderr 规则数组
|
||||
- **THEN** 系统 SHALL 正确解析并存储为 cmd target 的 expect 字段
|
||||
@@ -1,41 +0,0 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: 命令检测器测试 SHALL 使用跨平台命令
|
||||
命令检测器的测试 SHALL 使用 `bun -e` 脚本替代所有系统命令(包括 `true`、`false`、`sleep`、`bash`、`echo`、`yes | head`),确保测试在 Windows、macOS、Linux 三平台上行为一致。
|
||||
|
||||
#### Scenario: 进程退出码 0
|
||||
- **WHEN** 测试需要一个正常退出的命令
|
||||
- **THEN** 测试 SHALL 使用 `bun -e "process.exit(0)"` 替代 `true`
|
||||
|
||||
#### Scenario: 进程退出码非零
|
||||
- **WHEN** 测试需要一个失败退出的命令
|
||||
- **THEN** 测试 SHALL 使用 `bun -e "process.exit(1)"` 替代 `false`
|
||||
|
||||
#### Scenario: stdout 输出
|
||||
- **WHEN** 测试需要一个输出文本到 stdout 的命令
|
||||
- **THEN** 测试 SHALL 使用 `bun -e "console.log('text')"` 替代 `echo text`
|
||||
|
||||
#### Scenario: stderr 输出
|
||||
- **WHEN** 测试需要一个输出文本到 stderr 的命令
|
||||
- **THEN** 测试 SHALL 使用 `bun -e "process.stderr.write('error\n')"` 替代 `bash -c "echo error >&2"`
|
||||
|
||||
#### Scenario: 长时间运行命令
|
||||
- **WHEN** 测试需要一个超时场景的长时间运行命令
|
||||
- **THEN** 测试 SHALL 使用 `bun -e "await Bun.sleep(10000)"` 替代 `sleep 10`
|
||||
|
||||
#### Scenario: 大量输出
|
||||
- **WHEN** 测试需要一个产生大量输出的命令
|
||||
- **THEN** 测试 SHALL 使用 `bun -e "process.stdout.write('y\n'.repeat(N))"` 替代 `bash -c "yes | head -N"`
|
||||
|
||||
#### Scenario: 验证非 shell 模式下特殊字符不被展开
|
||||
- **WHEN** 通过 `Bun.spawn` 执行 `bun -e "console.log('*')"` 并检查 stdout 包含 `*`
|
||||
- **THEN** 测试 SHALL 在 Windows、macOS 和 Linux 上均返回 `matched: true`
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: probes.example.yaml 使用跨平台示例
|
||||
probes.example.yaml 中的 cmd 类型示例 SHALL 使用跨平台命令(如 `bun -e "..."`、`bun --version`),不使用 Unix 专属命令(如 `uname`、`ls /tmp`、`date`)。
|
||||
|
||||
#### Scenario: 示例命令跨平台可执行
|
||||
- **WHEN** 用户在 Windows、macOS 或 Linux 上直接使用 probes.example.yaml 中的 cmd 示例
|
||||
- **THEN** 所有 cmd 示例 SHALL 能正常执行,不依赖平台特定命令
|
||||
@@ -1,27 +0,0 @@
|
||||
## 1. 源码目录重命名
|
||||
|
||||
- [ ] 1.1 重命名 `src/server/checker/runner/command/` → `src/server/checker/runner/cmd/`,更新目录内所有文件的 type/configKey 字面量为 `"cmd"`
|
||||
- [ ] 1.2 重命名 `tests/server/checker/runner/command/` → `tests/server/checker/runner/cmd/`
|
||||
- [ ] 1.3 更新所有 import 路径中的 `runner/command` → `runner/cmd`(包括 runner/index.ts 等)
|
||||
|
||||
## 2. 类型与配置重命名
|
||||
|
||||
- [ ] 2.1 更新 `src/server/checker/runner/cmd/execute.ts` 中 `type = "cmd"`、`configKey = "cmd"`、`context.defaults["cmd"]`、所有 `t.command.xxx` → `t.cmd.xxx`
|
||||
- [ ] 2.2 更新 `src/server/checker/runner/cmd/types.ts` 中 `ResolvedCommandTarget.command` 属性名改为 `cmd`、`type: "command"` 改为 `type: "cmd"`
|
||||
- [ ] 2.3 更新 `src/server/checker/runner/cmd/validate.ts` 中所有 `"command"` → `"cmd"` 字面量
|
||||
- [ ] 2.4 更新 `src/server/checker/runner/cmd/schema.ts` 中 TypeBox 契约的分组名(如有 `"command"` 字面量)
|
||||
- [ ] 2.5 更新 `probes.example.yaml` 中 `type: command` → `type: cmd`、`command:` → `cmd:`,示例命令改为跨平台命令
|
||||
- [ ] 2.6 更新 `tests/server/app.test.ts`、`tests/server/bootstrap.test.ts`、`tests/server/checker/config-loader.test.ts`、`tests/server/checker/engine.test.ts` 中所有 `"command"` 字面量为 `"cmd"`
|
||||
- [ ] 2.7 重新生成 `probe-config.schema.json`(执行 schema 生成脚本或手动更新)
|
||||
|
||||
## 3. 跨平台测试改造
|
||||
|
||||
- [ ] 3.1 更新 `tests/server/checker/runner/cmd/runner.test.ts` 中所有系统命令为 `bun -e "..."` 形式
|
||||
- [ ] 3.2 更新 `tests/server/checker/runner/cmd/expect.test.ts` 中所有系统命令为 `bun -e "..."` 形式
|
||||
|
||||
## 4. Spec 文档与质量保障
|
||||
|
||||
- [ ] 4.1 重命名 `openspec/specs/command-checker/` → `openspec/specs/cmd-checker/`,更新 spec 内容中的 `command` → `cmd`
|
||||
- [ ] 4.2 执行完整测试套件 `bun test`,确保所有测试通过
|
||||
- [ ] 4.3 执行类型检查 `bunx tsc --noEmit`,确保无类型错误
|
||||
- [ ] 4.4 更新 README.md 中涉及 command checker 的描述和配置示例(包括 defaults.command 段、type 枚举、配置字段说明)
|
||||
Reference in New Issue
Block a user