From 7f7f0775ed7d3a5aee35852dcbf9c220f377d20a Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Mon, 1 Jun 2026 20:30:03 +0200 Subject: [PATCH] fix(testing): keep crabbox sync checkouts durable --- scripts/crabbox-wrapper.mjs | 29 +++++++++++++++---- test/scripts/crabbox-wrapper.test.ts | 42 ++++++++++++++++++++++++---- 2 files changed, 60 insertions(+), 11 deletions(-) diff --git a/scripts/crabbox-wrapper.mjs b/scripts/crabbox-wrapper.mjs index 0cf76a30080d..90f7e4fc6045 100755 --- a/scripts/crabbox-wrapper.mjs +++ b/scripts/crabbox-wrapper.mjs @@ -13,7 +13,7 @@ import { statSync, writeFileSync, } from "node:fs"; -import { tmpdir } from "node:os"; +import { homedir, tmpdir } from "node:os"; import { delimiter, dirname, extname, isAbsolute, relative, resolve } from "node:path"; import { fileURLToPath } from "node:url"; import { resolvePathEnvKey } from "./windows-cmd-helpers.mjs"; @@ -1540,7 +1540,9 @@ function isWindowsRemoteTarget(commandArgs) { } function isNativeWindowsRemoteTarget(commandArgs) { - return isWindowsRemoteTarget(commandArgs) && optionValue(commandArgs, "--windows-mode") !== "wsl2"; + return ( + isWindowsRemoteTarget(commandArgs) && optionValue(commandArgs, "--windows-mode") !== "wsl2" + ); } function isAwsMacosRemoteTarget(commandArgs, providerName) { @@ -1553,14 +1555,14 @@ function isAwsMacosRemoteTarget(commandArgs, providerName) { function remoteWindowsHydratedNodeModulesBootstrap() { return [ - '$openclawModulesDir = $env:PNPM_CONFIG_MODULES_DIR', - 'if ($openclawModulesDir) {', + "$openclawModulesDir = $env:PNPM_CONFIG_MODULES_DIR", + "if ($openclawModulesDir) {", 'if (-not (Test-Path $openclawModulesDir)) { throw "PNPM_CONFIG_MODULES_DIR does not exist: $openclawModulesDir" }', '$openclawWorkspaceModules = Join-Path (Get-Location).Path "node_modules"', '$openclawSelfModules = Join-Path $openclawModulesDir "node_modules"', 'if (-not (Test-Path $openclawSelfModules)) { cmd /c mklink /J "$openclawSelfModules" "$openclawModulesDir" | Out-Host; if ($LASTEXITCODE -ne 0) { throw "failed to link hydrated pnpm node_modules" } }', 'if (-not (Test-Path $openclawWorkspaceModules)) { cmd /c mklink /J "$openclawWorkspaceModules" "$openclawModulesDir" | Out-Host; if ($LASTEXITCODE -ne 0) { throw "failed to link workspace node_modules" } }', - '}', + "}", ].join("; "); } @@ -1894,8 +1896,23 @@ function shouldUseFullCheckoutForCleanRemoteSync(commandArgs, _providerName) { return isSparseCheckout() || isChangedGateCommand(runCommandArgs(commandArgs)); } +function defaultFullCheckoutSyncRoot() { + const home = homedir(); + if (home) { + return resolve(home, ".cache", "openclaw", "crabbox-sync"); + } + return resolve(tmpdir(), "openclaw-crabbox-sync"); +} + +function fullCheckoutSyncRoot() { + const configured = process.env.OPENCLAW_CRABBOX_SYNC_TMPDIR?.trim(); + const root = configured ? resolve(configured) : defaultFullCheckoutSyncRoot(); + mkdirSync(root, { recursive: true }); + return root; +} + function prepareFullCheckoutForSync(options = {}) { - const dir = mkdtempSync(resolve(tmpdir(), "openclaw-crabbox-sync-")); + const dir = mkdtempSync(resolve(fullCheckoutSyncRoot(), "openclaw-crabbox-sync-")); let active = false; const add = gitOutput(["worktree", "add", "--detach", dir, "HEAD"]); if (add.status !== 0) { diff --git a/test/scripts/crabbox-wrapper.test.ts b/test/scripts/crabbox-wrapper.test.ts index 8ffd5c55a43b..a2a907e798f3 100644 --- a/test/scripts/crabbox-wrapper.test.ts +++ b/test/scripts/crabbox-wrapper.test.ts @@ -1,5 +1,13 @@ import { spawnSync } from "node:child_process"; -import { chmodSync, mkdirSync, mkdtempSync, rmSync, statSync, writeFileSync } from "node:fs"; +import { + chmodSync, + mkdirSync, + mkdtempSync, + readdirSync, + rmSync, + statSync, + writeFileSync, +} from "node:fs"; import { tmpdir } from "node:os"; import path from "node:path"; import { afterAll, beforeAll, describe, expect, it } from "vitest"; @@ -182,6 +190,7 @@ function makeFakeGit( " exit 0", "fi", 'if [ "$1" = "worktree" ] && [ "$2" = "remove" ]; then', + ' rm -rf "$4"', " exit 0", "fi", ...Object.entries(responses).flatMap(([key, response]) => { @@ -209,7 +218,7 @@ function makeFakeGit( "if (args[0] === 'worktree' && args[1] === 'add') { fs.mkdirSync(args[3], { recursive: true }); process.exit(0); }", "if (args[0] === '-C' && args[2] === 'sparse-checkout' && args[3] === 'disable') { process.exit(0); }", "if (args[0] === '-C' && args[2] === 'reset' && args[3] === '--mixed') { process.exit(0); }", - "if (args[0] === 'worktree' && args[1] === 'remove') { process.exit(0); }", + "if (args[0] === 'worktree' && args[1] === 'remove') { fs.rmSync(args[3], { recursive: true, force: true }); process.exit(0); }", "const key = args.join('\\u0000');", "const response = responses.get(key);", "if (!response) { process.exit(1); }", @@ -1517,9 +1526,7 @@ describe.concurrent("scripts/crabbox-wrapper", () => { expect(output.args).toContain("--shell"); expect(remoteCommand).toContain("$openclawModulesDir = $env:PNPM_CONFIG_MODULES_DIR"); expect(remoteCommand).toContain('mklink /J "$openclawSelfModules" "$openclawModulesDir"'); - expect(remoteCommand).toContain( - 'mklink /J "$openclawWorkspaceModules" "$openclawModulesDir"', - ); + expect(remoteCommand).toContain('mklink /J "$openclawWorkspaceModules" "$openclawModulesDir"'); expect(remoteCommand).toContain("corepack pnpm check:changed"); }); @@ -2390,6 +2397,31 @@ describe.concurrent("scripts/crabbox-wrapper", () => { expect(parseFakeCrabboxOutput(result).cwd).toContain("openclaw-crabbox-sync-"); }); + it("creates sparse-sync temporary full checkouts under the durable cache root", () => { + const syncRoot = path.join(repoRoot, ".crabbox-test-sync-root"); + rmSync(syncRoot, { recursive: true, force: true }); + try { + const result = runWrapper( + "provider: hetzner, aws, local-container, blacksmith-testbox, or cloudflare\n", + ["run", "--provider", "aws", "--", "echo ok"], + { + env: { OPENCLAW_CRABBOX_SYNC_TMPDIR: syncRoot }, + gitResponses: { + [GIT_CONFIG_SPARSE_KEY]: { stdout: "true\n" }, + [GIT_STATUS_PORCELAIN_KEY]: { stdout: "" }, + }, + }, + ); + + const output = parseFakeCrabboxOutput(result); + expect(result.status).toBe(0); + expect(output.cwd).toContain(`${syncRoot}${path.sep}openclaw-crabbox-sync-`); + expect(readdirSync(syncRoot)).toEqual([]); + } finally { + rmSync(syncRoot, { recursive: true, force: true }); + } + }); + it("uses a temporary full checkout when existing AWS leases sync clean sparse worktrees", () => { const result = runWrapper( "provider: hetzner, aws, local-container, blacksmith-testbox, or cloudflare\n",