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:
@@ -58,7 +58,7 @@ src/
|
||||
registry.ts CheckerRegistry 注册中心
|
||||
index.ts 注册入口(显式数组 + 循环注册)
|
||||
http/ HTTP Checker(自包含模块,含 types/schema/execute/expect/validate/body)
|
||||
command/ Command Checker(自包含模块,含 types/schema/execute/expect/validate/text)
|
||||
cmd/ Cmd Checker(自包含模块,含 types/schema/execute/expect/validate/text)
|
||||
shared/
|
||||
api.ts 前后端共享 TypeScript 类型
|
||||
web/ React 前端 Dashboard(通过 Bun HTML import 集成)
|
||||
@@ -84,7 +84,7 @@ openspec/ OpenSpec 变更与规格文档
|
||||
probe-config.schema.json 用户配置 JSON Schema 导出物(用于 IDE 自动补全和校验)
|
||||
```
|
||||
|
||||
> **说明**:`runner/http/` 和 `runner/command/` 的完整文件结构见 [1.7.1 架构总览](#171-架构总览) 中的标准文件表。
|
||||
> **说明**:`runner/http/` 和 `runner/cmd/` 的完整文件结构见 [1.7.1 架构总览](#171-架构总览) 中的标准文件表。
|
||||
|
||||
## 前后端边界
|
||||
|
||||
@@ -227,7 +227,7 @@ export function handleTrend(idStr: string, url: URL, store: ProbeStore, mode: Ru
|
||||
|
||||
契约层使用 `src/server/checker/schema/` 中的 TypeBox fragments 生成 JSON Schema,并用 Ajv 执行启动期校验。Ajv 必须保持严格拒绝模式:`allErrors: true`、不启用类型强制转换、不注入默认值、不自动删除未知字段。
|
||||
|
||||
默认对象策略是 `additionalProperties: false`。只有明确声明的动态键值表可以开放任意键名,例如 `http.headers`、`defaults.http.headers`、`expect.headers`、`command.env`。
|
||||
默认对象策略是 `additionalProperties: false`。只有明确声明的动态键值表可以开放任意键名,例如 `http.headers`、`defaults.http.headers`、`expect.headers`、`cmd.env`。
|
||||
|
||||
契约校验和语义 validator 都必须返回 `ConfigValidationIssue[]`,不要在 validator 内直接拼接最终用户错误字符串。最终错误由 `formatConfigIssues()` 统一渲染,错误路径需要尽量包含 `targetName` 或 `defaults`/root 路径。
|
||||
|
||||
@@ -266,11 +266,11 @@ checkerRegistry(单例)
|
||||
| `validate.ts` | 启动期语义校验(JSON Schema 无法表达的规则) |
|
||||
| `execute.ts` | Checker 类:resolve(默认值合并 + 解析)、execute(执行检查)、serialize(DB 持久化) |
|
||||
| `expect.ts` | Checker 专用断言函数 |
|
||||
| `*.ts` | 其他 checker 专属逻辑(如 http/body.ts、command/text.ts) |
|
||||
| `*.ts` | 其他 checker 专属逻辑(如 http/body.ts、cmd/text.ts) |
|
||||
|
||||
#### 1.7.2 步骤一:创建 Checker 目录与类型
|
||||
|
||||
在 `src/server/checker/runner/tcp/types.ts` 中定义 checker 专属类型(参考 `http/types.ts`、`command/types.ts`):
|
||||
在 `src/server/checker/runner/tcp/types.ts` 中定义 checker 专属类型(参考 `http/types.ts`、`cmd/types.ts`):
|
||||
|
||||
- `XxxTargetConfig` — YAML 原始配置类型
|
||||
- `XxxExpectConfig` — expect 字段类型
|
||||
@@ -281,7 +281,7 @@ checkerRegistry(单例)
|
||||
|
||||
#### 1.7.3 步骤二:创建 TypeBox 契约 Schema
|
||||
|
||||
在 `src/server/checker/runner/tcp/schema.ts` 中定义 `CheckerSchemas`(config / defaults / expect 三部分)。参考 `http/schema.ts`、`command/schema.ts`,使用 `schema/fragments.ts` 中的共享片段。
|
||||
在 `src/server/checker/runner/tcp/schema.ts` 中定义 `CheckerSchemas`(config / defaults / expect 三部分)。参考 `http/schema.ts`、`cmd/schema.ts`,使用 `schema/fragments.ts` 中的共享片段。
|
||||
|
||||
**可复用的共享 fragments**(来自 `schema/fragments.ts`):
|
||||
|
||||
@@ -296,11 +296,11 @@ checkerRegistry(单例)
|
||||
| `createPureOperatorSchema()` | 操作符对象 |
|
||||
| `operatorProperties()` | 所有操作符字段的 Record |
|
||||
|
||||
**注意**:默认对象策略为 `additionalProperties: false`。只有明确的动态键值表(如 `http.headers`、`command.env`)可以开放任意键名。
|
||||
**注意**:默认对象策略为 `additionalProperties: false`。只有明确的动态键值表(如 `http.headers`、`cmd.env`)可以开放任意键名。
|
||||
|
||||
#### 1.7.4 步骤三:实现语义校验
|
||||
|
||||
在 `src/server/checker/runner/tcp/validate.ts` 中实现 JSON Schema 无法表达的语义规则(参考 `http/validate.ts`、`command/validate.ts`)。函数签名统一为:
|
||||
在 `src/server/checker/runner/tcp/validate.ts` 中实现 JSON Schema 无法表达的语义规则(参考 `http/validate.ts`、`cmd/validate.ts`)。函数签名统一为:
|
||||
|
||||
```typescript
|
||||
export function validateTcpConfig(input: CheckerValidationInput): ConfigValidationIssue[];
|
||||
@@ -315,7 +315,7 @@ export function validateTcpConfig(input: CheckerValidationInput): ConfigValidati
|
||||
|
||||
#### 1.7.5 步骤四:实现 Checker 类
|
||||
|
||||
在 `src/server/checker/runner/tcp/execute.ts` 中实现 `CheckerDefinition` 接口的全部成员(参考 `http/execute.ts`、`command/execute.ts`):
|
||||
在 `src/server/checker/runner/tcp/execute.ts` 中实现 `CheckerDefinition` 接口的全部成员(参考 `http/execute.ts`、`cmd/execute.ts`):
|
||||
|
||||
```
|
||||
TcpChecker implements Checker
|
||||
@@ -356,7 +356,7 @@ TcpChecker implements Checker
|
||||
| `operator.ts` | `evaluateJsonPath(json, path)` | JSONPath 提取 |
|
||||
| `validate-operator.ts` | `validateOperatorObject(ops, path, name)` | 操作符语义校验 |
|
||||
|
||||
**Checker 专属断言**(如需要)放在同目录的 `expect.ts` 中,参考 `http/expect.ts`(checkStatus、checkHeaders)和 `command/expect.ts`(checkExitCode)。
|
||||
**Checker 专属断言**(如需要)放在同目录的 `expect.ts` 中,参考 `http/expect.ts`(checkStatus、checkHeaders)和 `cmd/expect.ts`(checkExitCode)。
|
||||
|
||||
#### 1.7.6 步骤五:创建模块入口并注册
|
||||
|
||||
@@ -466,7 +466,7 @@ TcpChecker implements Checker
|
||||
- **调度**:`ProbeEngine` 用 `es-toolkit/groupBy` 按 interval 分组,每组独立 `setInterval` 定时触发
|
||||
- **并发控制**:`es-toolkit/Semaphore` 限制全局最大并发数(`maxConcurrentChecks`,默认 20),`acquire()` 阻塞等待
|
||||
- **Runner 选择**:`engine.runCheck()` 通过 `checkerRegistry.get(target.type)` 获取 checker,并调用 `checker.execute(target, { signal })`
|
||||
- **超时控制**:`ProbeEngine` 为每次检查创建 `AbortController` 并按 `target.timeoutMs` 触发 abort;checker 必须使用 `CheckerContext.signal` 感知超时,HTTP 将 signal 传给 `fetch()`,Command 在 signal abort 时 `proc.kill()`
|
||||
- **超时控制**:`ProbeEngine` 为每次检查创建 `AbortController` 并按 `target.timeoutMs` 触发 abort;checker 必须使用 `CheckerContext.signal` 感知超时,HTTP 将 signal 传给 `fetch()`,Cmd 在 signal abort 时 `proc.kill()`
|
||||
- **结果写入**:检查结果通过 `store.insertCheckResult()` 写入 SQLite,engine 通过 `targetNameToId` 缓存 name→id 映射
|
||||
- **异常可观测**:`probeGroup()` 对 `Promise.allSettled` 的 rejected 结果通过索引关联 target,并写入 `phase:"internal"` 的失败记录
|
||||
- **数据清理**:当 `retentionMs > 0` 时,engine 启动时立即执行一次 `store.prune()`,之后每小时定时执行,按 `timestamp` 清理过期数据
|
||||
@@ -486,7 +486,7 @@ HttpChecker.execute → 收集观测(statusCode/headers)
|
||||
|
||||
HTTP checker 的 `durationMs` 覆盖完整执行(含重定向、响应体读取、解码和 expect 校验)。status 或 headers 失败时不读取 body;进入 body 前若已超过 `maxDurationMs`,直接返回 duration failure。
|
||||
|
||||
**Command 校验流程**:
|
||||
**Cmd 校验流程**:
|
||||
|
||||
```
|
||||
CommandChecker.execute → 收集观测(exitCode/stdout/stderr/durationMs)
|
||||
@@ -502,7 +502,7 @@ CommandChecker.execute → 收集观测(exitCode/stdout/stderr/durationMs)
|
||||
- `css`:cheerio CSS 选择器 + 操作符比较
|
||||
- `xpath`:XPath 节点提取 + 操作符比较
|
||||
|
||||
**文本规则**(`runner/command/text.ts`):stdout/stderr 文本匹配,支持 `contains`、`match`(正则)、操作符比较
|
||||
**文本规则**(`runner/cmd/text.ts`):stdout/stderr 文本匹配,支持 `contains`、`match`(正则)、操作符比较
|
||||
|
||||
**操作符**(`expect/operator.ts`):`equals`(深度比较,`es-toolkit/isEqual`)、`contains`、`match`(正则,启动期通过 `expect/redos.ts` 拒绝 ReDoS 风险模式)、`empty`(`isNil`+`isEmptyObject`)、`exists`、`gte`/`lte`/`gt`/`lt`
|
||||
|
||||
@@ -522,7 +522,7 @@ CommandChecker.execute → 收集观测(exitCode/stdout/stderr/durationMs)
|
||||
- `tests/server/checker/runner/shared/duration.test.ts` ↔ `src/server/checker/expect/duration.ts`
|
||||
- `tests/server/checker/runner/shared/operator.test.ts` ↔ `src/server/checker/expect/operator.ts`
|
||||
- `tests/server/checker/runner/shared/body.test.ts` ↔ `src/server/checker/runner/http/body.ts`
|
||||
- `tests/server/checker/runner/shared/text.test.ts` ↔ `src/server/checker/runner/command/text.ts`
|
||||
- `tests/server/checker/runner/shared/text.test.ts` ↔ `src/server/checker/runner/cmd/text.ts`
|
||||
- 使用 `bun:test` 框架(`describe`/`test`/`expect`),测试数据库用临时目录 + `tmpdir()`
|
||||
- 新增 store 方法必须编写单元测试;新增 API 端点必须在 `app.test.ts` 中添加集成测试
|
||||
- 测试后清理:`afterAll` 中 `store.close()` + `rm(tempDir, { recursive: true })`
|
||||
@@ -983,4 +983,4 @@ bun run verify # 完整验证(check + 构建)
|
||||
|
||||
## 已知限制
|
||||
|
||||
当前不做告警通知、拨测目标动态增删、认证鉴权和分布式部署。Command 类型拨测不支持 Windows 环境。
|
||||
当前不做告警通知、拨测目标动态增删、认证鉴权和分布式部署。
|
||||
|
||||
Reference in New Issue
Block a user