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); }