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:
@@ -14,11 +14,11 @@ import { checkTextRules } from "./text";
|
||||
import { validateCommandConfig } from "./validate";
|
||||
|
||||
export class CommandChecker implements CheckerDefinition<ResolvedCommandTarget> {
|
||||
readonly configKey = "command";
|
||||
readonly configKey = "cmd";
|
||||
|
||||
readonly schemas = commandCheckerSchemas;
|
||||
|
||||
readonly type = "command";
|
||||
readonly type = "cmd";
|
||||
|
||||
async execute(t: ResolvedCommandTarget, ctx: CheckerContext): Promise<CheckResult> {
|
||||
const timestamp = new Date().toISOString();
|
||||
@@ -27,9 +27,9 @@ export class CommandChecker implements CheckerDefinition<ResolvedCommandTarget>
|
||||
let proc: ReturnType<typeof Bun.spawn>;
|
||||
|
||||
try {
|
||||
proc = Bun.spawn([t.command.exec, ...t.command.args], {
|
||||
cwd: t.command.cwd,
|
||||
env: t.command.env,
|
||||
proc = Bun.spawn([t.cmd.exec, ...t.cmd.args], {
|
||||
cwd: t.cmd.cwd,
|
||||
env: t.cmd.env,
|
||||
stderr: "pipe",
|
||||
stdin: "ignore",
|
||||
stdout: "pipe",
|
||||
@@ -65,7 +65,7 @@ export class CommandChecker implements CheckerDefinition<ResolvedCommandTarget>
|
||||
proc.stdout as ReadableStream<Uint8Array>,
|
||||
proc.stderr as ReadableStream<Uint8Array>,
|
||||
() => proc.kill(),
|
||||
t.command.maxOutputBytes,
|
||||
t.cmd.maxOutputBytes,
|
||||
);
|
||||
} catch {
|
||||
const durationMs = Math.round(performance.now() - start);
|
||||
@@ -87,7 +87,7 @@ export class CommandChecker implements CheckerDefinition<ResolvedCommandTarget>
|
||||
if (outputResult.exceeded) {
|
||||
return {
|
||||
durationMs,
|
||||
failure: errorFailure("exitCode", "output", `输出超过限制 ${t.command.maxOutputBytes} 字节`),
|
||||
failure: errorFailure("exitCode", "output", `输出超过限制 ${t.cmd.maxOutputBytes} 字节`),
|
||||
matched: false,
|
||||
statusDetail: `exitCode=${exitCode}`,
|
||||
targetName: t.name,
|
||||
@@ -169,22 +169,22 @@ export class CommandChecker implements CheckerDefinition<ResolvedCommandTarget>
|
||||
}
|
||||
|
||||
resolve(target: RawTargetConfig, context: ResolveContext): ResolvedCommandTarget {
|
||||
const t = target as RawTargetConfig & { command: CommandTargetConfig; type: "command" };
|
||||
const commandDefaults = context.defaults["command"] as undefined | { cwd?: string; maxOutputBytes?: string };
|
||||
const t = target as RawTargetConfig & { cmd: CommandTargetConfig; type: "cmd" };
|
||||
const cmdDefaults = context.defaults["cmd"] as undefined | { cwd?: string; maxOutputBytes?: string };
|
||||
|
||||
const cwd = t.command.cwd ?? commandDefaults?.cwd ?? ".";
|
||||
const cwd = t.cmd.cwd ?? cmdDefaults?.cwd ?? ".";
|
||||
const resolvedCwd = resolve(context.configDir, cwd);
|
||||
|
||||
const maxOutputBytes = parseSize(t.command.maxOutputBytes ?? commandDefaults?.maxOutputBytes ?? "100MB");
|
||||
const maxOutputBytes = parseSize(t.cmd.maxOutputBytes ?? cmdDefaults?.maxOutputBytes ?? "100MB");
|
||||
|
||||
const env = { ...process.env, ...(t.command.env ?? {}) } as Record<string, string>;
|
||||
const env = { ...process.env, ...(t.cmd.env ?? {}) } as Record<string, string>;
|
||||
|
||||
return {
|
||||
command: {
|
||||
args: t.command.args ?? [],
|
||||
cmd: {
|
||||
args: t.cmd.args ?? [],
|
||||
cwd: resolvedCwd,
|
||||
env,
|
||||
exec: t.command.exec,
|
||||
exec: t.cmd.exec,
|
||||
maxOutputBytes,
|
||||
},
|
||||
expect: target.expect as CommandExpectConfig | undefined,
|
||||
@@ -192,19 +192,19 @@ export class CommandChecker implements CheckerDefinition<ResolvedCommandTarget>
|
||||
intervalMs: context.defaultIntervalMs,
|
||||
name: t.name,
|
||||
timeoutMs: context.defaultTimeoutMs,
|
||||
type: "command",
|
||||
type: "cmd",
|
||||
} satisfies ResolvedCommandTarget;
|
||||
}
|
||||
|
||||
serialize(t: ResolvedCommandTarget): { config: string; target: string } {
|
||||
const parts = [t.command.exec, ...t.command.args];
|
||||
const parts = [t.cmd.exec, ...t.cmd.args];
|
||||
return {
|
||||
config: JSON.stringify({
|
||||
args: t.command.args,
|
||||
cwd: t.command.cwd,
|
||||
env: t.command.env,
|
||||
exec: t.command.exec,
|
||||
maxOutputBytes: t.command.maxOutputBytes,
|
||||
args: t.cmd.args,
|
||||
cwd: t.cmd.cwd,
|
||||
env: t.cmd.env,
|
||||
exec: t.cmd.exec,
|
||||
maxOutputBytes: t.cmd.maxOutputBytes,
|
||||
}),
|
||||
target: `exec ${parts.join(" ")}`,
|
||||
};
|
||||
@@ -29,13 +29,13 @@ export interface ResolvedCommandConfig {
|
||||
}
|
||||
|
||||
export interface ResolvedCommandTarget extends ResolvedTargetBase {
|
||||
command: ResolvedCommandConfig;
|
||||
cmd: ResolvedCommandConfig;
|
||||
expect?: CommandExpectConfig;
|
||||
group: string;
|
||||
intervalMs: number;
|
||||
name: string;
|
||||
timeoutMs: number;
|
||||
type: "command";
|
||||
type: "cmd";
|
||||
}
|
||||
|
||||
export type TextRule = ExpectOperator;
|
||||
@@ -7,17 +7,16 @@ import { parseSize } from "../../utils";
|
||||
|
||||
export function validateCommandConfig(input: CheckerValidationInput): ConfigValidationIssue[] {
|
||||
const issues: ConfigValidationIssue[] = [];
|
||||
const defaults =
|
||||
isRecord(input.defaults) && isRecord(input.defaults["command"]) ? input.defaults["command"] : undefined;
|
||||
const defaults = isRecord(input.defaults) && isRecord(input.defaults["cmd"]) ? input.defaults["cmd"] : undefined;
|
||||
|
||||
if (isSizeInput(defaults?.["maxOutputBytes"])) {
|
||||
issues.push(...validateSizeValue(defaults["maxOutputBytes"], "defaults.command.maxOutputBytes"));
|
||||
issues.push(...validateSizeValue(defaults["maxOutputBytes"], "defaults.cmd.maxOutputBytes"));
|
||||
}
|
||||
|
||||
for (let i = 0; i < input.targets.length; i++) {
|
||||
const target = input.targets[i] as unknown;
|
||||
if (!isRecord(target)) continue;
|
||||
if (target["type"] !== "command") continue;
|
||||
if (target["type"] !== "cmd") continue;
|
||||
issues.push(...validateCommandTarget(target, `targets[${i}]`));
|
||||
}
|
||||
|
||||
@@ -61,22 +60,18 @@ function validateCommandExpect(target: Record<string, unknown>, path: string): C
|
||||
function validateCommandTarget(target: Record<string, unknown>, path: string): ConfigValidationIssue[] {
|
||||
const issues: ConfigValidationIssue[] = [];
|
||||
const targetName = getTargetName(target);
|
||||
const command = target["command"];
|
||||
if (!isRecord(command)) {
|
||||
issues.push(issue("required", joinPath(path, "command"), "缺少 command.exec 字段", targetName));
|
||||
const cmd = target["cmd"];
|
||||
if (!isRecord(cmd)) {
|
||||
issues.push(issue("required", joinPath(path, "cmd"), "缺少 cmd.exec 字段", targetName));
|
||||
issues.push(...validateCommandExpect(target, path));
|
||||
return issues;
|
||||
}
|
||||
if (typeof command["exec"] !== "string" || command["exec"].trim() === "") {
|
||||
issues.push(issue("required", joinPath(joinPath(path, "command"), "exec"), "缺少 command.exec 字段", targetName));
|
||||
if (typeof cmd["exec"] !== "string" || cmd["exec"].trim() === "") {
|
||||
issues.push(issue("required", joinPath(joinPath(path, "cmd"), "exec"), "缺少 cmd.exec 字段", targetName));
|
||||
}
|
||||
if (isSizeInput(command["maxOutputBytes"])) {
|
||||
if (isSizeInput(cmd["maxOutputBytes"])) {
|
||||
issues.push(
|
||||
...validateSizeValue(
|
||||
command["maxOutputBytes"],
|
||||
joinPath(joinPath(path, "command"), "maxOutputBytes"),
|
||||
targetName,
|
||||
),
|
||||
...validateSizeValue(cmd["maxOutputBytes"], joinPath(joinPath(path, "cmd"), "maxOutputBytes"), targetName),
|
||||
);
|
||||
}
|
||||
issues.push(...validateCommandExpect(target, path));
|
||||
@@ -1,4 +1,4 @@
|
||||
import { CommandChecker } from "./command";
|
||||
import { CommandChecker } from "./cmd";
|
||||
import { HttpChecker } from "./http";
|
||||
import { CheckerRegistry } from "./registry";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user