mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-25 09:41:04 +01:00
114 lines
3.5 KiB
JavaScript
114 lines
3.5 KiB
JavaScript
import { spawnSync } from "node:child_process";
|
|
import { mkdirSync, writeFileSync, readFileSync, existsSync } from "node:fs";
|
|
import { createHash } from "node:crypto";
|
|
import { resolve, join } from "node:path";
|
|
import { lanes } from "./inventory.mjs";
|
|
import { summarize, markdown } from "./report.mjs";
|
|
const root = resolve(import.meta.dirname, "../..");
|
|
const args = process.argv.slice(2);
|
|
const selected = args.filter((a) => !a.startsWith("--"));
|
|
if (selected.some((l) => !(l in lanes)))
|
|
throw new Error(`Use layers: ${Object.keys(lanes).join(", ")}`);
|
|
const layers = selected.length ? selected : Object.keys(lanes);
|
|
if (args.includes("--list")) {
|
|
console.log(
|
|
JSON.stringify(
|
|
{
|
|
layers: Object.fromEntries(layers.map((l) => [l, lanes[l]])),
|
|
live: "never invoked by this command",
|
|
},
|
|
null,
|
|
2,
|
|
),
|
|
);
|
|
process.exit(0);
|
|
}
|
|
const git = (...args) =>
|
|
spawnSync("git", args, { cwd: root, encoding: "utf8" }).stdout;
|
|
const stamp = new Date().toISOString().replaceAll(":", "-");
|
|
const output = join(root, ".lifecycle-baseline", stamp);
|
|
mkdirSync(output, { recursive: true });
|
|
const results = {};
|
|
const execution = {};
|
|
let failed = false;
|
|
for (const layer of layers) {
|
|
const file = join(output, `${layer}.json`);
|
|
const result = spawnSync(
|
|
process.execPath,
|
|
[
|
|
join(root, "node_modules/vitest/vitest.mjs"),
|
|
"run",
|
|
"--config",
|
|
"tests/lifecycle-baseline/vitest.config.ts",
|
|
"--reporter=default",
|
|
"--reporter=json",
|
|
`--outputFile.json=${file}`,
|
|
],
|
|
{
|
|
cwd: root,
|
|
env: {
|
|
...process.env,
|
|
LIFECYCLE_BASELINE_LAYER: layer,
|
|
LIFECYCLE_BASELINE_OBSERVATIONS: join(
|
|
output,
|
|
`${layer}-observations.jsonl`,
|
|
),
|
|
},
|
|
stdio: "inherit",
|
|
},
|
|
);
|
|
if (existsSync(file)) results[layer] = JSON.parse(readFileSync(file, "utf8"));
|
|
else
|
|
results[layer] = {
|
|
testResults: [],
|
|
launchError:
|
|
result.error?.message ??
|
|
`exit ${result.status}, signal ${result.signal}`,
|
|
};
|
|
execution[layer] = {
|
|
exitCode: result.status,
|
|
signal: result.signal,
|
|
success: results[layer].success ?? false,
|
|
total: results[layer].numTotalTests ?? 0,
|
|
passed: results[layer].numPassedTests ?? 0,
|
|
failed: results[layer].numFailedTests ?? 0,
|
|
pending: results[layer].numPendingTests ?? 0,
|
|
launchError: results[layer].launchError ?? null,
|
|
};
|
|
if (result.status !== 0) failed = true;
|
|
}
|
|
const hash = createHash("sha256")
|
|
.update(git("rev-parse", "HEAD"))
|
|
.update(git("diff", "HEAD"));
|
|
// Include untracked authored tests too; staged/unstaged source differences remain inspectable.
|
|
for (const path of git("ls-files", "--others", "--exclude-standard")
|
|
.trim()
|
|
.split("\n")
|
|
.filter(Boolean)
|
|
.sort()) {
|
|
hash.update(path).update(readFileSync(join(root, path)));
|
|
}
|
|
const report = {
|
|
schema: "paperclip.lifecycle-baseline/v1",
|
|
commit: git("rev-parse", "HEAD").trim(),
|
|
fingerprint: hash.digest("hex"),
|
|
measuredAt: new Date().toISOString(),
|
|
layers,
|
|
execution,
|
|
scenarios: summarize(results),
|
|
};
|
|
writeFileSync(
|
|
join(output, "baseline.json"),
|
|
JSON.stringify(report, null, 2) + "\n",
|
|
);
|
|
writeFileSync(join(output, "baseline.md"), markdown(report));
|
|
writeFileSync(join(output, "source.diff"), git("diff", "HEAD"));
|
|
console.log(`Baseline retained at ${output}/baseline.md`);
|
|
if (
|
|
report.scenarios.some((s) =>
|
|
s.coverage.some((c) => !["pass", "not_run"].includes(c.status)),
|
|
)
|
|
)
|
|
failed = true;
|
|
process.exitCode = failed ? 1 : 0;
|