























@@ -1,7 +1,8 @@
11#!/usr/bin/env node
22import { spawn, spawnSync } from "node:child_process";
3-import { existsSync, readFileSync } from "node:fs";
4-import { dirname, relative, resolve } from "node:path";
3+import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
4+import { tmpdir } from "node:os";
5+import { dirname, isAbsolute, relative, resolve } from "node:path";
56import { fileURLToPath } from "node:url";
6778const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
@@ -29,6 +30,19 @@ function checkedOutput(command, commandArgs) {
2930};
3031}
313233+function gitOutput(commandArgs) {
34+const result = spawnSync("git", commandArgs, {
35+cwd: repoRoot,
36+encoding: "utf8",
37+stdio: ["ignore", "pipe", "pipe"],
38+});
39+return {
40+status: result.status ?? 1,
41+text: `${result.stdout ?? ""}${result.stderr ?? ""}`.trim(),
42+stdout: (result.stdout ?? "").trim(),
43+};
44+}
45+3246function configuredProvider() {
3347const envProvider = process.env.CRABBOX_PROVIDER?.trim();
3448if (envProvider) {
@@ -257,6 +271,64 @@ function hasOption(commandArgs, name) {
257271return false;
258272}
259273274+const localPathRunOptions = new Set([
275+"capture-stderr",
276+"capture-stdout",
277+"emit-proof",
278+"env-from-profile",
279+"script",
280+]);
281+282+function repoRelativePath(value) {
283+if (!value || value === "-" || isAbsolute(value)) {
284+return value;
285+}
286+return resolve(repoRoot, value);
287+}
288+289+function repoRelativeDownload(value) {
290+const split = value.indexOf("=");
291+if (split < 0) {
292+return value;
293+}
294+const remote = value.slice(0, split + 1);
295+const local = value.slice(split + 1);
296+return `${remote}${repoRelativePath(local)}`;
297+}
298+299+function absolutizeLocalRunPaths(commandArgs) {
300+if (commandArgs[0] !== "run") {
301+return commandArgs;
302+}
303+304+const normalizedArgs = [...commandArgs];
305+const { optionEnd } = runCommandBounds(normalizedArgs);
306+for (let index = 1; index < optionEnd; index += 1) {
307+const arg = normalizedArgs[index];
308+if (!arg.startsWith("-")) {
309+continue;
310+}
311+312+const optionName = runOptionName(arg);
313+const absolutize = optionName === "download" ? repoRelativeDownload : repoRelativePath;
314+if (localPathRunOptions.has(optionName) || optionName === "download") {
315+const equals = arg.indexOf("=");
316+if (equals >= 0) {
317+normalizedArgs[index] = `${arg.slice(0, equals + 1)}${absolutize(arg.slice(equals + 1))}`;
318+} else if (index + 1 < optionEnd) {
319+normalizedArgs[index + 1] = absolutize(normalizedArgs[index + 1]);
320+index += 1;
321+}
322+continue;
323+}
324+325+if (!arg.includes("=") && currentRunValueOptions().has(optionName)) {
326+index += 1;
327+}
328+}
329+return normalizedArgs;
330+}
331+260332function isLocalContainerProvider(providerName) {
261333return ["local-container", "docker", "container", "local-docker"].includes(providerName);
262334}
@@ -284,6 +356,69 @@ function commandRuntimeEntrypoint(commandArgs) {
284356return ["pnpm", "npm", "npx", "corepack", "node", "yarn", "bun"].includes(first) ? first : "";
285357}
286358359+function isSparseCheckout() {
360+const config = gitOutput(["config", "--bool", "core.sparseCheckout"]);
361+if (config.status === 0 && config.stdout === "true") {
362+return true;
363+}
364+const patterns = gitOutput(["sparse-checkout", "list"]);
365+return patterns.status === 0 && patterns.stdout.length > 0;
366+}
367+368+function isWorktreeClean() {
369+return gitOutput(["status", "--porcelain=v1"]).stdout === "";
370+}
371+372+function shouldUseFullCheckoutForCleanSparseBlacksmithSync(commandArgs, providerName) {
373+if (commandArgs[0] !== "run" || providerName !== "blacksmith-testbox") {
374+return false;
375+}
376+if (
377+hasOption(commandArgs, "--no-sync") ||
378+hasOption(commandArgs, "--id")
379+) {
380+return false;
381+}
382+383+return isSparseCheckout() && isWorktreeClean();
384+}
385+386+function prepareFullCheckoutForSync() {
387+const dir = mkdtempSync(resolve(tmpdir(), "openclaw-crabbox-sync-"));
388+let active = false;
389+const add = gitOutput(["worktree", "add", "--detach", dir, "HEAD"]);
390+if (add.status !== 0) {
391+rmSync(dir, { recursive: true, force: true });
392+throw new Error(`git worktree add failed: ${add.text}`);
393+}
394+active = true;
395+396+const disableSparse = gitOutput(["-C", dir, "sparse-checkout", "disable"]);
397+if (disableSparse.status !== 0) {
398+cleanupFullCheckout(dir, active);
399+throw new Error(`git sparse-checkout disable failed: ${disableSparse.text}`);
400+}
401+402+return {
403+ dir,
404+cleanup() {
405+cleanupFullCheckout(dir, active);
406+active = false;
407+},
408+};
409+}
410+411+function cleanupFullCheckout(dir, active) {
412+if (active) {
413+const remove = gitOutput(["worktree", "remove", "--force", dir]);
414+if (remove.status === 0) {
415+return;
416+}
417+console.error(`[crabbox] warning: git worktree remove failed for ${dir}: ${remove.text}`);
418+}
419+rmSync(dir, { recursive: true, force: true });
420+}
421+287422const version = checkedOutput(binary, ["--version"]);
288423const help = checkedOutput(binary, ["run", "--help"]);
289424const providerAliases = new Map([
@@ -419,6 +554,26 @@ if (provider === "blacksmith-testbox") {
419554);
420555}
421556557+let childCwd = repoRoot;
558+let cleanupChildCwd = () => {};
559+let cleanupDone = false;
560+if (shouldUseFullCheckoutForCleanSparseBlacksmithSync(args, provider)) {
561+const checkout = prepareFullCheckoutForSync();
562+childCwd = checkout.dir;
563+cleanupChildCwd = () => checkout.cleanup();
564+console.error(
565+`[crabbox] sparse clean checkout detected; syncing from temporary full checkout ${checkout.dir}`,
566+);
567+}
568+569+function cleanupOnce() {
570+if (cleanupDone) {
571+return;
572+}
573+cleanupDone = true;
574+cleanupChildCwd();
575+}
576+422577const runtimeEntrypoint = commandRuntimeEntrypoint(runCommandArgs(args));
423578if (args[0] === "run" && provider === "aws" && runtimeEntrypoint) {
424579const id = optionValue(args, "--id");
@@ -453,21 +608,40 @@ if (
453608);
454609}
455610456-const child = spawn(binary, args, {
457-cwd: repoRoot,
611+const childArgs = childCwd === repoRoot ? args : absolutizeLocalRunPaths(args);
612+const child = spawn(binary, childArgs, {
613+cwd: childCwd,
458614stdio: "inherit",
459615env: childEnv,
460616});
461617618+const signalExitCodes = new Map([
619+["SIGHUP", 129],
620+["SIGINT", 130],
621+["SIGTERM", 143],
622+]);
623+for (const signal of signalExitCodes.keys()) {
624+process.once(signal, () => {
625+if (!child.killed) {
626+child.kill(signal);
627+}
628+cleanupOnce();
629+process.exit(signalExitCodes.get(signal) ?? 1);
630+});
631+}
632+process.once("exit", cleanupOnce);
633+462634child.on("exit", (code, signal) => {
635+cleanupOnce();
463636if (signal) {
464-process.kill(process.pid, signal);
637+process.exit(signalExitCodes.get(signal) ?? 1);
465638return;
466639}
467640process.exit(code ?? 1);
468641});
469642470643child.on("error", (error) => {
644+cleanupOnce();
471645console.error(`[crabbox] failed to execute ${displayBinary}: ${error.message}`);
472646process.exit(2);
473647});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。