























@@ -40,7 +40,9 @@ function writeFakeCrabbox(binDir: string, helpText: string): string {
4040return crabboxPath;
4141}
424243-function makeFakeGit(responses: Record<string, { status?: number; stdout?: string; stderr?: string }>): string {
43+function makeFakeGit(
44+responses: Record<string, { status?: number; stdout?: string; stderr?: string }>,
45+): string {
4446const binDir = mkdtempSync(path.join(tmpdir(), "openclaw-fake-git-"));
4547tempDirs.push(binDir);
4648const gitPath = path.join(binDir, "git");
@@ -91,7 +93,10 @@ function runWrapper(
9193});
9294}
939594-function parseFakeCrabboxOutput(result: ReturnType<typeof runWrapper>): { args: string[]; cwd: string } {
96+function parseFakeCrabboxOutput(result: ReturnType<typeof runWrapper>): {
97+args: string[];
98+cwd: string;
99+} {
95100return JSON.parse(result.stdout.trim()) as { args: string[]; cwd: string };
96101}
97102@@ -261,11 +266,9 @@ describe("scripts/crabbox-wrapper", () => {
261266const staleBinDir = mkdtempSync(path.join(tmpdir(), "openclaw-stale-crabbox-"));
262267tempDirs.push(staleBinDir);
263268writeFileSync(path.join(staleBinDir, "crabbox"), "not executable\n", "utf8");
264-const result = runWrapper(
265-"provider: aws\n",
266-["run", "--provider", "aws", "--", "echo ok"],
267-{ extraPathEntries: [staleBinDir] },
268-);
269+const result = runWrapper("provider: aws\n", ["run", "--provider", "aws", "--", "echo ok"], {
270+extraPathEntries: [staleBinDir],
271+});
269272270273expect(result.status).toBe(0);
271274expect(parseFakeCrabboxOutput(result).args).toContain("aws");
@@ -411,6 +414,96 @@ describe("scripts/crabbox-wrapper", () => {
411414expect(parseFakeCrabboxOutput(result).cwd).toContain("openclaw-crabbox-sync-");
412415});
413416417+it("bootstraps Git metadata for sparse changed gates on remote raw syncs", () => {
418+const result = runWrapper(
419+"provider: hetzner, aws, local-container, blacksmith-testbox, or cloudflare\n",
420+["run", "--provider", "aws", "--", "corepack", "pnpm", "check:changed"],
421+{
422+gitResponses: {
423+["config\u0000--bool\u0000core.sparseCheckout"]: { stdout: "true\n" },
424+["status\u0000--porcelain=v1"]: { stdout: "" },
425+["merge-base\u0000origin/main\u0000HEAD"]: { stdout: "abc123\n" },
426+},
427+},
428+);
429+430+const output = parseFakeCrabboxOutput(result);
431+const remoteCommand = output.args.at(-1) ?? "";
432+expect(result.status).toBe(0);
433+expect(output.args).toContain("--shell");
434+expect(remoteCommand).toContain("git init -q");
435+expect(remoteCommand).toContain(
436+"git fetch -q --depth=1 origin abc123:refs/remotes/origin/main",
437+);
438+expect(remoteCommand).toContain("git reset --mixed --quiet refs/remotes/origin/main");
439+expect(remoteCommand).toContain("git add -A");
440+expect(remoteCommand).toContain("git diff --cached --quiet");
441+expect(remoteCommand).toContain("commit -q --no-gpg-sign -m remote-changed-gate-tree");
442+expect(remoteCommand).toMatch(/&& corepack pnpm check:changed$/u);
443+});
444+445+it("preserves existing shell changed-gate commands after remote Git bootstrap", () => {
446+const result = runWrapper(
447+"provider: hetzner, aws, local-container, blacksmith-testbox, or cloudflare\n",
448+["run", "--provider", "aws", "--shell", "--", "env CI=1 pnpm check:changed"],
449+{
450+gitResponses: {
451+["config\u0000--bool\u0000core.sparseCheckout"]: { stdout: "true\n" },
452+["status\u0000--porcelain=v1"]: { stdout: "" },
453+["merge-base\u0000origin/main\u0000HEAD"]: { stdout: "abc123\n" },
454+},
455+},
456+);
457+458+const output = parseFakeCrabboxOutput(result);
459+const remoteCommand = output.args.at(-1) ?? "";
460+expect(result.status).toBe(0);
461+expect(output.args.filter((arg) => arg === "--shell")).toHaveLength(1);
462+expect(remoteCommand).toContain(
463+"git fetch -q --depth=1 origin abc123:refs/remotes/origin/main",
464+);
465+expect(remoteCommand).toMatch(/&& env CI=1 pnpm check:changed$/u);
466+});
467+468+it("does not inject the POSIX changed-gate bootstrap for Windows targets", () => {
469+const result = runWrapper(
470+"provider: hetzner, aws, local-container, blacksmith-testbox, or cloudflare\n",
471+[
472+"run",
473+"--provider",
474+"aws",
475+"--target",
476+"windows",
477+"--",
478+"corepack",
479+"pnpm",
480+"check:changed",
481+],
482+{
483+gitResponses: {
484+["config\u0000--bool\u0000core.sparseCheckout"]: { stdout: "true\n" },
485+["status\u0000--porcelain=v1"]: { stdout: "" },
486+["merge-base\u0000origin/main\u0000HEAD"]: { stdout: "abc123\n" },
487+},
488+},
489+);
490+491+const output = parseFakeCrabboxOutput(result);
492+expect(result.status).toBe(0);
493+expect(output.args).not.toContain("--shell");
494+expect(output.args).toEqual([
495+"run",
496+"--provider",
497+"aws",
498+"--target",
499+"windows",
500+"--",
501+"corepack",
502+"pnpm",
503+"check:changed",
504+]);
505+});
506+414507it("keeps clean sparse local-container syncs on the original checkout", () => {
415508const result = runWrapper(
416509"provider: hetzner, aws, local-container, blacksmith-testbox, or cloudflare\n",
@@ -508,7 +601,9 @@ describe("scripts/crabbox-wrapper", () => {
508601const output = parseFakeCrabboxOutput(result);
509602expect(result.status).toBe(0);
510603expect(output.cwd).toContain("openclaw-crabbox-sync-");
511-expect(output.args).toContain(`--capture-stdout=${path.join(repoRoot, ".artifacts/stdout.log")}`);
604+expect(output.args).toContain(
605+`--capture-stdout=${path.join(repoRoot, ".artifacts/stdout.log")}`,
606+);
512607expect(output.args).toContain(path.join(repoRoot, ".artifacts/stderr.log"));
513608expect(output.args).toContain(`/tmp/proof=${path.join(repoRoot, ".artifacts/proof")}`);
514609});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。