# Codemagic CI/CD — builds the Biz Connect iOS app (Capacitor shell over the live web UI) and uploads it # to TestFlight / App Store Connect. No Mac needed: this runs on Codemagic's macOS cloud instances. # # The app is a thin Capacitor wrapper that loads https://remote.bizgaze.com, so there's no bundled web # code to build here — we generate the iOS project, patch its privacy strings, sign, archive and upload. # # ── One-time setup (see mobile/IOS_SETUP.md for the click-by-click) ──────────────────────────────── # 1. App Store Connect: create the app with bundle id com.bizgaze.connect # 2. Codemagic → Teams/Integrations → App Store Connect: add your ASC API key (issuer id, key id, .p8). # Name the integration exactly: BizGaze App Store Connect # 3. That's it — automatic code signing fetches/creates the distribution cert + profile from that key. workflows: ios-testflight: name: Biz Connect iOS → TestFlight max_build_duration: 60 instance_type: mac_mini_m2 integrations: app_store_connect: BizGaze App Store Connect # ← must match the integration name you create environment: # NOTE: we deliberately do NOT use an `ios_signing:` block here. That block makes Codemagic # try to *fetch an existing* provisioning profile at build startup — it never creates one — so on # a brand-new app it fails init with "No matching profiles found …". Instead the "Set up code # signing" script below runs `fetch-signing-files … --create`, which creates the distribution # certificate + profile on first run, then `xcode-project use-profiles` wires them into the project. groups: - ios_signing # ← Codemagic variable group holding CERTIFICATE_PRIVATE_KEY (secure). See below. vars: BUNDLE_ID: "com.bizgaze.connect" XCODE_PROJECT: "mobile/ios/App/App.xcodeproj" XCODE_SCHEME: "App" node: 22 xcode: latest cocoapods: default scripts: - name: Install JS dependencies script: | cd mobile # npm install (not ci): the dependency set changed to Capacitor 7 + the safe-area/keyboard # plugins, so we let npm resolve a fresh tree rather than require a pre-synced lockfile. npm install - name: Generate the iOS project (Capacitor) script: | cd mobile # `cap add ios` scaffolds ios/App; safe to re-run — it no-ops if it already exists. # Capacitor 8 Swift Package Manager: generate an SPM project (no Podfile). LiveKit is pulled via the # native-call plugin's Package.swift; `cap sync` wires all local plugins into the CapApp-SPM package. if [ ! -d "ios" ]; then npx cap add ios --packagemanager SPM; fi npx cap sync ios # Sanity: the Xcode project must exist. Print the iOS dir so the log shows the SPM layout (CapApp-SPM # present, NO Podfile) — if a Podfile appears, the SPM flag didn't take and we'd need to fix it. test -d ios/App/App.xcodeproj || { echo "ERROR: iOS Xcode project not generated"; ls -la ios/App || true; exit 1; } echo "iOS project layout:"; ls -la ios/App # ── Diagnose local-plugin SPM wiring (the regression: our file: plugins didn't function at runtime) ── echo "=== local plugins in node_modules — symlink vs copy, and is Package.swift present? ===" for p in native-call media-library audio-route share-inbox file-opener; do echo "-- $p --"; ls -ld "node_modules/$p" 2>/dev/null || echo " (dir missing)" ( ls "node_modules/$p/Package.swift" >/dev/null 2>&1 && echo " Package.swift PRESENT" ) || echo " Package.swift MISSING" done echo "=== CapApp-SPM Package.swift — are our local plugins + LiveKit wired in? ===" CAPSPM=$(find ios -path "*CapApp-SPM*Package.swift" 2>/dev/null | head -1) if [ -n "$CAPSPM" ]; then echo "found: $CAPSPM"; cat "$CAPSPM"; else echo " CapApp-SPM/Package.swift NOT FOUND"; find ios -name Package.swift 2>/dev/null; fi # App icon + splash from resources/icon.png & resources/splash*.png (1024x1024 icon, 2732² splash). npx capacitor-assets generate --ios || echo "asset generation skipped" - name: Patch Info.plist (App-Review privacy strings) + bundle id script: | bash mobile/scripts/ios-patch.sh - name: Add the Share Extension target script: | # Inject the second target (Biz Connect in the iOS share sheet) into the freshly-generated # Xcode project. Uses the `xcodeproj` gem that ships with CocoaPods, so no extra install. # Runs BEFORE pod install: the extension uses no pods, and this way the workspace that pods # generates already contains the new target. ruby mobile/scripts/add-share-extension.rb - name: Set up code signing script: | # Create the distribution certificate + provisioning profile from the ASC API key and add the # cert to the keychain. NOTE: `xcode-project use-profiles` is intentionally NOT here — it must # run AFTER `pod install` generates the workspace, otherwise it fails to wire the profile into # the App target and the archive dies with "App requires a provisioning profile". # # --certificate-key is REQUIRED for reusable signing: without it, --create makes a throwaway # distribution cert whose private key dies with the build machine, so the next build finds a # cert it has no key for ("Cannot save Signing Certificates without certificate private key"). # By passing our own fixed private key (CERTIFICATE_PRIVATE_KEY, a secure var in the # `ios_signing` group), the cert is created once from that key and reused by every build. # # TWO bundle ids now need signing: the app AND the share extension (.share). Each gets its # own App Store profile. The App Group capability (group.com.bizgaze.connect) must be enabled on # BOTH App IDs in the Apple Developer portal — see mobile/IOS_SETUP.md. fetch-signing-files # registers a missing bundle id and creates its profile, but does NOT toggle the App Group # capability, so that stays a one-time manual step. keychain initialize app-store-connect fetch-signing-files "$BUNDLE_ID" \ --type IOS_APP_STORE \ --certificate-key="@env:CERTIFICATE_PRIVATE_KEY" \ --create app-store-connect fetch-signing-files "${BUNDLE_ID}.share" \ --type IOS_APP_STORE \ --certificate-key="@env:CERTIFICATE_PRIVATE_KEY" \ --create keychain add-certificates # (No "Install CocoaPods" step under SPM — there is no Podfile. Xcode resolves the Swift packages # (Capacitor, plugins, LiveKit + its WebRTC/UniFFI/SwiftProtobuf) during the archive below.) - name: Build the signed IPA script: | # Apply the fetched provisioning profile(s) to the Xcode project NOW that the workspace exists, # then archive. use-profiles scans for **/*.xcodeproj under the repo root and sets manual # signing (team + profile specifier) on the matching App target. xcode-project use-profiles # `xcode-project build-ipa` prints a PRETTIFIED summary and swallows the raw xcodebuild "error:" # lines — a failed archive shows only "Failed to archive" with no reason. On failure, surface the # actual errors from the raw log so we don't have to dig through the artifact. if ! xcode-project build-ipa --project "$XCODE_PROJECT" --scheme "$XCODE_SCHEME"; then echo "======================= xcodebuild errors =======================" grep -h -E "error:|errSec|Provisioning profile|entitlement|Code ?Sign|does not (support|contain)|requires a provisioning|No profile|No signing|doesn't (include|match)|Command .* failed" /tmp/xcodebuild_logs/*.log 2>/dev/null | grep -vi "warning:" | tail -60 || echo "(no matching lines — open the xcodebuild_logs artifact)" echo "=================================================================" exit 1 fi artifacts: - build/ios/ipa/*.ipa - /tmp/xcodebuild_logs/*.log publishing: app_store_connect: auth: integration # Upload the build to App Store Connect. It is immediately usable for INTERNAL TestFlight testing # (no Apple review). We keep external-beta submission OFF for now: submit_to_testflight=true would # push the build to EXTERNAL beta review, which requires the Test Information (feedback email + # beta review contact + a demo login, since our app needs sign-in) to be filled in first, and # fails the build until then. Flip to true (and add `beta_groups:` + fill Test Information at # App Store Connect → TestFlight → Test Information) when you want outside testers. submit_to_testflight: false # Flip this to true (and add a `submit_to_app_store` group with reviewer notes) once you're ready # to push a build to public App Store review instead of only TestFlight. # submit_to_app_store: false # No email recipients here on purpose — build status is watched on the Codemagic dashboard. Add # per-user notifications in the Codemagic UI (or a `publishing.email` block) later if you want them.