fix(mac): use corepack pnpm for app packaging

This commit is contained in:
Vincent Koc
2026-05-27 00:47:21 +02:00
parent 1600bcd44d
commit 728b61a0a4
3 changed files with 100 additions and 4 deletions

View File

@@ -9,6 +9,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- Crabbox: bootstrap raw AWS macOS JavaScript commands launched through `/usr/bin/env` so native mac runners without preinstalled Node, Corepack, or pnpm can still run wrapped Node and pnpm proof.
- macOS: let app packaging fall back to `corepack pnpm` when a fresh native runner has Node/Corepack but no pnpm shim on `PATH`.
## 2026.5.26

View File

@@ -65,6 +65,30 @@ sparkle_framework_for_arch() {
echo "$(build_path_for_arch "$1")/$BUILD_CONFIG/Sparkle.framework"
}
PNPM_CMD=()
resolve_pnpm_cmd() {
if command -v pnpm >/dev/null 2>&1; then
PNPM_CMD=(pnpm)
return 0
fi
if command -v corepack >/dev/null 2>&1 && (cd "$ROOT_DIR" && corepack pnpm --version >/dev/null 2>&1); then
PNPM_CMD=(corepack pnpm)
return 0
fi
echo "ERROR: pnpm is not on PATH and corepack pnpm is unavailable. Install pnpm or run with Node/Corepack on PATH." >&2
exit 1
}
run_pnpm() {
if [[ "${#PNPM_CMD[@]}" -eq 0 ]]; then
resolve_pnpm_cmd
fi
(cd "$ROOT_DIR" && "${PNPM_CMD[@]}" "$@")
}
merge_framework_machos() {
local primary="$1"
local dest="$2"
@@ -128,7 +152,7 @@ merge_framework_machos() {
if [[ "${SKIP_PNPM_INSTALL:-0}" != "1" ]]; then
echo "📦 Ensuring deps (pnpm install --frozen-lockfile)"
(cd "$ROOT_DIR" && pnpm install --frozen-lockfile --config.node-linker=hoisted)
run_pnpm install --frozen-lockfile --config.node-linker=hoisted
else
echo "📦 Skipping pnpm install (SKIP_PNPM_INSTALL=1)"
fi
@@ -153,7 +177,7 @@ fi
if [[ "${SKIP_TSC:-0}" != "1" ]]; then
echo "📦 Building JS (pnpm build)"
(cd "$ROOT_DIR" && pnpm build)
run_pnpm build
else
echo "📦 Skipping JS build (SKIP_TSC=1)"
fi

View File

@@ -1,5 +1,5 @@
import { spawnSync } from "node:child_process";
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { chmodSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import path from "node:path";
import { afterEach, describe, expect, it } from "vitest";
@@ -36,6 +36,17 @@ function runHelper(script: string) {
});
}
function getPackageManagerHelperBlock(): string {
const script = readFileSync(scriptPath, "utf8");
const start = script.indexOf("PNPM_CMD=()");
const end = script.indexOf("merge_framework_machos()");
expect(start).toBeGreaterThanOrEqual(0);
expect(end).toBeGreaterThan(start);
return script.slice(start, end);
}
afterEach(() => {
for (const dir of tempDirs.splice(0)) {
rmSync(dir, { recursive: true, force: true });
@@ -50,11 +61,71 @@ describe("package-mac-app plist stamping", () => {
script.indexOf('if [[ -z "${APP_BUILD:-}" ]]'),
);
expect(installBlock).toContain("pnpm install --frozen-lockfile");
expect(installBlock).toContain("run_pnpm install --frozen-lockfile");
expect(installBlock).toContain("--config.node-linker=hoisted");
expect(installBlock).not.toContain("--no-frozen-lockfile");
});
it("falls back to corepack pnpm when the pnpm shim is absent", () => {
const helperBlock = getPackageManagerHelperBlock();
const tempRoot = mkdtempSync(path.join(tmpdir(), "openclaw-package-pnpm-root-"));
const toolsDir = mkdtempSync(path.join(tmpdir(), "openclaw-package-pnpm-tools-"));
const logPath = path.join(tempRoot, "corepack.log");
tempDirs.push(tempRoot, toolsDir);
const corepackPath = path.join(toolsDir, "corepack");
writeFileSync(
corepackPath,
[
"#!/usr/bin/env bash",
"set -euo pipefail",
"printf '%s|%s\\n' \"$PWD\" \"$*\" >> \"$OPENCLAW_TEST_LOG\"",
"if [[ \"${1:-}\" == \"pnpm\" && \"${2:-}\" == \"--version\" ]]; then",
" echo '11.2.2'",
"fi",
"",
].join("\n"),
"utf8",
);
chmodSync(corepackPath, 0o755);
const result = runHelper(`
set -euo pipefail
ROOT_DIR=${JSON.stringify(tempRoot)}
OPENCLAW_TEST_LOG=${JSON.stringify(logPath)}
export OPENCLAW_TEST_LOG
PATH=${JSON.stringify(`${toolsDir}:/usr/bin:/bin`)}
${helperBlock}
run_pnpm install --frozen-lockfile --config.node-linker=hoisted
run_pnpm build
`);
expect(result.status).toBe(0);
expect(readFileSync(logPath, "utf8").trim().split("\n")).toEqual([
`${tempRoot}|pnpm --version`,
`${tempRoot}|pnpm install --frozen-lockfile --config.node-linker=hoisted`,
`${tempRoot}|pnpm build`,
]);
});
it("fails with an actionable error when neither pnpm nor corepack pnpm is available", () => {
const helperBlock = getPackageManagerHelperBlock();
const tempRoot = mkdtempSync(path.join(tmpdir(), "openclaw-package-pnpm-root-"));
const toolsDir = mkdtempSync(path.join(tmpdir(), "openclaw-package-pnpm-tools-"));
tempDirs.push(tempRoot, toolsDir);
const result = runHelper(`
set -euo pipefail
ROOT_DIR=${JSON.stringify(tempRoot)}
PATH=${JSON.stringify(`${toolsDir}:/usr/bin:/bin`)}
${helperBlock}
run_pnpm build
`);
expect(result.status).toBe(1);
expect(result.stderr).toContain("pnpm is not on PATH and corepack pnpm is unavailable");
});
it("does not kill unrelated OpenClaw processes during packaging", () => {
const script = readFileSync(scriptPath, "utf8");
const stopBlock = script.slice(