From 8ed33c2aff24d088031acb0f482d9b735c53a9f3 Mon Sep 17 00:00:00 2001 From: Onur Solmaz <2453968+osolmaz@users.noreply.github.com> Date: Mon, 23 Mar 2026 16:04:53 +0100 Subject: [PATCH] release: automate macOS publishing (#52853) * release: automate macOS publishing * release: keep mac appcast in openclaw repo * release: add preflight-only release workflow runs * release: keep appcast updates manual * release: generate signed appcast as workflow artifact * release: require preflight before publish * release: require mac app for every release * docs: clarify every release ships mac app * release: document Sparkle feed and SHA rules * release: keep publish flow tag-based * release: stabilize mac appcast flow * release: document local mac fallback --- .../openclaw-release-maintainer/SKILL.md | 90 +++++- .github/workflows/macos-release.yml | 306 ++++++++++++++++++ .github/workflows/openclaw-npm-release.yml | 69 +++- docs/reference/RELEASING.md | 2 +- scripts/package-mac-app.sh | 8 +- 5 files changed, 457 insertions(+), 18 deletions(-) create mode 100644 .github/workflows/macos-release.yml diff --git a/.agents/skills/openclaw-release-maintainer/SKILL.md b/.agents/skills/openclaw-release-maintainer/SKILL.md index 9904ce73596..5c4715895be 100644 --- a/.agents/skills/openclaw-release-maintainer/SKILL.md +++ b/.agents/skills/openclaw-release-maintainer/SKILL.md @@ -37,6 +37,15 @@ Use this skill for release and publish-time workflow. Keep ordinary development - For fallback correction tags like `vYYYY.M.D-N`, the repo version locations still stay at `YYYY.M.D`. - “Bump version everywhere” means all version locations above except `appcast.xml`. - Release signing and notary credentials live outside the repo in the private maintainer docs. +- Every OpenClaw release ships the npm package and macOS app together. +- The production Sparkle feed lives at `https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml`, and the canonical published file is `appcast.xml` on `main` in the `openclaw` repo. +- That shared production Sparkle feed is stable-only. Beta mac releases may + upload assets to the GitHub prerelease, but they must not replace the shared + `appcast.xml` unless a separate beta feed exists. +- For fallback correction tags like `vYYYY.M.D-N`, the repo version still stays + at `YYYY.M.D`, but the mac release must use a strictly higher numeric + `APP_BUILD` / Sparkle build than the original release so existing installs + see it as newer. ## Build changelog-backed release notes @@ -68,42 +77,101 @@ OPENCLAW_INSTALL_SMOKE_SKIP_NONROOT=1 pnpm test:install:smoke ## Check all relevant release builds -- Always validate the core npm release path before creating the tag. -- Default core release checks: +- Always validate the OpenClaw npm release path before creating the tag. +- Default release checks: - `pnpm check` - `pnpm build` - `node --import tsx scripts/release-check.ts` - `pnpm release:check` - `OPENCLAW_INSTALL_SMOKE_SKIP_NONROOT=1 pnpm test:install:smoke` - Check all release-related build surfaces touched by the release, not only the npm package. -- Include mac release readiness in preflight: - - if the release includes mac artifacts, run or inspect the mac packaging/notary/appcast flow - - if the release does not include mac artifacts, explicitly confirm that exception before continuing +- Include mac release readiness in preflight by running or inspecting the mac + packaging, notarization, and appcast flow for every release. +- Treat the `appcast.xml` update on `main` as part of mac release readiness, not an optional follow-up. +- The workflows remain tag-based. The agent is responsible for making sure + preflight runs complete successfully before any publish run starts. +- Any fix after preflight means a new commit. Delete and recreate the tag and + matching GitHub release from the fixed commit, then rerun preflight from + scratch before publishing. +- For stable mac releases, generate the signed `appcast.xml` before uploading + public release assets so the updater feed cannot lag the published binaries. +- Serialize stable appcast-producing runs across tags so two releases do not + generate replacement `appcast.xml` files from the same stale seed. - For stable releases, confirm the latest beta already passed the broader release workflows before cutting stable. - If any required build, packaging step, or release workflow is red, do not say the release is ready. ## Use the right auth flow -- Core `openclaw` publish uses GitHub trusted publishing. +- OpenClaw publish uses GitHub trusted publishing. - The publish run must be started manually with `workflow_dispatch`. +- Both release workflows accept `preflight_only=true` to run CI + validation/build steps without entering the gated publish job. +- npm preflight and macOS preflight must both pass before any publish run + starts. +- The release workflows stay tag-based; rely on the documented release sequence + rather than workflow-level SHA pinning. - The `npm-release` environment must be approved by `@openclaw/openclaw-release-managers` before publish continues. -- Do not use `NPM_TOKEN` or the plugin OTP flow for core releases. +- Mac publish uses `.github/workflows/macos-release.yml` for build, signing, + notarization, stable-feed `appcast.xml` artifact generation, and release-asset + upload. +- The agent must download the signed `appcast.xml` artifact from a successful + stable mac workflow and then update `appcast.xml` on `main`. +- For beta mac releases, do not update the shared production `appcast.xml` + unless a separate beta Sparkle feed exists. +- `.github/workflows/macos-release.yml` still requires the `mac-release` + environment approval. +- Do not use `NPM_TOKEN` or the plugin OTP flow for OpenClaw releases. - `@openclaw/*` plugin publishes use a separate maintainer-only flow. - Only publish plugins that already exist on npm; bundled disk-tree-only plugins stay unpublished. +## Fallback local mac publish + +- Keep the original local macOS publish workflow available as a fallback in case + CI/CD mac publishing is unavailable or broken. +- Preserve the existing maintainer workflow Peter uses: run it on a real Mac + with local signing, notary, and Sparkle credentials already configured. +- Follow the private maintainer macOS runbook for the local steps: + `scripts/package-mac-dist.sh` to build, sign, notarize, and package the app; + manual GitHub release asset upload; then `scripts/make_appcast.sh` plus the + `appcast.xml` commit to `main`. +- For stable tags, the local fallback may update the shared production + `appcast.xml`. +- For beta tags, the local fallback still publishes the mac assets but must not + update the shared production `appcast.xml` unless a separate beta feed exists. +- Treat the local workflow as fallback only. Prefer the CI/CD publish workflow + when it is working. + ## Run the release sequence 1. Confirm the operator explicitly wants to cut a release. 2. Choose the exact target version and git tag. 3. Make every repo version location match that tag before creating it. 4. Update `CHANGELOG.md` and assemble the matching GitHub release notes. -5. Run the full preflight for all relevant release builds, including mac readiness when applicable. +5. Run the full preflight for all relevant release builds, including mac readiness. 6. Confirm the target npm version is not already published. 7. Create and push the git tag. 8. Create or refresh the matching GitHub release. -9. Start `.github/workflows/openclaw-npm-release.yml` with `workflow_dispatch` and the same tag. -10. Wait for `npm-release` approval from `@openclaw/openclaw-release-managers`. -11. After publish, verify npm and any attached release artifacts. +9. Start `.github/workflows/openclaw-npm-release.yml` with `preflight_only=true` + and wait for it to pass. +10. Start `.github/workflows/macos-release.yml` with `preflight_only=true` and + wait for it to pass. +11. If either preflight fails, fix the issue on a new commit, delete the tag + and matching GitHub release, recreate them from the fixed commit, and rerun + both preflights from scratch before continuing. Never reuse old preflight + results after the commit changes. +12. Start `.github/workflows/openclaw-npm-release.yml` with the same tag for + the real publish. +13. Wait for `npm-release` approval from `@openclaw/openclaw-release-managers`. +14. Start `.github/workflows/macos-release.yml` for the real publish and wait + for `mac-release` approval and success. +15. For stable releases, let the mac workflow generate the signed + `appcast.xml` artifact before it uploads the public mac assets, then + download that artifact from the successful run, update `appcast.xml` on + `main`, and verify the feed. +16. For beta releases, publish the mac assets but expect no shared production + `appcast.xml` artifact and do not update the shared production feed unless a + separate beta feed exists. +17. After publish, verify npm and any attached release artifacts. ## GHSA advisory work diff --git a/.github/workflows/macos-release.yml b/.github/workflows/macos-release.yml new file mode 100644 index 00000000000..91ae67ccbd7 --- /dev/null +++ b/.github/workflows/macos-release.yml @@ -0,0 +1,306 @@ +name: macOS Release + +on: + workflow_dispatch: + inputs: + tag: + description: Existing release tag to build macOS artifacts for (for example v2026.3.22 or v2026.3.22-beta.1) + required: true + type: string + preflight_only: + description: Run validation/build only and skip the gated publish job + required: true + default: false + type: boolean + +concurrency: + group: macos-release-${{ inputs.tag }} + cancel-in-progress: false + +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" + NODE_VERSION: "24.x" + PNPM_VERSION: "10.23.0" + SPARKLE_FEED_URL: https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml + +jobs: + preflight_macos_release: + runs-on: macos-latest + permissions: + contents: read + steps: + - name: Validate tag input format + env: + RELEASE_TAG: ${{ inputs.tag }} + run: | + set -euo pipefail + if [[ ! "${RELEASE_TAG}" =~ ^v[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*((-beta\.[1-9][0-9]*)|(-[1-9][0-9]*))?$ ]]; then + echo "Invalid release tag format: ${RELEASE_TAG}" + exit 1 + fi + + - name: Checkout selected tag + uses: actions/checkout@v6 + with: + ref: refs/tags/${{ inputs.tag }} + fetch-depth: 0 + + - name: Setup Node environment + uses: ./.github/actions/setup-node-env + with: + node-version: ${{ env.NODE_VERSION }} + pnpm-version: ${{ env.PNPM_VERSION }} + install-bun: "false" + use-sticky-disk: "false" + + - name: Ensure matching GitHub release exists + env: + GH_TOKEN: ${{ github.token }} + RELEASE_TAG: ${{ inputs.tag }} + run: gh release view "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" >/dev/null + + - name: Validate release tag and package metadata + env: + RELEASE_TAG: ${{ inputs.tag }} + RELEASE_MAIN_REF: origin/main + run: | + set -euo pipefail + RELEASE_SHA=$(git rev-parse HEAD) + export RELEASE_SHA RELEASE_TAG RELEASE_MAIN_REF + git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main + pnpm release:openclaw:npm:check + + - name: Resolve package version + id: package_version + run: echo "value=$(node -p 'require(\"./package.json\").version')" >> "$GITHUB_OUTPUT" + + - name: Check + run: pnpm check + + - name: Build + run: pnpm build + + - name: Build Control UI + run: node scripts/ui.js build + + - name: Verify release contents + run: pnpm release:check + + - name: Swift build + run: swift build --package-path apps/macos --configuration release + + - name: Swift test + run: swift test --package-path apps/macos --parallel + + - name: Package macOS release with ad-hoc signing + env: + APP_VERSION: ${{ steps.package_version.outputs.value }} + BUNDLE_ID: ai.openclaw.mac + BUILD_CONFIG: release + CODESIGN_TIMESTAMP: "off" + SIGN_IDENTITY: "-" + SKIP_NOTARIZE: "1" + SKIP_PNPM_INSTALL: "1" + SKIP_TSC: "1" + SKIP_UI_BUILD: "1" + SPARKLE_FEED_URL: ${{ env.SPARKLE_FEED_URL }} + run: scripts/package-mac-dist.sh + + publish_macos_release: + needs: [preflight_macos_release] + if: ${{ !inputs.preflight_only }} + runs-on: macos-latest + environment: mac-release + concurrency: + # Stable releases all derive the same shared appcast.xml; serialize those + # runs so each artifact starts from the latest stable feed snapshot. + group: macos-release-publish-${{ contains(inputs.tag, '-beta.') && inputs.tag || 'stable-feed' }} + cancel-in-progress: false + permissions: + contents: write + steps: + - name: Validate tag input format + env: + RELEASE_TAG: ${{ inputs.tag }} + run: | + set -euo pipefail + if [[ ! "${RELEASE_TAG}" =~ ^v[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*((-beta\.[1-9][0-9]*)|(-[1-9][0-9]*))?$ ]]; then + echo "Invalid release tag format: ${RELEASE_TAG}" + exit 1 + fi + + - name: Checkout selected tag + uses: actions/checkout@v6 + with: + ref: refs/tags/${{ inputs.tag }} + fetch-depth: 0 + + - name: Setup Node environment + uses: ./.github/actions/setup-node-env + with: + node-version: ${{ env.NODE_VERSION }} + pnpm-version: ${{ env.PNPM_VERSION }} + install-bun: "false" + use-sticky-disk: "false" + + - name: Ensure matching GitHub release exists + env: + GH_TOKEN: ${{ github.token }} + RELEASE_TAG: ${{ inputs.tag }} + run: gh release view "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" >/dev/null + + - name: Resolve package version + id: package_version + run: echo "value=$(node -p 'require(\"./package.json\").version')" >> "$GITHUB_OUTPUT" + + - name: Determine release channel + id: release_channel + env: + RELEASE_TAG: ${{ inputs.tag }} + run: | + set -euo pipefail + if [[ "$RELEASE_TAG" == *-beta.* ]]; then + echo "is_beta=true" >> "$GITHUB_OUTPUT" + else + echo "is_beta=false" >> "$GITHUB_OUTPUT" + fi + + - name: Import Developer ID certificate + env: + MACOS_DEVELOPER_ID_P12_BASE64: ${{ secrets.MACOS_DEVELOPER_ID_P12_BASE64 }} + MACOS_DEVELOPER_ID_P12_PASSWORD: ${{ secrets.MACOS_DEVELOPER_ID_P12_PASSWORD }} + run: | + set -euo pipefail + CERT_PATH="$RUNNER_TEMP/openclaw-macos-release.p12" + KEYCHAIN_PATH="$RUNNER_TEMP/openclaw-release.keychain-db" + KEYCHAIN_PASSWORD="$(openssl rand -hex 32)" + echo "::add-mask::$KEYCHAIN_PASSWORD" + export CERT_PATH MACOS_DEVELOPER_ID_P12_BASE64 + python3 - <<'PY' + import base64 + import os + from pathlib import Path + + Path(os.environ["CERT_PATH"]).write_bytes( + base64.b64decode(os.environ["MACOS_DEVELOPER_ID_P12_BASE64"]) + ) + PY + security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH" + security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + security import "$CERT_PATH" \ + -k "$KEYCHAIN_PATH" \ + -P "$MACOS_DEVELOPER_ID_P12_PASSWORD" \ + -T /usr/bin/codesign \ + -T /usr/bin/security + EXISTING_KEYCHAINS="$(security list-keychains -d user | tr -d '"')" + security list-keychains -d user -s "$KEYCHAIN_PATH" $EXISTING_KEYCHAINS + security default-keychain -d user -s "$KEYCHAIN_PATH" + security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + echo "KEYCHAIN_PATH=$KEYCHAIN_PATH" >> "$GITHUB_ENV" + + - name: Resolve signing identity + run: | + set -euo pipefail + SIGN_IDENTITY="$(security find-identity -p codesigning -v "$KEYCHAIN_PATH" 2>/dev/null | awk -F'\"' '/Developer ID Application/ { print $2; exit }')" + if [[ -z "${SIGN_IDENTITY}" ]]; then + echo "Developer ID Application identity not found in imported keychain." >&2 + exit 1 + fi + echo "SIGN_IDENTITY=$SIGN_IDENTITY" >> "$GITHUB_ENV" + + - name: Write notary and Sparkle key files + env: + APP_STORE_CONNECT_API_KEY_P8: ${{ secrets.APP_STORE_CONNECT_API_KEY_P8 }} + APP_STORE_CONNECT_KEY_ID: ${{ secrets.APP_STORE_CONNECT_KEY_ID }} + APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }} + SPARKLE_PRIVATE_KEY: ${{ secrets.SPARKLE_PRIVATE_KEY }} + run: | + set -euo pipefail + NOTARYTOOL_KEY_PATH="$RUNNER_TEMP/openclaw-notary.p8" + SPARKLE_PRIVATE_KEY_PATH="$RUNNER_TEMP/openclaw-sparkle-ed25519.pem" + export NOTARYTOOL_KEY_PATH SPARKLE_PRIVATE_KEY_PATH + python3 - <<'PY' + import os + from pathlib import Path + + def write_secret(path_env: str, value_env: str) -> None: + value = os.environ[value_env].replace("\\n", "\n") + Path(os.environ[path_env]).write_text(value, encoding="utf-8") + + write_secret("NOTARYTOOL_KEY_PATH", "APP_STORE_CONNECT_API_KEY_P8") + write_secret("SPARKLE_PRIVATE_KEY_PATH", "SPARKLE_PRIVATE_KEY") + PY + echo "NOTARYTOOL_KEY=$NOTARYTOOL_KEY_PATH" >> "$GITHUB_ENV" + echo "NOTARYTOOL_KEY_ID=$APP_STORE_CONNECT_KEY_ID" >> "$GITHUB_ENV" + echo "NOTARYTOOL_ISSUER=$APP_STORE_CONNECT_ISSUER_ID" >> "$GITHUB_ENV" + echo "SPARKLE_PRIVATE_KEY_FILE=$SPARKLE_PRIVATE_KEY_PATH" >> "$GITHUB_ENV" + + - name: Build, sign, notarize, and package macOS release + env: + APP_VERSION: ${{ steps.package_version.outputs.value }} + BUNDLE_ID: ai.openclaw.mac + BUILD_CONFIG: release + SIGN_IDENTITY: ${{ env.SIGN_IDENTITY }} + SKIP_PNPM_INSTALL: "1" + SPARKLE_FEED_URL: ${{ env.SPARKLE_FEED_URL }} + run: scripts/package-mac-dist.sh + + - name: Checkout main branch for appcast seed + if: ${{ steps.release_channel.outputs.is_beta != 'true' }} + uses: actions/checkout@v6 + with: + path: openclaw-main + ref: main + fetch-depth: 0 + + - name: Seed appcast from main + if: ${{ steps.release_channel.outputs.is_beta != 'true' }} + run: | + set -euo pipefail + APPCAST_SOURCE="openclaw-main/appcast.xml" + if [[ -f "$APPCAST_SOURCE" ]]; then + cp "$APPCAST_SOURCE" appcast.xml + else + echo "No existing appcast at $APPCAST_SOURCE; generating a fresh feed." + fi + + - name: Generate signed appcast artifact + if: ${{ steps.release_channel.outputs.is_beta != 'true' }} + env: + SPARKLE_DOWNLOAD_URL_PREFIX: https://github.com/openclaw/openclaw/releases/download/${{ inputs.tag }}/ + SPARKLE_RELEASE_VERSION: ${{ steps.package_version.outputs.value }} + run: scripts/make_appcast.sh "dist/OpenClaw-${{ steps.package_version.outputs.value }}.zip" "${{ env.SPARKLE_FEED_URL }}" + + - name: Upload stable appcast artifact + if: ${{ steps.release_channel.outputs.is_beta != 'true' }} + uses: actions/upload-artifact@v7 + with: + name: macos-appcast-${{ inputs.tag }} + path: appcast.xml + if-no-files-found: error + + - name: Skip shared appcast for beta releases + if: ${{ steps.release_channel.outputs.is_beta == 'true' }} + run: echo "Beta release detected; skip shared production appcast artifact generation." + + - name: Upload macOS assets to GitHub release + env: + GH_TOKEN: ${{ github.token }} + RELEASE_TAG: ${{ inputs.tag }} + VERSION: ${{ steps.package_version.outputs.value }} + run: | + set -euo pipefail + gh release upload "$RELEASE_TAG" \ + "dist/OpenClaw-$VERSION.zip" \ + "dist/OpenClaw-$VERSION.dmg" \ + "dist/OpenClaw-$VERSION.dSYM.zip" \ + --clobber \ + --repo "$GITHUB_REPOSITORY" + + - name: Clean up signing keychain + if: always() + run: | + if [[ -n "${KEYCHAIN_PATH:-}" ]]; then + security delete-keychain "$KEYCHAIN_PATH" >/dev/null 2>&1 || true + fi diff --git a/.github/workflows/openclaw-npm-release.yml b/.github/workflows/openclaw-npm-release.yml index 7ba90944e3f..644231ccceb 100644 --- a/.github/workflows/openclaw-npm-release.yml +++ b/.github/workflows/openclaw-npm-release.yml @@ -7,6 +7,11 @@ on: description: Release tag to publish (for example v2026.3.22, v2026.3.22-beta.1, or fallback v2026.3.22-1) required: true type: string + preflight_only: + description: Run validation/build only and skip the gated publish job + required: true + default: false + type: boolean concurrency: group: openclaw-npm-release-${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref }} @@ -18,13 +23,10 @@ env: PNPM_VERSION: "10.23.0" jobs: - publish_openclaw_npm: - # npm trusted publishing + provenance requires a GitHub-hosted runner. + preflight_openclaw_npm: runs-on: ubuntu-latest - environment: npm-release permissions: contents: read - id-token: write steps: - name: Validate tag input format env: @@ -84,5 +86,64 @@ jobs: - name: Verify release contents run: pnpm release:check + publish_openclaw_npm: + # npm trusted publishing + provenance requires a GitHub-hosted runner. + needs: [preflight_openclaw_npm] + if: ${{ !inputs.preflight_only }} + runs-on: ubuntu-latest + environment: npm-release + permissions: + contents: read + id-token: write + steps: + - name: Validate tag input format + env: + RELEASE_TAG: ${{ inputs.tag }} + run: | + set -euo pipefail + if [[ ! "${RELEASE_TAG}" =~ ^v[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*((-beta\.[1-9][0-9]*)|(-[1-9][0-9]*))?$ ]]; then + echo "Invalid release tag format: ${RELEASE_TAG}" + exit 1 + fi + + - name: Checkout + uses: actions/checkout@v6 + with: + ref: refs/tags/${{ inputs.tag }} + fetch-depth: 0 + + - name: Setup Node environment + uses: ./.github/actions/setup-node-env + with: + node-version: ${{ env.NODE_VERSION }} + pnpm-version: ${{ env.PNPM_VERSION }} + install-bun: "false" + use-sticky-disk: "false" + + - name: Validate release tag and package metadata + env: + RELEASE_TAG: ${{ inputs.tag }} + RELEASE_MAIN_REF: origin/main + run: | + set -euo pipefail + RELEASE_SHA=$(git rev-parse HEAD) + export RELEASE_SHA RELEASE_TAG RELEASE_MAIN_REF + # Fetch the full main ref so merge-base ancestry checks keep working + # for older tagged commits that are still contained in main. + git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main + pnpm release:openclaw:npm:check + + - name: Ensure version is not already published + run: | + set -euo pipefail + PACKAGE_VERSION=$(node -p "require('./package.json').version") + + if npm view "openclaw@${PACKAGE_VERSION}" version >/dev/null 2>&1; then + echo "openclaw@${PACKAGE_VERSION} is already published on npm." + exit 1 + fi + + echo "Publishing openclaw@${PACKAGE_VERSION}" + - name: Publish run: bash scripts/openclaw-npm-publish.sh --publish diff --git a/docs/reference/RELEASING.md b/docs/reference/RELEASING.md index 275675c7dba..298276bbde6 100644 --- a/docs/reference/RELEASING.md +++ b/docs/reference/RELEASING.md @@ -23,7 +23,7 @@ OpenClaw has three public release lanes: - Do not zero-pad month or day - `latest` means the current stable npm release - `beta` means the current prerelease npm release -- Beta releases may ship before the macOS app catches up +- Every OpenClaw release ships the npm package and macOS app together ## Release cadence diff --git a/scripts/package-mac-app.sh b/scripts/package-mac-app.sh index 04f6925d77b..35309048bfe 100755 --- a/scripts/package-mac-app.sh +++ b/scripts/package-mac-app.sh @@ -114,8 +114,12 @@ merge_framework_machos() { done < <(find "$primary" -type f -print0) } -echo "📦 Ensuring deps (pnpm install)" -(cd "$ROOT_DIR" && pnpm install --no-frozen-lockfile --config.node-linker=hoisted) +if [[ "${SKIP_PNPM_INSTALL:-0}" != "1" ]]; then + echo "📦 Ensuring deps (pnpm install)" + (cd "$ROOT_DIR" && pnpm install --no-frozen-lockfile --config.node-linker=hoisted) +else + echo "📦 Skipping pnpm install (SKIP_PNPM_INSTALL=1)" +fi if [[ -z "${APP_BUILD:-}" ]]; then APP_BUILD="$GIT_BUILD_NUMBER"