Files
pikasTech-HWLAB/scripts/src/check-runner-lib.mjs
T
2026-05-29 01:34:39 +08:00

197 lines
6.5 KiB
JavaScript

import { spawn } from "node:child_process";
import { existsSync } from "node:fs";
import os from "node:os";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { CHECK_PLAN_VERSION, checkProfiles, listCheckTasks } from "./check-plan.mjs";
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
export async function runCheckRunner(argv, io = process) {
const options = parseCheckRunnerArgs(argv);
if (options.help) {
writeJson(io.stdout, helpPayload());
return { exitCode: 0 };
}
const selected = selectTasks(options);
const payload = {
ok: true,
command: "check-runner",
version: CHECK_PLAN_VERSION,
profile: options.profile,
groups: options.groups,
ids: options.ids,
taskCount: selected.length,
tasks: selected.map(serializeTask)
};
if (options.list || options.dryRun) {
writeJson(io.stdout, { ...payload, mode: options.list ? "list" : "dry-run" });
return { exitCode: 0 };
}
writeJson(io.stdout, { ...payload, mode: "run-start", tasks: undefined });
const startedAt = Date.now();
for (let index = 0; index < selected.length; index += 1) {
const task = selected[index];
const resolved = resolveTaskCommand(task);
writeJson(io.stdout, {
ok: true,
event: "task-start",
index: index + 1,
total: selected.length,
task: serializeTask(task),
resolvedCommand: resolved.command
});
const exitCode = await runTask(resolved);
if (exitCode !== 0) {
writeJson(io.stdout, {
ok: false,
event: "task-failed",
task: serializeTask(task),
exitCode,
elapsedMs: Date.now() - startedAt
});
return { exitCode };
}
}
writeJson(io.stdout, {
ok: true,
event: "run-complete",
profile: options.profile,
taskCount: selected.length,
elapsedMs: Date.now() - startedAt
});
return { exitCode: 0 };
}
export function parseCheckRunnerArgs(argv) {
const options = { profile: "check", groups: [], ids: [], list: false, dryRun: false, help: false };
for (let index = 0; index < argv.length; index += 1) {
const arg = argv[index];
const readValue = (name) => {
const inline = arg.match(new RegExp(`^${name}=(.+)$`, "u"));
if (inline) return inline[1];
const value = argv[index + 1];
if (!value || value.startsWith("--")) throw new Error(`${name} requires a value`);
index += 1;
return value;
};
if (arg === "--help" || arg === "-h") options.help = true;
else if (arg === "--profile" || arg.startsWith("--profile=")) options.profile = readValue("--profile");
else if (arg === "--group" || arg.startsWith("--group=")) options.groups.push(...readValue("--group").split(",").filter(Boolean));
else if (arg === "--id" || arg.startsWith("--id=")) options.ids.push(...readValue("--id").split(",").filter(Boolean));
else if (arg === "--list") options.list = true;
else if (arg === "--dry-run") options.dryRun = true;
else throw new Error(`unknown check-runner argument: ${arg}`);
}
if (!checkProfiles[options.profile]) throw new Error(`unknown check profile: ${options.profile}`);
return options;
}
export function selectTasks(options) {
const groups = new Set(options.groups);
const ids = new Set(options.ids);
const profileTasks = listCheckTasks(options.profile);
const availableGroups = new Set(profileTasks.map((task) => task.group));
const availableIds = new Set(profileTasks.map((task) => task.id));
for (const group of groups) {
if (!availableGroups.has(group)) throw new Error(`unknown check group for profile ${options.profile}: ${group}`);
}
for (const id of ids) {
if (!availableIds.has(id)) throw new Error(`unknown check task id for profile ${options.profile}: ${id}`);
}
const selected = profileTasks.filter((task) => {
if (groups.size > 0 && !groups.has(task.group)) return false;
if (ids.size > 0 && !ids.has(task.id)) return false;
return true;
});
if (selected.length === 0) throw new Error(`check-runner selected no tasks for profile ${options.profile}`);
return selected;
}
export function resolveTaskCommand(task) {
const [bin, ...args] = task.command;
if (!bin) throw new Error(`task has empty command: ${task.id}`);
const cwd = task.cwd ? path.resolve(repoRoot, task.cwd) : repoRoot;
return {
task,
command: bin === "bun" ? resolveBunCommand(cwd) : bin,
args,
cwd
};
}
function runTask(task) {
return new Promise((resolve) => {
const child = spawn(task.command, task.args, {
cwd: task.cwd,
env: process.env,
stdio: ["ignore", "inherit", "inherit"]
});
child.on("error", (error) => {
process.stderr.write(JSON.stringify({ ok: false, event: "task-spawn-error", task: serializeTask(task.task), error: error.message }, null, 2) + "\n");
resolve(127);
});
child.on("exit", (code, signal) => {
if (signal) {
process.stderr.write(JSON.stringify({ ok: false, event: "task-signal", task: serializeTask(task.task), signal }, null, 2) + "\n");
resolve(128);
return;
}
resolve(code ?? 0);
});
});
}
function resolveBunCommand(cwd) {
for (const candidate of bunCandidates(cwd)) {
if (!candidate) continue;
if (!candidate.includes(path.sep)) return candidate;
if (existsSync(candidate)) return candidate;
}
return "bun";
}
function bunCandidates(cwd) {
const exe = process.platform === "win32" ? "bun.exe" : "bun";
return [
process.env.HWLAB_BUN_COMMAND,
process.env.HWLAB_BUN_BINARY,
process.env.BUN_BINARY,
path.join(cwd, "node_modules", ".bin", exe),
path.join(repoRoot, "node_modules", ".bin", exe),
path.join(os.homedir(), ".bun", "bin", exe),
"/root/.bun/bin/bun",
"/usr/local/bin/bun",
"bun"
].filter(Boolean);
}
function serializeTask(task) {
return { id: task.id, group: task.group, cwd: task.cwd ?? ".", command: task.command };
}
function writeJson(stream, value) {
stream.write(JSON.stringify(value, null, 2) + "\n");
}
function helpPayload() {
return {
ok: true,
command: "check-runner",
usage: [
"node scripts/check-runner.mjs --profile check",
"node scripts/check-runner.mjs --profile validate",
"node scripts/check-runner.mjs --profile check --group cloud-api",
"node scripts/check-runner.mjs --profile check --list",
"node scripts/check-runner.mjs --profile check --dry-run"
],
profiles: Object.keys(checkProfiles),
groups: [...new Set(Object.values(checkProfiles).flat().map((task) => task.group))].sort()
};
}