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:
@@ -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"];
|
||||
|
||||
Reference in New Issue
Block a user