151d776842
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
196 lines
4.5 KiB
JavaScript
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);
|
|
}
|