Files
orca/.github/workflows/release-mac-build.yml
T
Neil cb715898cd fix(release): pass the draft-verify tag on Windows pwsh (#21851)
The Windows matrix defaults to pwsh, so assert-github-release-is-draft.mjs
received an empty argv and failed with "tag is required" after the signed
installer was already uploaded. Force bash, interpolate the tag in YAML,
and fall back to env TAG.
2026-09-20 16:01:01 -07:00

198 lines
8.2 KiB
YAML

name: Release macOS Build
run-name: Mac release build ${{ inputs.tag }} (${{ inputs.release_run_id }})
on:
workflow_dispatch:
inputs:
tag:
description: Release tag whose draft should receive macOS artifacts
required: true
type: string
release_run_id:
description: release-cut workflow run that requested this build
required: true
type: string
permissions:
contents: write
concurrency:
group: release-mac-build-${{ inputs.tag }}
cancel-in-progress: false
jobs:
build-mac:
if: github.repository == 'stablyai/orca'
# Why: this workflow is outside the SignPath signing run, so Blacksmith
# cannot enter Windows artifact provenance while mac notarization gets the
# faster runner.
runs-on: blacksmith-6vcpu-macos-15
timeout-minutes: 60
env:
NODE_OPTIONS: --max-old-space-size=4096
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: refs/tags/${{ inputs.tag }}
- name: Restore draft-publish scripts from the workflow ref
env:
WORKFLOW_SHA: ${{ github.workflow_sha }}
run: |
set -euo pipefail
git fetch --no-tags --depth=1 origin "$WORKFLOW_SHA"
git checkout "$WORKFLOW_SHA" -- config/scripts/assert-github-release-is-draft.mjs
- name: Setup pnpm
uses: pnpm/setup@v2
with:
install: false
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version-file: package.json
cache: pnpm
cache-dependency-path: |
pnpm-lock.yaml
mobile/pnpm-lock.yaml
# Cache the Electron binary + electron-builder tool downloads (notarytool,
# winCodeSign, nsis, squirrel, AppImage). Saves ~30-90s per job, incl. mac.
- name: Cache electron-builder downloads
uses: actions/cache@v5
with:
path: |
~/Library/Caches/electron
~/Library/Caches/electron-builder
key: electron-builder-mac-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: |
electron-builder-mac-
# Why: pnpm install triggers electron's postinstall, which downloads the
# Electron binary from GitHub release assets. GitHub's download CDN
# occasionally returns 504s that fail the whole release. Retry on
# failure so transient network errors don't require a manual re-run.
# Why both CPUs: the mac config packages x64 and arm64 from this arm64
# runner, so the install must carry both variants of the native optional deps.
- name: Install dependencies
uses: nick-fields/retry@v4
with:
timeout_minutes: 10
max_attempts: 3
retry_wait_seconds: 30
command: pnpm install --frozen-lockfile --cpu=current,x64,arm64
# Why here: electron-builder's beforePack requires out/mobile-web, and the bundle
# build resolves React Native and Expo from mobile/node_modules.
- uses: ./.github/actions/install-mobile-dependencies
- name: Verify macOS signing environment
run: node config/scripts/verify-macos-release-env.mjs
env:
CSC_LINK: ${{ secrets.MAC_CERTS }}
CSC_KEY_PASSWORD: ${{ secrets.MAC_CERTS_PASSWORD }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
# Why: `plutil -lint` accepts duplicate plist keys, but `codesign`
# rejects duplicate entitlements after the expensive app build.
- name: Verify macOS entitlements
run: pnpm verify:macos-entitlements
# Why: telemetry's transport gate (`src/main/telemetry/client.ts:IS_OFFICIAL_BUILD`)
# requires the build identity to be the literal string `stable` or `rc`,
# substituted by electron-vite's `define` block at build time.
- name: Classify release tag for telemetry build identity
id: tag-classify
shell: bash
env:
TAG: ${{ inputs.tag }}
run: |
set -euo pipefail
# Why the optional trailing identifier: suffixed side-branch RCs
# (vX.Y.Z-rc.N.perf) are rc-channel prerelease builds — same telemetry
# identity as plain RCs.
if [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+-rc\.[0-9]+(\.[0-9A-Za-z]+)?$ ]]; then
identity=rc
elif [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
identity=stable
else
echo "::error::Tag $TAG does not match stable or rc pattern; refusing to build official artifact"
exit 1
fi
echo "identity=$identity" >>"$GITHUB_OUTPUT"
echo "Classified $TAG as $identity"
- name: Build app
run: pnpm build:release
env:
# Why: Vite's web build crossed Node's default old-space ceiling on
# the macOS release runner, leaving v1.4.2-rc.8 as an incomplete draft.
NODE_OPTIONS: --max-old-space-size=4096
ORCA_BUILD_IDENTITY: ${{ steps.tag-classify.outputs.identity }}
ORCA_DIAGNOSTICS_TOKEN_URL: https://www.onorca.dev/diagnostics/token
ORCA_POSTHOG_WRITE_KEY: ${{ secrets.ORCA_POSTHOG_WRITE_KEY }}
- name: Gate runtime file-watcher process isolation
run: |
# Why: #8212 is a native-process crash contract. Prove both the Node
# host and the exact Electron runtime survive SIGSEGV before packaging.
node config/scripts/runtime-file-watcher-fault-harness.mjs
ELECTRON_RUN_AS_NODE=1 pnpm exec electron config/scripts/runtime-file-watcher-fault-harness.mjs
- name: Gate SSH relay watcher process isolation
run: |
# Why: the remote native watcher shares a daemon with live PTYs.
# Kill only its child and require both PTY and watch recovery before packaging.
node config/scripts/relay-watcher-fault-harness.mjs
- name: Abort if the parent release-cut run was cancelled
# Why: this workflow is dispatched separately, so cancelling release-cut
# does not stop mac `--publish always`. A cancelled parent left v1.4.206
# public with only a partial mac upload.
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PARENT_RUN: ${{ inputs.release_run_id }}
run: |
set -euo pipefail
conclusion="$(gh api "repos/$GITHUB_REPOSITORY/actions/runs/$PARENT_RUN" --jq '.conclusion // empty')"
if [[ "$conclusion" == "cancelled" || "$conclusion" == "failure" || "$conclusion" == "timed_out" ]]; then
echo "::error::Parent release-cut run $PARENT_RUN is $conclusion; refusing to publish mac artifacts."
exit 1
fi
- name: Publish release artifacts (macOS)
uses: nick-fields/retry@v4
with:
timeout_minutes: 45
max_attempts: 3
retry_wait_seconds: 30
command: node config/scripts/ensure-native-runtime.mjs --runtime=electron && ORCA_MAC_RELEASE=1 pnpm exec electron-builder --config config/electron-builder.config.cjs --mac --publish always -c.publish.releaseType=draft
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CSC_LINK: ${{ secrets.MAC_CERTS }}
CSC_KEY_PASSWORD: ${{ secrets.MAC_CERTS_PASSWORD }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
- name: Verify release remains draft after artifact upload
# Why: re-draft immediately if electron-builder flipped the GitHub
# release public, then fail. Checking without restoring leaves
# /releases/latest serving a missing Windows exe.
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ inputs.tag }}
run: node config/scripts/assert-github-release-is-draft.mjs "${{ inputs.tag }}"
# Why post-publish for macOS: electron-builder packs and uploads in a
# single `--publish always` invocation, so there is no cheap insertion
# point between pack and upload without splitting those steps.
- name: Verify telemetry constants present in app.asar
run: node config/scripts/verify-telemetry-constants.mjs