426e9eeabd
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Waiting to run
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Waiting to run
Build Agent Image / build-and-push (push) Waiting to run
Chat shell gestures / Chat shell gesture + parity e2e (push) Waiting to run
ci / test (push) Waiting to run
ci / lint-and-format (push) Waiting to run
ci / build (push) Waiting to run
ci / dev-startup (push) Waiting to run
Cloud Gateway Discord / Test (push) Waiting to run
Cloud Gateway Webhook / Test (push) Waiting to run
Cloud Tests / lint-and-types (push) Waiting to run
Cloud Tests / unit-tests (push) Waiting to run
Cloud Tests / integration-tests (push) Waiting to run
Cloud Tests / e2e-tests (push) Blocked by required conditions
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
Deploy Apps Worker (Product 2) / Determine environment (push) Waiting to run
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Blocked by required conditions
Deploy Eliza Provisioning Worker / Determine environment (push) Waiting to run
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Blocked by required conditions
Dev Smoke / Classify changed paths (push) Waiting to run
Dev Smoke / bun run dev onboarding chat (push) Blocked by required conditions
Dev Smoke / Vite HMR dependency-level smoke (push) Blocked by required conditions
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Waiting to run
gitleaks / gitleaks (push) Waiting to run
Markdown Links / Relative Markdown Links (push) Waiting to run
Publish @elizaos/example-code / check_npm (push) Waiting to run
Publish @elizaos/example-code / publish_npm (push) Blocked by required conditions
Publish @elizaos/plugin-elizacloud / verify_version (push) Waiting to run
Publish @elizaos/plugin-elizacloud / publish_npm (push) Blocked by required conditions
Quality (Extended) / Homepage Build (PR smoke) (push) Waiting to run
Quality (Extended) / Comment-only diff guard (push) Waiting to run
Quality (Extended) / Format + Type Safety Ratchet (push) Waiting to run
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Waiting to run
Quality (Extended) / Develop Gate (lint) (push) Waiting to run
Sandbox Live Smoke / Sandbox live smoke (push) Waiting to run
Snap Build & Test / Build Snap (amd64) (push) Waiting to run
Snap Build & Test / Build Snap (arm64) (push) Waiting to run
supply-chain / sbom (push) Waiting to run
supply-chain / vulnerability-scan (push) Waiting to run
Build, Push & Deploy to Phala Cloud / build-and-push (push) Waiting to run
Test Packaging / Validate Packaging Configs (push) Waiting to run
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Waiting to run
Test Packaging / Pack & Test JS Tarballs (push) Waiting to run
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Waiting to run
UI Fixture E2E / ui-fixture-e2e (push) Waiting to run
UI Fixture E2E / fixture-e2e (push) Waiting to run
UI Story Gate / story-gate (push) Waiting to run
vault-ci / test (macos-latest) (push) Waiting to run
vault-ci / test (ubuntu-latest) (push) Waiting to run
vault-ci / test (windows-latest) (push) Waiting to run
vault-ci / app-core wiring tests (push) Waiting to run
verify-patches / verify patches/CHECKSUMS.sha256 (push) Waiting to run
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Waiting to run
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Waiting to run
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Waiting to run
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Waiting to run
Voice Benchmark Smoke / voice bench smoke summary (push) Blocked by required conditions
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Waiting to run
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Waiting to run
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Waiting to run
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Waiting to run
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Waiting to run
Test Packaging / Build & Test PyPI Package (push) Waiting to run
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
552 lines
22 KiB
YAML
552 lines
22 KiB
YAML
name: Apple Store Build & Publish
|
|
|
|
on:
|
|
workflow_call:
|
|
inputs:
|
|
platform:
|
|
description: "Platform to build"
|
|
required: false
|
|
type: string
|
|
default: both
|
|
track:
|
|
description: "Release track"
|
|
required: false
|
|
type: string
|
|
default: testflight
|
|
version:
|
|
description: "Version override (e.g. 2.0.0-beta.0)"
|
|
required: true
|
|
type: string
|
|
secrets:
|
|
# APPLE_ID, APPLE_TEAM_ID, APPLE_APP_SPECIFIC_PASSWORD are
|
|
# downgraded to `required: false` so this reusable workflow's
|
|
# secret contract does not startup_failure the parent
|
|
# (release-all.yml / release-orchestrator.yml) when called via
|
|
# `secrets: inherit` and the secrets are absent. The body's
|
|
# iOS-signing preflight checks for missing iOS secrets and
|
|
# exits with a clear ::error::, so the job still fails loudly
|
|
# when it can't sign — just inside the job, not at the parent
|
|
# workflow's startup. Same pattern as publish-apt-repo.yml
|
|
# (PR #7976), android-release.yml, update-homebrew.yml.
|
|
APPLE_ID:
|
|
required: false
|
|
APPLE_TEAM_ID:
|
|
required: false
|
|
ITC_TEAM_ID:
|
|
required: false
|
|
APP_STORE_APP_ID:
|
|
required: false
|
|
MATCH_PASSWORD:
|
|
required: false
|
|
MATCH_GIT_URL:
|
|
required: false
|
|
MATCH_GIT_BASIC_AUTHORIZATION:
|
|
required: false
|
|
APPLE_APP_SPECIFIC_PASSWORD:
|
|
required: false
|
|
MAS_CSC_LINK:
|
|
required: false
|
|
MAS_CSC_KEY_PASSWORD:
|
|
required: false
|
|
MAS_INSTALLER_CERT:
|
|
required: false
|
|
MAS_INSTALLER_KEY_PASSWORD:
|
|
required: false
|
|
APP_STORE_API_KEY_ID:
|
|
required: false
|
|
APP_STORE_API_ISSUER_ID:
|
|
required: false
|
|
APP_STORE_API_KEY_P8:
|
|
required: false
|
|
workflow_dispatch:
|
|
inputs:
|
|
platform:
|
|
description: "Platform to build"
|
|
type: choice
|
|
options:
|
|
- both
|
|
- ios
|
|
- macos
|
|
default: both
|
|
track:
|
|
description: "Release track"
|
|
type: choice
|
|
options:
|
|
- testflight
|
|
- app-store
|
|
default: testflight
|
|
version:
|
|
description: "Version override (e.g. 2.0.0-beta.0)"
|
|
required: false
|
|
type: string
|
|
|
|
concurrency:
|
|
group: apple-release-${{ github.ref }}
|
|
cancel-in-progress: false
|
|
|
|
env:
|
|
BUN_VERSION: "canary"
|
|
NODE_VERSION: "24.15.0"
|
|
LANG: en_US.UTF-8
|
|
LC_ALL: en_US.UTF-8
|
|
|
|
# Default to least privilege. Override per-job where needed.
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
prepare:
|
|
name: Prepare
|
|
runs-on: ${{ fromJSON(vars.HETZNER_FLEET_ONLINE == 'false' && '["ubuntu-24.04"]' || '["self-hosted","hetzner-robot"]') }}
|
|
outputs:
|
|
version: ${{ steps.ver.outputs.version }}
|
|
build_number: ${{ steps.ver.outputs.build_number }}
|
|
build_ios: ${{ steps.targets.outputs.ios }}
|
|
build_macos: ${{ steps.targets.outputs.macos }}
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
|
|
|
|
- name: Determine version
|
|
id: ver
|
|
run: |
|
|
if [[ -n "${{ inputs.version }}" ]]; then
|
|
VERSION="${{ inputs.version }}"
|
|
else
|
|
VERSION=$(node -p "require('./package.json').version")
|
|
fi
|
|
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
|
echo "build_number=$(date +%s)" >> "$GITHUB_OUTPUT"
|
|
echo "Version: $VERSION"
|
|
|
|
- name: Determine build targets
|
|
id: targets
|
|
run: |
|
|
PLATFORM="${{ inputs.platform || 'both' }}"
|
|
if [[ "$PLATFORM" == "both" || "$PLATFORM" == "ios" ]]; then
|
|
echo "ios=true" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "ios=false" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
if [[ "$PLATFORM" == "both" || "$PLATFORM" == "macos" ]]; then
|
|
echo "macos=true" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "macos=false" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
# ── iOS App Store ────────────────────────────────────────────────────
|
|
build-ios:
|
|
name: Build & Submit iOS
|
|
needs: prepare
|
|
if: needs.prepare.outputs.build_ios == 'true'
|
|
runs-on: macos-15
|
|
# Embedding the no-JIT Bun engine + CocoaPods adds build time over a thin
|
|
# cloud-only client, so allow more headroom than the bare-IPA budget.
|
|
timeout-minutes: 90
|
|
env:
|
|
# Make this split build (web + cap sync + ios-overlay + fastlane) behave
|
|
# like buildIos()'s App Store target, which bundles a working on-device
|
|
# agent. These env vars are normally set by configureIosAppStoreBuildDefaults
|
|
# / buildWeb("ios"); the workflow must set them itself because it bypasses
|
|
# buildIos(). Without them the IPA ships as a cloud-only thin client whose
|
|
# "start local agent" path hard-fails ("the JSContext compatibility
|
|
# transport is disabled outside iOS development builds").
|
|
# - ELIZA_BUILD_VARIANT/RELEASE_AUTHORITY: bake __ELIZA_BUILD_VARIANT__
|
|
# ="store" (store CSP + isNativeIosStoreBuild()) and embed the engine.
|
|
# - ELIZA_IOS_FULL_BUN_ENGINE=1: force the engine pod into the IPA.
|
|
# - VITE_ELIZA_IOS_FULL_BUN_AVAILABLE=1: tell the renderer the engine is
|
|
# present (isFullBunRuntimeBuiltIn) so the transport actually uses it.
|
|
# - VITE_ELIZA_IOS_RUNTIME_MODE=cloud-hybrid: App Store default mode; the
|
|
# user switching to "local" persists to localStorage and wins.
|
|
ELIZA_BUILD_VARIANT: store
|
|
ELIZA_RELEASE_AUTHORITY: apple-app-store
|
|
ELIZA_IOS_FULL_BUN_ENGINE: "1"
|
|
VITE_ELIZA_IOS_FULL_BUN_AVAILABLE: "1"
|
|
VITE_ELIZA_IOS_RUNTIME_MODE: cloud-hybrid
|
|
APPLE_ID: ${{ secrets.APPLE_ID }}
|
|
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
|
ITC_TEAM_ID: ${{ secrets.ITC_TEAM_ID }}
|
|
APP_STORE_APP_ID: ${{ secrets.APP_STORE_APP_ID }}
|
|
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
|
|
MATCH_GIT_URL: ${{ secrets.MATCH_GIT_URL }}
|
|
MATCH_GIT_BASIC_AUTHORIZATION: ${{ secrets.MATCH_GIT_BASIC_AUTHORIZATION }}
|
|
FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
|
|
- name: Setup Bun
|
|
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6
|
|
with:
|
|
bun-version: ${{ env.BUN_VERSION }}
|
|
|
|
- name: Install dependencies
|
|
run: bun install --ignore-scripts || bun install --ignore-scripts
|
|
|
|
- name: Materialize bun npm package binary
|
|
run: if [ -f node_modules/bun/install.js ]; then (cd node_modules/bun && node install.js); fi
|
|
|
|
- name: Set version
|
|
env:
|
|
RELEASE_VERSION: ${{ needs.prepare.outputs.version }}
|
|
run: |
|
|
node -e "
|
|
const fs = require('fs');
|
|
const v = process.env.RELEASE_VERSION;
|
|
for (const f of ['package.json', 'packages/app/package.json']) {
|
|
const pkg = JSON.parse(fs.readFileSync(f, 'utf8'));
|
|
pkg.version = v;
|
|
fs.writeFileSync(f, JSON.stringify(pkg, null, 2) + '\n');
|
|
}
|
|
"
|
|
|
|
- name: Build web assets
|
|
working-directory: packages/app
|
|
run: bun run build
|
|
|
|
- name: Validate iOS secrets
|
|
run: |
|
|
# Secrets required for signing the IPA. Without these the build
|
|
# itself cannot produce a signed artifact.
|
|
missing=()
|
|
[[ -z "$MATCH_GIT_URL" ]] && missing+=("MATCH_GIT_URL")
|
|
[[ -z "$MATCH_PASSWORD" ]] && missing+=("MATCH_PASSWORD")
|
|
[[ -z "$MATCH_GIT_BASIC_AUTHORIZATION" ]] && missing+=("MATCH_GIT_BASIC_AUTHORIZATION")
|
|
[[ -z "$ITC_TEAM_ID" ]] && missing+=("ITC_TEAM_ID")
|
|
if [[ ${#missing[@]} -gt 0 ]]; then
|
|
echo "::error::Missing required iOS signing secrets: ${missing[*]}"
|
|
echo "Configure these secrets in the repo settings to enable iOS App Store builds."
|
|
exit 1
|
|
fi
|
|
|
|
if [[ -z "$APP_STORE_APP_ID" ]]; then
|
|
echo "::error::APP_STORE_APP_ID is required for TestFlight/App Store delivery."
|
|
exit 1
|
|
fi
|
|
|
|
- name: Prepare CocoaPods trunk repo
|
|
run: bash packages/app-core/scripts/prepare-ios-cocoapods.sh
|
|
env:
|
|
LANG: en_US.UTF-8
|
|
|
|
- name: Sync Capacitor iOS
|
|
working-directory: packages/app
|
|
run: bun run cap:sync:ios
|
|
env:
|
|
LANG: en_US.UTF-8
|
|
|
|
# Build the on-device agent bundle the embedded Bun engine executes.
|
|
# buildIos() does this via buildMobileAgentBundle(); this split workflow
|
|
# must run it explicitly so the ios-overlay step below can stage
|
|
# packages/agent/dist-mobile-ios into the app's public/agent.
|
|
- name: Build iOS agent bundle
|
|
run: bun run --cwd packages/agent build:ios-bun
|
|
|
|
- name: Overlay iOS native files (Fastlane, AppDelegate, entitlements)
|
|
run: node packages/app-core/scripts/run-mobile-build.mjs ios-overlay
|
|
env:
|
|
LANG: en_US.UTF-8
|
|
|
|
- name: Update Xcode project version
|
|
working-directory: packages/app/ios/App/App.xcodeproj
|
|
run: |
|
|
VERSION="${{ needs.prepare.outputs.version }}"
|
|
BUILD="${{ needs.prepare.outputs.build_number }}"
|
|
sed -i '' "s/MARKETING_VERSION = .*/MARKETING_VERSION = $VERSION;/" project.pbxproj
|
|
sed -i '' "s/CURRENT_PROJECT_VERSION = .*/CURRENT_PROJECT_VERSION = $BUILD;/" project.pbxproj
|
|
|
|
- name: Install CocoaPods
|
|
working-directory: packages/app/ios/App
|
|
run: pod install
|
|
|
|
- name: Setup Ruby
|
|
uses: ruby/setup-ruby@d45b1a4e94b71acab930e56e79c6aa188764e7f9
|
|
with:
|
|
ruby-version: "4.0.3"
|
|
bundler-cache: true
|
|
working-directory: packages/app/ios
|
|
|
|
- name: Resolve app identifiers
|
|
working-directory: packages/app
|
|
run: |
|
|
# Read the runtime bundle id straight from pbxproj (the overlay step
|
|
# has already rewritten it to the elizaOS-specific id).
|
|
APP_ID=$(grep -m1 -E 'PRODUCT_BUNDLE_IDENTIFIER = [^.]+\.' ios/App/App.xcodeproj/project.pbxproj \
|
|
| grep -v WebsiteBlockerContentExtension \
|
|
| awk '{print $3}' | tr -d ';')
|
|
if [[ -z "$APP_ID" ]]; then
|
|
echo "::error::Could not resolve iOS bundle id from pbxproj"
|
|
exit 1
|
|
fi
|
|
APP_ID_EXTRA="${APP_ID}.WebsiteBlockerContentExtension"
|
|
echo "APP_IDENTIFIER=$APP_ID" >> "$GITHUB_ENV"
|
|
echo "APP_IDENTIFIER_EXTRA=$APP_ID_EXTRA" >> "$GITHUB_ENV"
|
|
echo "Resolved: APP_IDENTIFIER=$APP_ID, APP_IDENTIFIER_EXTRA=$APP_ID_EXTRA"
|
|
|
|
- name: Run iOS store preflight
|
|
working-directory: packages/app
|
|
run: bun run preflight:ios:store
|
|
|
|
- name: Build and upload
|
|
working-directory: packages/app/ios
|
|
env:
|
|
APP_IDENTIFIER: ${{ env.APP_IDENTIFIER }}
|
|
APP_IDENTIFIER_EXTRA: ${{ env.APP_IDENTIFIER_EXTRA }}
|
|
run: |
|
|
if [[ ! -f fastlane/Fastfile ]]; then
|
|
echo "::error::fastlane/Fastfile missing at packages/app/ios/. cap:sync:ios or run-mobile-build.mjs should have copied it."
|
|
ls -la fastlane/ || true
|
|
exit 1
|
|
fi
|
|
TRACK="${{ inputs.track || 'testflight' }}"
|
|
if [[ "$TRACK" == "app-store" ]]; then
|
|
bundle exec fastlane release
|
|
else
|
|
bundle exec fastlane beta
|
|
fi
|
|
|
|
- name: Upload IPA artifact
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
|
|
with:
|
|
name: ios-ipa
|
|
path: packages/app/ios/build/Eliza.ipa
|
|
retention-days: 90
|
|
if-no-files-found: error
|
|
|
|
# ── Mac App Store ────────────────────────────────────────────────────
|
|
build-macos:
|
|
name: Build & Submit macOS
|
|
needs: prepare
|
|
if: needs.prepare.outputs.build_macos == 'true'
|
|
runs-on: macos-15
|
|
timeout-minutes: 75
|
|
env:
|
|
APPLE_ID: ${{ secrets.APPLE_ID }}
|
|
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
|
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
|
CSC_LINK: ${{ secrets.MAS_CSC_LINK }}
|
|
CSC_KEY_PASSWORD: ${{ secrets.MAS_CSC_KEY_PASSWORD }}
|
|
MAS_INSTALLER_CERT: ${{ secrets.MAS_INSTALLER_CERT }}
|
|
MAS_INSTALLER_KEY_PASSWORD: ${{ secrets.MAS_INSTALLER_KEY_PASSWORD }}
|
|
APP_STORE_API_KEY_ID: ${{ secrets.APP_STORE_API_KEY_ID }}
|
|
APP_STORE_API_ISSUER_ID: ${{ secrets.APP_STORE_API_ISSUER_ID }}
|
|
APP_STORE_API_KEY_P8: ${{ secrets.APP_STORE_API_KEY_P8 }}
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
|
|
- name: Setup Bun
|
|
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6
|
|
with:
|
|
bun-version: ${{ env.BUN_VERSION }}
|
|
|
|
- name: Install dependencies
|
|
run: bun install --ignore-scripts || bun install --ignore-scripts
|
|
|
|
- name: Materialize bun npm package binary
|
|
run: if [ -f node_modules/bun/install.js ]; then (cd node_modules/bun && node install.js); fi
|
|
|
|
- name: Set version
|
|
env:
|
|
RELEASE_VERSION: ${{ needs.prepare.outputs.version }}
|
|
run: |
|
|
node -e "
|
|
const fs = require('fs');
|
|
const v = process.env.RELEASE_VERSION;
|
|
for (const f of ['package.json', 'packages/app/package.json', 'packages/app-core/platforms/electrobun/package.json']) {
|
|
try {
|
|
const pkg = JSON.parse(fs.readFileSync(f, 'utf8'));
|
|
pkg.version = v;
|
|
fs.writeFileSync(f, JSON.stringify(pkg, null, 2) + '\n');
|
|
} catch(e) {}
|
|
}
|
|
const cfgPath = 'packages/app-core/platforms/electrobun/electrobun.config.ts';
|
|
let cfg = fs.readFileSync(cfgPath, 'utf8');
|
|
cfg = cfg.replace(/version:\s*\"[^\"]+\"/, 'version: \"' + v + '\"');
|
|
fs.writeFileSync(cfgPath, cfg);
|
|
"
|
|
|
|
- name: Build core dist
|
|
run: |
|
|
bunx tsdown
|
|
echo '{"type":"module"}' > dist/package.json
|
|
node --import tsx packages/scripts/write-build-info.ts
|
|
|
|
- name: Build renderer
|
|
working-directory: packages/app
|
|
run: |
|
|
bun install --ignore-scripts
|
|
if [ -f node_modules/bun/install.js ]; then (cd node_modules/bun && node install.js); fi
|
|
bun run build:web
|
|
|
|
- name: Stage renderer
|
|
run: |
|
|
node packages/scripts/rm-path-recursive.mjs packages/app-core/platforms/electrobun/renderer
|
|
cp -r packages/app/dist packages/app-core/platforms/electrobun/renderer
|
|
|
|
- name: Build native macOS effects
|
|
working-directory: packages/app-core/platforms/electrobun
|
|
run: bun run build:native-effects
|
|
|
|
- name: Build webview preload
|
|
working-directory: packages/app-core/platforms/electrobun
|
|
run: bun run build:preload
|
|
|
|
- name: Validate signing secrets
|
|
run: |
|
|
missing=()
|
|
[[ -z "$CSC_LINK" ]] && missing+=("MAS_CSC_LINK")
|
|
[[ -z "$CSC_KEY_PASSWORD" ]] && missing+=("MAS_CSC_KEY_PASSWORD")
|
|
[[ -z "$MAS_INSTALLER_CERT" ]] && missing+=("MAS_INSTALLER_CERT")
|
|
[[ -z "$MAS_INSTALLER_KEY_PASSWORD" ]] && missing+=("MAS_INSTALLER_KEY_PASSWORD")
|
|
[[ -z "$APP_STORE_API_KEY_ID" ]] && missing+=("APP_STORE_API_KEY_ID")
|
|
[[ -z "$APP_STORE_API_ISSUER_ID" ]] && missing+=("APP_STORE_API_ISSUER_ID")
|
|
[[ -z "$APP_STORE_API_KEY_P8" ]] && missing+=("APP_STORE_API_KEY_P8")
|
|
if [[ ${#missing[@]} -gt 0 ]]; then
|
|
echo "::error::Missing required signing secrets: ${missing[*]}"
|
|
echo "Configure these secrets in the repo settings to enable Mac App Store builds."
|
|
exit 1
|
|
fi
|
|
|
|
- name: Import signing certificates
|
|
run: |
|
|
KEYCHAIN="build-mas.keychain"
|
|
KEYCHAIN_PASS="$(openssl rand -hex 16)"
|
|
security create-keychain -p "$KEYCHAIN_PASS" "$KEYCHAIN"
|
|
security default-keychain -s "$KEYCHAIN"
|
|
security unlock-keychain -p "$KEYCHAIN_PASS" "$KEYCHAIN"
|
|
security set-keychain-settings -t 3600 "$KEYCHAIN"
|
|
|
|
echo "$CSC_LINK" | base64 -d > /tmp/mas-app.p12
|
|
security import /tmp/mas-app.p12 -k "$KEYCHAIN" -P "$CSC_KEY_PASSWORD" \
|
|
-T /usr/bin/codesign -T /usr/bin/productbuild
|
|
rm -f /tmp/mas-app.p12
|
|
|
|
echo "$MAS_INSTALLER_CERT" | base64 -d > /tmp/mas-installer.p12
|
|
security import /tmp/mas-installer.p12 -k "$KEYCHAIN" -P "$MAS_INSTALLER_KEY_PASSWORD" \
|
|
-T /usr/bin/productbuild
|
|
rm -f /tmp/mas-installer.p12
|
|
|
|
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASS" "$KEYCHAIN"
|
|
|
|
MAS_IDENTITY=$(security find-identity -v -p codesigning "$KEYCHAIN" | grep "Apple Distribution" | head -1 | awk -F'"' '{print $2}')
|
|
INSTALLER_IDENTITY=$(security find-identity -v "$KEYCHAIN" | grep "3rd Party Mac Developer Installer" | head -1 | awk -F'"' '{print $2}')
|
|
echo "MAS_IDENTITY=$MAS_IDENTITY" >> "$GITHUB_ENV"
|
|
echo "INSTALLER_IDENTITY=$INSTALLER_IDENTITY" >> "$GITHUB_ENV"
|
|
|
|
- name: Build Electrobun app
|
|
working-directory: packages/app-core/platforms/electrobun
|
|
run: ELECTROBUN_SKIP_CODESIGN=1 bun run build
|
|
env:
|
|
ELECTROBUN_BUILD_ENV: "stable"
|
|
|
|
- name: Re-sign for Mac App Store
|
|
working-directory: packages/app-core/platforms/electrobun
|
|
run: |
|
|
APP_PATH=$(find build -name "*.app" -maxdepth 2 | head -1)
|
|
ENTITLEMENTS="${GITHUB_WORKSPACE}/packages/app-core/platforms/electrobun/entitlements/mas.entitlements"
|
|
CHILD_ENTITLEMENTS="${GITHUB_WORKSPACE}/packages/app-core/platforms/electrobun/entitlements/mas-child.entitlements"
|
|
|
|
# Sign frameworks and dylibs with child entitlements
|
|
find "$APP_PATH" -name "*.dylib" -o -name "*.framework" | while read -r lib; do
|
|
codesign --force --options runtime --sign "$MAS_IDENTITY" \
|
|
--entitlements "$CHILD_ENTITLEMENTS" "$lib"
|
|
done
|
|
|
|
# Sign helper apps
|
|
find "$APP_PATH/Contents/Frameworks" -name "*.app" 2>/dev/null | while read -r helper; do
|
|
codesign --force --deep --options runtime --sign "$MAS_IDENTITY" \
|
|
--entitlements "$CHILD_ENTITLEMENTS" "$helper"
|
|
done
|
|
|
|
# Sign main app bundle
|
|
codesign --force --deep --options runtime --sign "$MAS_IDENTITY" \
|
|
--entitlements "$ENTITLEMENTS" "$APP_PATH"
|
|
|
|
codesign --verify --deep --strict "$APP_PATH"
|
|
|
|
- name: Create installer pkg
|
|
working-directory: packages/app-core/platforms/electrobun
|
|
run: |
|
|
APP_PATH=$(find build -name "*.app" -maxdepth 2 | head -1)
|
|
VERSION="${{ needs.prepare.outputs.version }}"
|
|
PKG_NAME="Eliza-${VERSION}-mas.pkg"
|
|
|
|
productbuild --sign "$INSTALLER_IDENTITY" \
|
|
--component "$APP_PATH" /Applications \
|
|
"build/$PKG_NAME"
|
|
|
|
echo "PKG_PATH=packages/app-core/platforms/electrobun/build/$PKG_NAME" >> "$GITHUB_ENV"
|
|
|
|
- name: Upload to App Store Connect
|
|
run: |
|
|
# xcrun altool requires the .p8 private key at the canonical path:
|
|
# ~/.appstoreconnect/private_keys/AuthKey_<KEY_ID>.p8
|
|
# The key content is provided via the APP_STORE_API_KEY_P8 secret.
|
|
KEY_DIR="$HOME/.appstoreconnect/private_keys"
|
|
mkdir -p "$KEY_DIR"
|
|
KEY_FILE="$KEY_DIR/AuthKey_${{ secrets.APP_STORE_API_KEY_ID }}.p8"
|
|
printf '%s' "$APP_STORE_API_KEY_P8" > "$KEY_FILE"
|
|
chmod 600 "$KEY_FILE"
|
|
|
|
xcrun altool --upload-app \
|
|
--file "$PKG_PATH" \
|
|
--type macos \
|
|
--apiKey "${{ secrets.APP_STORE_API_KEY_ID }}" \
|
|
--apiIssuer "${{ secrets.APP_STORE_API_ISSUER_ID }}" \
|
|
--show-progress
|
|
|
|
rm -f "$KEY_FILE"
|
|
|
|
- name: Upload pkg artifact
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
|
|
with:
|
|
name: macos-mas-pkg
|
|
path: ${{ env.PKG_PATH }}
|
|
retention-days: 90
|
|
if-no-files-found: error
|
|
|
|
# ── Summary ──────────────────────────────────────────────────────────
|
|
summary:
|
|
name: Release Summary
|
|
needs: [prepare, build-ios, build-macos]
|
|
if: always()
|
|
runs-on: ${{ fromJSON(vars.HETZNER_FLEET_ONLINE == 'false' && '["ubuntu-24.04"]' || '["self-hosted","hetzner-robot"]') }}
|
|
steps:
|
|
- name: Summary
|
|
run: |
|
|
{
|
|
echo "## Apple Store Release Summary"
|
|
echo ""
|
|
echo "**Version:** ${{ needs.prepare.outputs.version }}"
|
|
echo "**Build:** ${{ needs.prepare.outputs.build_number }}"
|
|
echo ""
|
|
echo "| Platform | Status |"
|
|
echo "|----------|--------|"
|
|
echo "| iOS App Store | ${{ needs.build-ios.result || 'skipped' }} |"
|
|
echo "| Mac App Store | ${{ needs.build-macos.result || 'skipped' }} |"
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
- name: Require enabled Apple releases succeeded
|
|
run: |
|
|
failed=0
|
|
if [[ "${{ needs.prepare.outputs.build_ios }}" == "true" && "${{ needs.build-ios.result }}" != "success" ]]; then
|
|
echo "::error::iOS release result was '${{ needs.build-ios.result }}', expected success"
|
|
failed=1
|
|
fi
|
|
if [[ "${{ needs.prepare.outputs.build_macos }}" == "true" && "${{ needs.build-macos.result }}" != "success" ]]; then
|
|
echo "::error::Mac App Store release result was '${{ needs.build-macos.result }}', expected success"
|
|
failed=1
|
|
fi
|
|
exit "$failed"
|