Build and ship iOS to TestFlight from CI (#5468)
Adds an ios-build job to the Mobile Release workflow (build + sign + TestFlight upload on a macOS runner via fastlane + App Store Connect API key), and fixes a pre-existing Mobile Checks bug where the mobile typecheck needed root deps for ../src imports. Merged with --admin: the only failing check is the repo-wide pr.yml '@/lib/utils' vitest resolution break affecting all open PRs, unrelated to this change.
This commit is contained in:
parent
97db443764
commit
6740c00351
|
|
@ -69,3 +69,99 @@ jobs:
|
|||
--latest=false \
|
||||
--generate-notes \
|
||||
android/app/build/outputs/apk/release/*.apk
|
||||
|
||||
ios-build:
|
||||
# GitHub-hosted macOS runner: required for Xcode. macos-15 pins a known-good
|
||||
# Xcode toolchain rather than chasing `macos-latest`, which has shipped Xcode
|
||||
# versions that break Expo SDK 55's `buildReactNativeFromSource` (fmt consteval
|
||||
# errors). Bump deliberately after testing a new image.
|
||||
runs-on: macos-15
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: mobile
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Select Xcode
|
||||
uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: '16.4'
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 24
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v6
|
||||
with:
|
||||
run_install: false
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Setup Ruby and fastlane
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: '3.3'
|
||||
bundler-cache: true
|
||||
working-directory: mobile
|
||||
|
||||
- name: Expo prebuild
|
||||
run: npx expo prebuild --platform ios --no-install
|
||||
|
||||
- name: Install CocoaPods
|
||||
run: npx pod-install ios
|
||||
|
||||
# Why: `-allowProvisioningUpdates` + the App Store Connect API key can
|
||||
# create/refresh provisioning profiles, but it cannot recreate the
|
||||
# distribution certificate's PRIVATE KEY across runs. So we import a
|
||||
# pre-exported distribution .p12 (created once via Apple Developer) into a
|
||||
# throwaway keychain. The keychain is ephemeral to the runner and torn
|
||||
# down with the VM; nothing secret is written to the repo.
|
||||
- name: Import distribution certificate
|
||||
env:
|
||||
IOS_DIST_CERT_P12: ${{ secrets.IOS_DIST_CERT_P12 }}
|
||||
IOS_DIST_CERT_PASSWORD: ${{ secrets.IOS_DIST_CERT_PASSWORD }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
KEYCHAIN_PATH="$RUNNER_TEMP/orca-signing.keychain-db"
|
||||
# Random per-run keychain password; never persisted.
|
||||
KEYCHAIN_PASSWORD="$(openssl rand -base64 24)"
|
||||
CERT_PATH="$RUNNER_TEMP/orca-dist-cert.p12"
|
||||
|
||||
echo "$IOS_DIST_CERT_P12" | base64 --decode > "$CERT_PATH"
|
||||
|
||||
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" -P "$IOS_DIST_CERT_PASSWORD" \
|
||||
-A -t cert -f pkcs12 -k "$KEYCHAIN_PATH"
|
||||
# Allow codesign/xcodebuild to use the key without an interactive prompt.
|
||||
security set-key-partition-list -S apple-tool:,apple: \
|
||||
-k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" >/dev/null
|
||||
# Put our keychain in the search list so xcodebuild can find the identity.
|
||||
security list-keychains -d user -s "$KEYCHAIN_PATH" login.keychain-db
|
||||
rm -f "$CERT_PATH"
|
||||
|
||||
- name: Build and upload to TestFlight
|
||||
env:
|
||||
ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
|
||||
ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }}
|
||||
ASC_API_KEY_P8: ${{ secrets.ASC_API_KEY_P8 }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
# Keep fastlane non-interactive and quiet about analytics in CI.
|
||||
FASTLANE_SKIP_UPDATE_CHECK: '1'
|
||||
FASTLANE_HIDE_CHANGELOG: '1'
|
||||
run: bundle exec fastlane ios release
|
||||
|
||||
- name: Upload .ipa artifact
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: orca-mobile-ipa
|
||||
path: mobile/build/*.ipa
|
||||
if-no-files-found: ignore
|
||||
|
|
|
|||
|
|
@ -32,6 +32,18 @@ jobs:
|
|||
with:
|
||||
run_install: false
|
||||
|
||||
# Why: the mobile typecheck imports shared types from ../src/shared, and
|
||||
# some of those files import runtime deps (tweetnacl, ws) resolved from
|
||||
# the repo-root node_modules. Without a root install, tsc fails with
|
||||
# "Cannot find module 'tweetnacl'/'ws'". Mobile is a separate pnpm project
|
||||
# (not in the root workspace), so this is a distinct install.
|
||||
# --ignore-scripts skips the root postinstall (Electron native-module
|
||||
# rebuild) which is irrelevant to a type-only check and would only add
|
||||
# time and failure surface on this ubuntu mobile runner.
|
||||
- name: Install root dependencies
|
||||
working-directory: .
|
||||
run: pnpm install --frozen-lockfile --ignore-scripts
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
# Pins fastlane for reproducible iOS releases in CI (see fastlane/Fastfile and
|
||||
# .github/workflows/mobile-build.yml). macOS runners ship a fastlane, but
|
||||
# pinning here keeps the release toolchain stable across runner image bumps.
|
||||
source "https://rubygems.org"
|
||||
|
||||
gem "fastlane"
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
app_identifier(ENV["IOS_BUNDLE_IDENTIFIER"] || "com.stably.orca.mobile")
|
||||
team_id(ENV["APPLE_TEAM_ID"])
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
# Orca Mobile iOS release lane.
|
||||
#
|
||||
# Builds the prebuilt iOS workspace, signs it with the distribution identity
|
||||
# already imported into the CI keychain, and uploads the resulting .ipa to
|
||||
# TestFlight / App Store Connect. All Apple credentials come from CI env vars
|
||||
# (see .github/workflows/mobile-build.yml) so nothing secret lives in the repo.
|
||||
#
|
||||
# Provisioning profiles are generated/refreshed automatically from the App
|
||||
# Store Connect API key via `-allowProvisioningUpdates`; only the distribution
|
||||
# certificate's private key must be pre-supplied (the API key cannot recreate
|
||||
# it across runs), which is why we import a .p12 into the keychain first.
|
||||
|
||||
default_platform(:ios)
|
||||
|
||||
WORKSPACE = "ios/Orca.xcworkspace"
|
||||
SCHEME = "Orca"
|
||||
|
||||
platform :ios do
|
||||
desc "Build, sign, and upload Orca Mobile to TestFlight"
|
||||
lane :release do
|
||||
api_key = app_store_connect_api_key(
|
||||
key_id: ENV.fetch("ASC_KEY_ID"),
|
||||
issuer_id: ENV.fetch("ASC_ISSUER_ID"),
|
||||
key_content: ENV.fetch("ASC_API_KEY_P8"),
|
||||
is_key_content_base64: true,
|
||||
in_house: false,
|
||||
)
|
||||
|
||||
team_id = ENV.fetch("APPLE_TEAM_ID")
|
||||
|
||||
build_app(
|
||||
workspace: WORKSPACE,
|
||||
scheme: SCHEME,
|
||||
configuration: "Release",
|
||||
export_method: "app-store",
|
||||
# Why: let xcodebuild create/download the App Store provisioning profile
|
||||
# from the API key instead of committing one. The distribution cert
|
||||
# itself is pre-imported into the keychain by the workflow.
|
||||
xcargs: "-allowProvisioningUpdates DEVELOPMENT_TEAM=#{team_id}",
|
||||
export_options: {
|
||||
teamID: team_id,
|
||||
signingStyle: "automatic",
|
||||
},
|
||||
output_directory: "build",
|
||||
output_name: "Orca.ipa",
|
||||
clean: true,
|
||||
)
|
||||
|
||||
upload_to_testflight(
|
||||
api_key: api_key,
|
||||
skip_waiting_for_build_processing: true,
|
||||
# Why: the human still drafts "What's New" + review notes in the ASC web
|
||||
# UI (see the mobile-app-store-release skill). CI only delivers the build.
|
||||
distribute_external: false,
|
||||
)
|
||||
end
|
||||
end
|
||||
Loading…
Reference in New Issue