fix(trinity): make Cloud Trinity actually reachable (live gateway URL + honest fallback + forceCloud precedence) #283
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: iOS CI | |
| # --------------------------------------------------------------------------- | |
| # Builds and tests the MTRX iOS app on every push and pull request targeting | |
| # main. Runs on a macOS runner so xcodebuild can drive a real iOS Simulator. | |
| # | |
| # The pipeline has three jobs: | |
| # 1. lint — SwiftLint on the whole workspace (fast, fails early) | |
| # 2. build — xcodebuild build-for-testing + test on the iOS simulator | |
| # 3. archive — optional TestFlight upload, only runs on tag pushes and | |
| # only if the APP_STORE_CONNECT_* secrets are set | |
| # | |
| # Secrets expected (set in Settings → Secrets): | |
| # APPLE_TEAM_ID — Apple Developer Team ID | |
| # APP_STORE_CONNECT_API_KEY_ID — ASC API key identifier | |
| # APP_STORE_CONNECT_API_ISSUER — ASC issuer UUID | |
| # APP_STORE_CONNECT_API_KEY — Base64-encoded .p8 private key | |
| # MTRX_RUNTIME_URL — Backend URL injected into tests (optional) | |
| # --------------------------------------------------------------------------- | |
| on: | |
| push: | |
| branches: [main] | |
| tags: ["v*"] | |
| pull_request: | |
| branches: [main] | |
| workflow_dispatch: | |
| # Cancel in-progress runs when a new commit is pushed to the same ref. | |
| concurrency: | |
| group: ios-${{ github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| SCHEME: MTRX | |
| PROJECT: MTRX.xcodeproj | |
| DESTINATION: "platform=iOS Simulator,name=iPhone 17,OS=latest" | |
| DERIVED_DATA: ${{ github.workspace }}/DerivedData | |
| jobs: | |
| # --------------------------------------------------------------------- lint | |
| lint: | |
| name: SwiftLint | |
| runs-on: macos-14 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install SwiftLint | |
| run: | | |
| if ! command -v swiftlint >/dev/null; then | |
| brew install swiftlint | |
| fi | |
| swiftlint version | |
| - name: Run SwiftLint (strict on warnings) | |
| run: swiftlint --strict --reporter github-actions-logging | |
| # -------------------------------------------------------------------- build | |
| build: | |
| name: Build & Test | |
| # macos-26 ships the Xcode 26 / iOS 26 SDK the app is written against | |
| # (Liquid Glass `glassEffect`, `NWPath.isUltraConstrained`, FoundationModels, | |
| # `@retroactive`, and modern SwiftUI @MainActor isolation). macos-14 | |
| # (Xcode 15.4 / iOS 17 SDK) is too old to compile those symbols. | |
| runs-on: macos-26 | |
| needs: lint | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Show Xcode versions | |
| run: | | |
| xcode-select -p | |
| xcodebuild -version | |
| - name: Cache DerivedData | |
| uses: actions/cache@v4 | |
| with: | |
| path: ${{ env.DERIVED_DATA }} | |
| key: derived-${{ runner.os }}-${{ hashFiles('**/*.pbxproj', '**/Package.resolved') }} | |
| restore-keys: | | |
| derived-${{ runner.os }}- | |
| - name: Resolve Swift Package dependencies | |
| run: | | |
| xcodebuild \ | |
| -project "${{ env.PROJECT }}" \ | |
| -scheme "${{ env.SCHEME }}" \ | |
| -resolvePackageDependencies | |
| - name: Build for testing | |
| run: | | |
| set -o pipefail | |
| xcodebuild \ | |
| -project "${{ env.PROJECT }}" \ | |
| -scheme "${{ env.SCHEME }}" \ | |
| -destination "${{ env.DESTINATION }}" \ | |
| -derivedDataPath "${{ env.DERIVED_DATA }}" \ | |
| -configuration Debug \ | |
| -enableCodeCoverage YES \ | |
| CODE_SIGNING_REQUIRED=NO \ | |
| CODE_SIGNING_ALLOWED=NO \ | |
| build-for-testing | xcpretty | |
| - name: Verify the widget extension is embedded | |
| # Phase 6: the MTRXWidgets appex must build and embed in the host app, | |
| # else the Home Screen widgets + Live Activity silently vanish. | |
| run: | | |
| set -e | |
| app=$(find "${{ env.DERIVED_DATA }}/Build/Products" -name 'MTRX.app' | head -1) | |
| test -n "$app" || { echo "MTRX.app not found in build products"; exit 1; } | |
| appex="$app/PlugIns/MTRXWidgets.appex" | |
| test -d "$appex" || { echo "FAIL: MTRXWidgets.appex not embedded"; exit 1; } | |
| echo "OK: widget extension embedded at $appex" | |
| - name: Run unit tests | |
| env: | |
| MTRX_RUNTIME_URL: ${{ secrets.MTRX_RUNTIME_URL || 'http://localhost:8000' }} | |
| run: | | |
| set -o pipefail | |
| xcodebuild \ | |
| -project "${{ env.PROJECT }}" \ | |
| -scheme "${{ env.SCHEME }}" \ | |
| -destination "${{ env.DESTINATION }}" \ | |
| -derivedDataPath "${{ env.DERIVED_DATA }}" \ | |
| -enableCodeCoverage YES \ | |
| CODE_SIGNING_REQUIRED=NO \ | |
| CODE_SIGNING_ALLOWED=NO \ | |
| test-without-building | xcpretty --report junit | |
| - name: Upload test results | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: test-results | |
| path: | | |
| build/reports/junit.xml | |
| ${{ env.DERIVED_DATA }}/Logs/Test/*.xcresult | |
| if-no-files-found: ignore | |
| retention-days: 14 | |
| - name: Upload coverage summary | |
| if: always() | |
| run: | | |
| if command -v xcrun >/dev/null; then | |
| RESULT=$(find "${{ env.DERIVED_DATA }}/Logs/Test" -name '*.xcresult' | head -n 1) | |
| if [ -n "$RESULT" ]; then | |
| xcrun xccov view --report --only-targets "$RESULT" || true | |
| fi | |
| fi | |
| # ------------------------------------------------------------------ archive | |
| archive: | |
| name: Archive & TestFlight | |
| # Match the build job's toolchain (Xcode 26 / iOS 26 SDK). | |
| runs-on: macos-26 | |
| needs: build | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Bail out if TestFlight secrets are missing | |
| run: | | |
| if [ -z "${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}" ]; then | |
| echo "TestFlight secrets not configured — skipping archive." | |
| echo "SKIP_ARCHIVE=1" >> $GITHUB_ENV | |
| fi | |
| - name: Build archive | |
| if: env.SKIP_ARCHIVE != '1' | |
| run: | | |
| xcodebuild \ | |
| -project "${{ env.PROJECT }}" \ | |
| -scheme "${{ env.SCHEME }}" \ | |
| -configuration Release \ | |
| -destination "generic/platform=iOS" \ | |
| -archivePath "${{ github.workspace }}/build/MTRX.xcarchive" \ | |
| -allowProvisioningUpdates \ | |
| DEVELOPMENT_TEAM="${{ secrets.APPLE_TEAM_ID }}" \ | |
| archive | xcpretty | |
| - name: Export IPA | |
| if: env.SKIP_ARCHIVE != '1' | |
| run: | | |
| cat > ExportOptions.plist <<'EOF' | |
| <?xml version="1.0" encoding="UTF-8"?> | |
| <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | |
| <plist version="1.0"> | |
| <dict> | |
| <key>method</key><string>app-store</string> | |
| <key>uploadBitcode</key><false/> | |
| <key>uploadSymbols</key><true/> | |
| <key>signingStyle</key><string>automatic</string> | |
| </dict> | |
| </plist> | |
| EOF | |
| xcodebuild \ | |
| -exportArchive \ | |
| -archivePath "${{ github.workspace }}/build/MTRX.xcarchive" \ | |
| -exportPath "${{ github.workspace }}/build/ipa" \ | |
| -exportOptionsPlist ExportOptions.plist \ | |
| -allowProvisioningUpdates | |
| - name: Upload to TestFlight | |
| if: env.SKIP_ARCHIVE != '1' | |
| env: | |
| API_KEY: ${{ secrets.APP_STORE_CONNECT_API_KEY }} | |
| API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }} | |
| API_ISSUER: ${{ secrets.APP_STORE_CONNECT_API_ISSUER }} | |
| run: | | |
| mkdir -p ~/.appstoreconnect/private_keys | |
| echo "$API_KEY" | base64 --decode > ~/.appstoreconnect/private_keys/AuthKey_${API_KEY_ID}.p8 | |
| xcrun altool --upload-app \ | |
| --type ios \ | |
| --file "${{ github.workspace }}/build/ipa/MTRX.ipa" \ | |
| --apiKey "$API_KEY_ID" \ | |
| --apiIssuer "$API_ISSUER" |