Skip to content

Commit 53ad531

Browse files
committed
fix(crabbox): preserve sparse run artifacts
1 parent 78c5eea commit 53ad531

2 files changed

Lines changed: 85 additions & 1 deletion

File tree

scripts/crabbox-wrapper.mjs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@ import {
44
accessSync,
55
chmodSync,
66
constants,
7+
cpSync,
8+
mkdirSync,
79
mkdtempSync,
810
readFileSync,
11+
readdirSync,
912
rmSync,
1013
statSync,
1114
writeFileSync,
@@ -585,6 +588,42 @@ function absolutizeLocalRunPaths(commandArgs) {
585588
return normalizedArgs;
586589
}
587590

591+
function pathExists(path) {
592+
try {
593+
statSync(path);
594+
return true;
595+
} catch {
596+
return false;
597+
}
598+
}
599+
600+
function preserveTemporaryCrabboxRuns() {
601+
if (childCwd === repoRoot) {
602+
return;
603+
}
604+
605+
const sourceRuns = resolve(childCwd, ".crabbox", "runs");
606+
if (!pathExists(sourceRuns)) {
607+
return;
608+
}
609+
610+
const targetRuns = resolve(repoRoot, ".crabbox", "runs");
611+
mkdirSync(targetRuns, { recursive: true });
612+
let preserved = 0;
613+
for (const entry of readdirSync(sourceRuns)) {
614+
cpSync(resolve(sourceRuns, entry), resolve(targetRuns, entry), {
615+
recursive: true,
616+
force: true,
617+
});
618+
preserved += 1;
619+
}
620+
if (preserved > 0) {
621+
console.error(
622+
`[crabbox] preserved ${preserved} temporary run artifact ${preserved === 1 ? "directory" : "directories"} under ${relative(repoRoot, targetRuns)}`,
623+
);
624+
}
625+
}
626+
588627
function shellQuote(value) {
589628
const text = `${value}`;
590629
if (text === "") {
@@ -1861,6 +1900,7 @@ function cleanupOnce() {
18611900
}
18621901
cleanupDone = true;
18631902
scriptBootstrap.cleanup();
1903+
preserveTemporaryCrabboxRuns();
18641904
cleanupChildCwd();
18651905
}
18661906

test/scripts/crabbox-wrapper.test.ts

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { spawnSync } from "node:child_process";
2-
import { chmodSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
2+
import { chmodSync, mkdirSync, mkdtempSync, rmSync, statSync, writeFileSync } from "node:fs";
33
import { tmpdir } from "node:os";
44
import path from "node:path";
55
import { afterAll, beforeAll, describe, expect, it } from "vitest";
@@ -53,6 +53,12 @@ function writeFakeCrabbox(binDir: string, helpText: string): string {
5353
" fi",
5454
" done",
5555
"fi",
56+
'for arg in "$@"; do',
57+
' if [ "$arg" = "--artifact-glob" ] || [ "$arg" = "-artifact-glob" ]; then',
58+
" mkdir -p .crabbox/runs/run_fake",
59+
' printf "%s\\n" "fake artifact" > .crabbox/runs/run_fake/fake-artifacts.tgz',
60+
" fi",
61+
"done",
5662
'script_path=""',
5763
'previous_arg=""',
5864
'for arg in "$@"; do',
@@ -91,6 +97,10 @@ function writeFakeCrabbox(binDir: string, helpText: string): string {
9197
"const scriptIndex = args.findIndex((arg) => arg === '--script' || arg === '-script');",
9298
"const scriptPath = scriptIndex >= 0 ? args[scriptIndex + 1] : '';",
9399
"const scriptContent = scriptPath ? require('node:fs').readFileSync(scriptPath, 'utf8') : '';",
100+
"if (args.includes('--artifact-glob') || args.includes('-artifact-glob')) {",
101+
" require('node:fs').mkdirSync('.crabbox/runs/run_fake', { recursive: true });",
102+
" require('node:fs').writeFileSync('.crabbox/runs/run_fake/fake-artifacts.tgz', 'fake artifact\\n');",
103+
"}",
94104
"console.log(JSON.stringify({ args, cwd: process.cwd(), scriptContent }));",
95105
].join("\n");
96106
writeFileSync(helperPath, `${helperScript}\n`, "utf8");
@@ -2234,6 +2244,40 @@ describe.concurrent("scripts/crabbox-wrapper", () => {
22342244
expect(output.args).toContain(`/tmp/proof=${path.join(repoRoot, ".artifacts/proof")}`);
22352245
});
22362246

2247+
it("preserves artifact-glob downloads from temporary sparse-sync checkouts", () => {
2248+
const preservedDir = path.join(repoRoot, ".crabbox", "runs", "run_fake");
2249+
rmSync(preservedDir, { recursive: true, force: true });
2250+
2251+
const result = runWrapper(
2252+
"provider: hetzner, aws, local-container, blacksmith-testbox, or cloudflare\n",
2253+
[
2254+
"run",
2255+
"--provider",
2256+
"blacksmith-testbox",
2257+
"--blacksmith-ref",
2258+
"main",
2259+
"--artifact-glob",
2260+
".artifacts/proof/**",
2261+
"--",
2262+
"echo ok",
2263+
],
2264+
{
2265+
gitResponses: {
2266+
["config\u0000--bool\u0000core.sparseCheckout"]: { stdout: "true\n" },
2267+
["status\u0000--porcelain=v1"]: { stdout: "" },
2268+
},
2269+
},
2270+
);
2271+
2272+
const output = parseFakeCrabboxOutput(result);
2273+
expect(result.status).toBe(0);
2274+
expect(output.cwd).toContain("openclaw-crabbox-sync-");
2275+
expect(result.stderr).toContain("syncing from temporary full checkout");
2276+
expect(result.stderr).toContain("preserved");
2277+
expect(statSync(path.join(preservedDir, "fake-artifacts.tgz")).isFile()).toBe(true);
2278+
rmSync(preservedDir, { recursive: true, force: true });
2279+
});
2280+
22372281
it("uses the temporary full checkout for sparse sync-only runs", () => {
22382282
const result = runWrapper(
22392283
"provider: hetzner, aws, local-container, blacksmith-testbox, or cloudflare\n",

0 commit comments

Comments
 (0)