1
0

refactor: 引入 Checker 统一接口与 Runner 抽象机制

定义 Checker 接口(resolve/execute/serialize)和 CheckerRegistry
注册中心,消除 engine/config-loader/store 中硬编码类型分支。
按 checker 类型分子包(runner/http/、runner/command/),提取
共享 expect 到 runner/shared/。超时控制通过引擎注入 AbortSignal。
CheckFailure.phase 从联合类型改为 string。配置校验下沉到各
Checker.resolve() 内部。

新增 checker-runner-abstraction spec,更新 DEVELOPMENT.md。
This commit is contained in:
2026-05-12 17:08:57 +08:00
parent e1c33b4002
commit ce8baae3d1
41 changed files with 1493 additions and 1395 deletions

View File

@@ -1,32 +1,19 @@
import type {
CommandDefaultsConfig,
CommandTargetConfig,
DefaultsConfig,
HttpDefaultsConfig,
HttpExpectConfig,
HttpTargetConfig,
ProbeConfig,
ResolvedCommandTarget,
ResolvedHttpTarget,
ResolvedTarget,
EngineRuntimeConfig,
TargetConfig,
TargetType,
} from "./types";
import { parseSize } from "./size";
import { resolve } from "node:path";
import { dirname } from "node:path";
import { dirname, resolve } from "node:path";
import { checkerRegistry } from "./runner";
const DEFAULT_HOST = "127.0.0.1";
const DEFAULT_PORT = 3000;
const DEFAULT_DATA_DIR = "./data";
const DEFAULT_INTERVAL = "30s";
const DEFAULT_TIMEOUT = "10s";
const DEFAULT_HTTP_METHOD = "GET";
const DEFAULT_MAX_BODY_BYTES = "100MB";
const DEFAULT_MAX_OUTPUT_BYTES = "100MB";
const DEFAULT_MAX_CONCURRENT_CHECKS = 20;
const SUPPORTED_TYPES: TargetType[] = ["http", "command"];
export interface ResolvedConfig {
host: string;
@@ -100,73 +87,14 @@ function resolveTarget(
): ResolvedTarget {
const intervalMs = parseDuration(target.interval ?? defaults.interval ?? DEFAULT_INTERVAL);
const timeoutMs = parseDuration(target.timeout ?? defaults.timeout ?? DEFAULT_TIMEOUT);
const group = target.group ?? "default";
if (target.type === "http") {
return resolveHttpTarget(target, defaults.http, intervalMs, timeoutMs, group);
}
const checker = checkerRegistry.get(target.type);
const result = checker.resolve(target, { defaults, configDir, defaultIntervalMs, defaultTimeoutMs });
return resolveCommandTarget(target, defaults.command, intervalMs, timeoutMs, configDir, group);
}
result.intervalMs = intervalMs;
result.timeoutMs = timeoutMs;
function resolveHttpTarget(
target: TargetConfig & { type: "http"; http: HttpTargetConfig },
httpDefaults: HttpDefaultsConfig | undefined,
intervalMs: number,
timeoutMs: number,
group: string,
): ResolvedHttpTarget {
const maxBodyBytes = parseSize(target.http.maxBodyBytes ?? httpDefaults?.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES);
return {
type: "http",
name: target.name,
group,
http: {
url: target.http.url,
method: target.http.method ?? httpDefaults?.method ?? DEFAULT_HTTP_METHOD,
headers: { ...(httpDefaults?.headers ?? {}), ...(target.http.headers ?? {}) },
body: target.http.body,
maxBodyBytes,
},
intervalMs,
timeoutMs,
expect: target.expect as HttpExpectConfig | undefined,
};
}
function resolveCommandTarget(
target: TargetConfig & { type: "command"; command: CommandTargetConfig },
commandDefaults: CommandDefaultsConfig | undefined,
intervalMs: number,
timeoutMs: number,
configDir: string,
group: string,
): ResolvedCommandTarget {
const cwd = target.command.cwd ?? commandDefaults?.cwd ?? ".";
const resolvedCwd = resolve(configDir, cwd);
const maxOutputBytes = parseSize(
target.command.maxOutputBytes ?? commandDefaults?.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES,
);
const env = { ...process.env, ...(target.command.env ?? {}) } as Record<string, string>;
return {
type: "command",
name: target.name,
group,
command: {
exec: target.command.exec,
args: target.command.args ?? [],
cwd: resolvedCwd,
env,
maxOutputBytes,
},
intervalMs,
timeoutMs,
expect: target.expect as import("./types").CommandExpectConfig | undefined,
};
return result;
}
function validateConfig(config: ProbeConfig): void {
@@ -175,6 +103,7 @@ function validateConfig(config: ProbeConfig): void {
}
const names = new Set<string>();
const supportedTypes = checkerRegistry.supportedTypes;
for (let i = 0; i < config.targets.length; i++) {
const raw = config.targets[i] as unknown as Record<string, unknown>;
@@ -189,22 +118,8 @@ function validateConfig(config: ProbeConfig): void {
throw new Error(`target "${name}" 缺少 type 字段`);
}
if (!SUPPORTED_TYPES.includes(type as TargetType)) {
throw new Error(`target "${name}" 使用不支持的 type: "${type}",支持: ${SUPPORTED_TYPES.join(", ")}`);
}
if (type === "http") {
const http = raw["http"] as Record<string, unknown> | undefined;
if (!http?.["url"] || typeof http["url"] !== "string" || (http["url"] as string).trim() === "") {
throw new Error(`target "${name}" 缺少 http.url 字段`);
}
}
if (type === "command") {
const cmd = raw["command"] as Record<string, unknown> | undefined;
if (!cmd?.["exec"] || typeof cmd["exec"] !== "string" || (cmd["exec"] as string).trim() === "") {
throw new Error(`target "${name}" 缺少 command.exec 字段`);
}
if (!supportedTypes.includes(type)) {
throw new Error(`target "${name}" 使用不支持的 type: "${type}",支持: ${supportedTypes.join(", ")}`);
}
const group = raw["group"];