mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 05:51:15 +08:00
fix(test): sync sparse AWS Crabbox runs from full checkout
This commit is contained in:
@@ -7,6 +7,7 @@ Docs: https://docs.openclaw.ai
|
||||
### Changes
|
||||
|
||||
### Fixes
|
||||
- Crabbox: sync clean sparse-checkout remote changed gates from a temporary full checkout with local-only commits overlaid as worktree changes so git-backed script checks can seed the runner repository.
|
||||
- Tests: make startup memory and startup bench smoke scripts build CLI startup artifacts when run from a fresh source checkout.
|
||||
- iMessage: mark authorized slash-command turns as text-sourced commands so `/status`, `/new`, and `/restart` acknowledgements return to the source conversation. (#82642) thanks @homer-byte.
|
||||
- Crabbox: install Corepack shims into the writable hydration `PNPM_HOME` so local AWS runner hydration no longer tries to overwrite `/usr/local/bin/pnpm`.
|
||||
|
||||
@@ -426,8 +426,8 @@ function runCommandArgs(commandArgs) {
|
||||
return start >= 0 ? commandArgs.slice(start) : [];
|
||||
}
|
||||
|
||||
function commandRuntimeEntrypoint(commandArgs) {
|
||||
const words = commandArgs.length === 1 ? commandArgs[0].split(/\s+/u) : commandArgs;
|
||||
function normalizedCommandWords(commandArgs) {
|
||||
const words = commandArgs.length === 1 ? commandArgs[0].split(/\s+/u) : [...commandArgs];
|
||||
while (words[0] === "env") {
|
||||
words.shift();
|
||||
while (/^[A-Za-z_][A-Za-z0-9_]*=/.test(words[0] ?? "")) {
|
||||
@@ -437,13 +437,43 @@ function commandRuntimeEntrypoint(commandArgs) {
|
||||
while (/^[A-Za-z_][A-Za-z0-9_]*=/.test(words[0] ?? "")) {
|
||||
words.shift();
|
||||
}
|
||||
const first = (words[0] ?? "")
|
||||
.replace(/^['"]|['";|&()]+$/g, "")
|
||||
.split("/")
|
||||
.pop();
|
||||
return words.map((word) => word.replace(/^['"]|['";|&()]+$/g, ""));
|
||||
}
|
||||
|
||||
function commandRuntimeEntrypoint(commandArgs) {
|
||||
const words = normalizedCommandWords(commandArgs);
|
||||
const first = (words[0] ?? "").split("/").pop();
|
||||
return ["pnpm", "npm", "npx", "corepack", "node", "yarn", "bun"].includes(first) ? first : "";
|
||||
}
|
||||
|
||||
function isChangedGateCommand(commandArgs) {
|
||||
const words = normalizedCommandWords(commandArgs);
|
||||
if (words[0] === "corepack") {
|
||||
words.shift();
|
||||
}
|
||||
return (
|
||||
(words[0] === "pnpm" && words[1] === "check:changed") ||
|
||||
(words[0] === "pnpm" && words[1] === "run" && words[2] === "check:changed") ||
|
||||
(words[0] === "node" && (words[1] ?? "").endsWith("scripts/check-changed.mjs"))
|
||||
);
|
||||
}
|
||||
|
||||
function headInRemoteRefs() {
|
||||
const refs = gitOutput([
|
||||
"for-each-ref",
|
||||
"--contains",
|
||||
"HEAD",
|
||||
"--format=%(refname)",
|
||||
"refs/remotes",
|
||||
]);
|
||||
return refs.status === 0 && refs.stdout !== "";
|
||||
}
|
||||
|
||||
function mergeBaseForChangedGate() {
|
||||
const base = gitOutput(["merge-base", "origin/main", "HEAD"]);
|
||||
return base.status === 0 && base.stdout ? base.stdout : "origin/main";
|
||||
}
|
||||
|
||||
function isSparseCheckout() {
|
||||
const config = gitOutput(["config", "--bool", "core.sparseCheckout"]);
|
||||
if (config.status === 0 && config.stdout === "true") {
|
||||
@@ -457,8 +487,8 @@ function isWorktreeClean() {
|
||||
return gitOutput(["status", "--porcelain=v1"]).stdout === "";
|
||||
}
|
||||
|
||||
function shouldUseFullCheckoutForCleanSparseBlacksmithSync(commandArgs, providerName) {
|
||||
if (commandArgs[0] !== "run" || providerName !== "blacksmith-testbox") {
|
||||
function shouldUseFullCheckoutForCleanSparseRemoteSync(commandArgs, providerName) {
|
||||
if (commandArgs[0] !== "run" || isLocalContainerProvider(providerName)) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
@@ -471,7 +501,7 @@ function shouldUseFullCheckoutForCleanSparseBlacksmithSync(commandArgs, provider
|
||||
return isSparseCheckout() && isWorktreeClean();
|
||||
}
|
||||
|
||||
function prepareFullCheckoutForSync() {
|
||||
function prepareFullCheckoutForSync(options = {}) {
|
||||
const dir = mkdtempSync(resolve(tmpdir(), "openclaw-crabbox-sync-"));
|
||||
let active = false;
|
||||
const add = gitOutput(["worktree", "add", "--detach", dir, "HEAD"]);
|
||||
@@ -487,8 +517,17 @@ function prepareFullCheckoutForSync() {
|
||||
throw new Error(`git sparse-checkout disable failed: ${disableSparse.text}`);
|
||||
}
|
||||
|
||||
if (options.changedGateBase) {
|
||||
const reset = gitOutput(["-C", dir, "reset", "--mixed", "--quiet", options.changedGateBase]);
|
||||
if (reset.status !== 0) {
|
||||
cleanupFullCheckout(dir, active);
|
||||
throw new Error(`git reset for changed-gate sync failed: ${reset.text}`);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
dir,
|
||||
changedGateBase: options.changedGateBase ?? "",
|
||||
cleanup() {
|
||||
cleanupFullCheckout(dir, active);
|
||||
active = false;
|
||||
@@ -645,13 +684,21 @@ if (provider === "blacksmith-testbox") {
|
||||
let childCwd = repoRoot;
|
||||
let cleanupChildCwd = () => {};
|
||||
let cleanupDone = false;
|
||||
if (shouldUseFullCheckoutForCleanSparseBlacksmithSync(args, provider)) {
|
||||
const checkout = prepareFullCheckoutForSync();
|
||||
if (shouldUseFullCheckoutForCleanSparseRemoteSync(args, provider)) {
|
||||
const runWords = runCommandArgs(args);
|
||||
const changedGateBase =
|
||||
isChangedGateCommand(runWords) && !headInRemoteRefs() ? mergeBaseForChangedGate() : "";
|
||||
const checkout = prepareFullCheckoutForSync({ changedGateBase });
|
||||
childCwd = checkout.dir;
|
||||
cleanupChildCwd = () => checkout.cleanup();
|
||||
console.error(
|
||||
`[crabbox] sparse clean checkout detected; syncing from temporary full checkout ${checkout.dir}`,
|
||||
);
|
||||
if (checkout.changedGateBase) {
|
||||
console.error(
|
||||
`[crabbox] remote changed gate detected; overlaying local HEAD as worktree changes from ${checkout.changedGateBase}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function cleanupOnce() {
|
||||
|
||||
@@ -45,6 +45,7 @@ function makeFakeGit(responses: Record<string, { status?: number; stdout?: strin
|
||||
"const args = process.argv.slice(2);",
|
||||
"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); }",
|
||||
"const key = args.join('\\u0000');",
|
||||
"const response = responses.get(key);",
|
||||
@@ -248,6 +249,58 @@ describe("scripts/crabbox-wrapper", () => {
|
||||
expect(parseFakeCrabboxOutput(result).cwd).toContain("openclaw-crabbox-sync-");
|
||||
});
|
||||
|
||||
it("uses a temporary full checkout for clean sparse AWS syncs", () => {
|
||||
const result = runWrapper(
|
||||
"provider: hetzner, aws, local-container, blacksmith-testbox, or cloudflare\n",
|
||||
["run", "--provider", "aws", "--", "corepack", "pnpm", "check:changed"],
|
||||
{
|
||||
gitResponses: {
|
||||
["config\u0000--bool\u0000core.sparseCheckout"]: { stdout: "true\n" },
|
||||
["status\u0000--porcelain=v1"]: { stdout: "" },
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.status).toBe(0);
|
||||
expect(result.stderr).toContain("syncing from temporary full checkout");
|
||||
expect(result.stderr).toContain("overlaying local HEAD as worktree changes from origin/main");
|
||||
expect(parseFakeCrabboxOutput(result).cwd).toContain("openclaw-crabbox-sync-");
|
||||
});
|
||||
|
||||
it("keeps clean sparse local-container syncs on the original checkout", () => {
|
||||
const result = runWrapper(
|
||||
"provider: hetzner, aws, local-container, blacksmith-testbox, or cloudflare\n",
|
||||
["run", "--provider", "local-container", "--", "echo ok"],
|
||||
{
|
||||
gitResponses: {
|
||||
["config\u0000--bool\u0000core.sparseCheckout"]: { stdout: "true\n" },
|
||||
["status\u0000--porcelain=v1"]: { stdout: "" },
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.status).toBe(0);
|
||||
expect(result.stderr).not.toContain("syncing from temporary full checkout");
|
||||
expect(parseFakeCrabboxOutput(result).cwd).toBe(repoRoot);
|
||||
});
|
||||
|
||||
it("keeps existing AWS leases on the original sparse checkout", () => {
|
||||
const result = runWrapper(
|
||||
"provider: hetzner, aws, local-container, blacksmith-testbox, or cloudflare\n",
|
||||
["run", "--provider", "aws", "--id", "cbx_existing", "--", "echo ok"],
|
||||
{
|
||||
gitResponses: {
|
||||
["config\u0000--bool\u0000core.sparseCheckout"]: { stdout: "true\n" },
|
||||
["status\u0000--porcelain=v1"]: { stdout: "" },
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.status).toBe(0);
|
||||
expect(result.stderr).not.toContain("syncing from temporary full checkout");
|
||||
expect(parseFakeCrabboxOutput(result).cwd).toBe(repoRoot);
|
||||
});
|
||||
|
||||
it("uses a temporary full checkout when clean sparse branches differ from the Blacksmith ref", () => {
|
||||
const result = runWrapper(
|
||||
"provider: hetzner, aws, local-container, blacksmith-testbox, or cloudflare\n",
|
||||
|
||||
Reference in New Issue
Block a user