Files
2026-03-24 18:34:56 +08:00

196 lines
4.5 KiB
JavaScript

import path from "node:path";
import { spawnSync } from "node:child_process";
import {
ensureKnownSuite,
formatSuiteListing,
getChangedFiles,
getCommandsForSuites,
pnpmBin,
repoRoot,
selectSuitesForChangedFiles,
suiteDefinitions,
writeHarnessArtifact
} from "./core.mjs";
function parseArgs(argv) {
const options = {
changedFiles: [],
dryRun: false,
from: null,
list: false,
to: null,
suite: "component"
};
for (let index = 0; index < argv.length; index += 1) {
const current = argv[index];
if (current === "--dry-run") {
options.dryRun = true;
continue;
}
if (current === "--list") {
options.list = true;
continue;
}
if (
current === "--changed-file" ||
current === "--from" ||
current === "--suite" ||
current === "--to"
) {
const next = argv[index + 1];
if (!next) {
throw new Error(`Expected a value after ${current}.`);
}
if (current === "--changed-file") {
options.changedFiles.push(next);
} else if (current === "--from") {
options.from = next;
} else if (current === "--to") {
options.to = next;
} else {
options.suite = next;
}
index += 1;
continue;
}
if (current === "--") {
continue;
}
throw new Error(`Unknown argument: ${current}`);
}
return options;
}
function runCommand(command) {
const startedAt = new Date().toISOString();
const startTime = Date.now();
process.stdout.write(`\n[harness] ${command.label}\n`);
process.stdout.write(`[harness] pnpm ${command.args.join(" ")}\n\n`);
const result = spawnSync(pnpmBin, command.args, {
cwd: repoRoot,
shell: false,
stdio: "inherit"
});
return {
command: `pnpm ${command.args.join(" ")}`,
durationMs: Date.now() - startTime,
exitCode: result.status ?? 1,
label: command.label,
startedAt,
status: result.status === 0 ? "passed" : "failed"
};
}
function printSelection(selection) {
if (selection.suites.length === 0) {
process.stdout.write("[harness] No suites selected for the current diff.\n");
if (selection.unmatchedFiles.length > 0) {
process.stdout.write("[harness] Unmatched files:\n");
for (const filePath of selection.unmatchedFiles) {
process.stdout.write(` - ${filePath}\n`);
}
}
return;
}
process.stdout.write("[harness] Selected suites from changed files:\n");
for (const suiteName of selection.suites) {
process.stdout.write(`- ${suiteName}\n`);
for (const reason of selection.reasons[suiteName] ?? []) {
process.stdout.write(` - ${reason.file}: ${reason.reason}\n`);
}
}
if (selection.unmatchedFiles.length > 0) {
process.stdout.write("[harness] Unmatched files:\n");
for (const filePath of selection.unmatchedFiles) {
process.stdout.write(` - ${filePath}\n`);
}
}
}
const options = parseArgs(process.argv.slice(2));
if (options.list) {
process.stdout.write("Available harness suites:\n");
for (const suite of formatSuiteListing()) {
process.stdout.write(`- ${suite.name}: ${suite.description}\n`);
}
process.exit(0);
}
ensureKnownSuite(options.suite);
const selection =
options.suite === "changed"
? selectSuitesForChangedFiles(getChangedFiles(options))
: null;
const selectedSuites = selection?.suites ?? [options.suite];
const commands = getCommandsForSuites(selectedSuites);
const report = {
commands: [],
description:
options.suite === "changed"
? "Validate suites selected from the current git diff or working tree."
: suiteDefinitions[options.suite].description,
finishedAt: null,
startedAt: new Date().toISOString(),
status: options.dryRun ? "dry-run" : "passed",
suite: options.suite,
selectedSuites
};
if (selection) {
report.selection = selection;
printSelection(selection);
}
for (const command of commands) {
if (options.dryRun) {
report.commands.push({
command: `pnpm ${command.args.join(" ")}`,
label: command.label,
status: "planned",
suite: command.suite
});
continue;
}
const result = runCommand(command);
report.commands.push(result);
if (result.status === "failed") {
report.status = "failed";
break;
}
}
report.finishedAt = new Date().toISOString();
const reportPath = writeHarnessArtifact(options.suite, report);
process.stdout.write(`\n[harness] Report written to ${path.relative(repoRoot, reportPath)}\n`);
if (report.status === "failed") {
process.exit(1);
}