ci(ios): find IPA dynamically and fix API key JSON generation #9
Workflow file for this run
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 Release | |
| on: | |
| push: | |
| tags: | |
| - 'v*' | |
| concurrency: | |
| group: ios-release-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| release: | |
| name: Build & Upload to TestFlight | |
| runs-on: nscloud-macos-sequoia-arm64-6x14 | |
| timeout-minutes: 60 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| submodules: recursive | |
| - name: Select latest Xcode | |
| run: | | |
| LATEST=$(ls -d /Applications/Xcode_*.app 2>/dev/null | sort -V | tail -1) | |
| if [ -n "$LATEST" ]; then | |
| sudo xcode-select -s "$LATEST" | |
| fi | |
| xcodebuild -version | |
| - uses: actions/setup-java@v4 | |
| with: | |
| distribution: temurin | |
| java-version: 23 | |
| - uses: gradle/actions/setup-gradle@v4 | |
| - name: Import signing certificate | |
| env: | |
| APPLE_CERTIFICATE_BASE64: ${{ secrets.APPLE_CERTIFICATE_BASE64 }} | |
| APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} | |
| run: | | |
| CERTIFICATE_PATH="$RUNNER_TEMP/certificate.p12" | |
| KEYCHAIN_PATH="$RUNNER_TEMP/signing.keychain-db" | |
| KEYCHAIN_PASSWORD="$(openssl rand -base64 32)" | |
| echo -n "$APPLE_CERTIFICATE_BASE64" | base64 --decode -o "$CERTIFICATE_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 "$CERTIFICATE_PATH" \ | |
| -P "$APPLE_CERTIFICATE_PASSWORD" \ | |
| -A \ | |
| -t cert \ | |
| -f pkcs12 \ | |
| -k "$KEYCHAIN_PATH" | |
| security set-key-partition-list \ | |
| -S apple-tool:,apple: \ | |
| -k "$KEYCHAIN_PASSWORD" \ | |
| "$KEYCHAIN_PATH" | |
| security list-keychains -d user -s "$KEYCHAIN_PATH" login.keychain-db | |
| - name: Import provisioning profile | |
| env: | |
| APPLE_PROVISIONING_PROFILE_BASE64: ${{ secrets.APPLE_PROVISIONING_PROFILE_BASE64 }} | |
| run: | | |
| PROFILE_PATH="$RUNNER_TEMP/profile.mobileprovision" | |
| echo -n "$APPLE_PROVISIONING_PROFILE_BASE64" | base64 --decode -o "$PROFILE_PATH" | |
| mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles | |
| PROFILE_UUID=$(/usr/libexec/PlistBuddy -c "Print UUID" /dev/stdin <<< \ | |
| "$(security cms -D -i "$PROFILE_PATH")") | |
| cp "$PROFILE_PATH" ~/Library/MobileDevice/Provisioning\ Profiles/"$PROFILE_UUID".mobileprovision | |
| echo "PROVISIONING_PROFILE_UUID=$PROFILE_UUID" >> "$GITHUB_ENV" | |
| - name: Set up App Store Connect API key | |
| env: | |
| APP_STORE_CONNECT_API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }} | |
| APP_STORE_CONNECT_API_KEY_BASE64: ${{ secrets.APP_STORE_CONNECT_API_KEY_BASE64 }} | |
| run: | | |
| mkdir -p ~/.private_keys | |
| echo -n "$APP_STORE_CONNECT_API_KEY_BASE64" | base64 --decode \ | |
| -o ~/.private_keys/AuthKey_"$APP_STORE_CONNECT_API_KEY_ID".p8 | |
| - name: Extract version from tag | |
| run: | | |
| TAG="${GITHUB_REF#refs/tags/v}" | |
| echo "MARKETING_VERSION=$TAG" >> "$GITHUB_ENV" | |
| echo "CURRENT_PROJECT_VERSION=$GITHUB_RUN_NUMBER" >> "$GITHUB_ENV" | |
| echo "Building version $TAG ($GITHUB_RUN_NUMBER)" | |
| - name: Build archive | |
| run: | | |
| xcodebuild archive \ | |
| -project app/ios/FareBot.xcodeproj \ | |
| -scheme FareBot \ | |
| -configuration Release \ | |
| -destination 'generic/platform=iOS' \ | |
| -archivePath "$RUNNER_TEMP/FareBot.xcarchive" \ | |
| MARKETING_VERSION="$MARKETING_VERSION" \ | |
| CURRENT_PROJECT_VERSION="$CURRENT_PROJECT_VERSION" \ | |
| CODE_SIGN_STYLE=Manual \ | |
| CODE_SIGN_IDENTITY="Apple Distribution" \ | |
| DEVELOPMENT_TEAM=ZJ9GEQ36AH \ | |
| PROVISIONING_PROFILE_SPECIFIER="$PROVISIONING_PROFILE_UUID" | |
| - name: Export IPA | |
| env: | |
| APP_STORE_CONNECT_API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }} | |
| APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }} | |
| run: | | |
| EXPORT_PLIST="$RUNNER_TEMP/ExportOptions.plist" | |
| cat > "$EXPORT_PLIST" <<-PLIST | |
| <?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-connect</string> | |
| <key>teamID</key> | |
| <string>ZJ9GEQ36AH</string> | |
| <key>signingStyle</key> | |
| <string>manual</string> | |
| <key>signingCertificate</key> | |
| <string>Apple Distribution</string> | |
| <key>provisioningProfiles</key> | |
| <dict> | |
| <key>com.codebutler.farebot</key> | |
| <string>$PROVISIONING_PROFILE_UUID</string> | |
| </dict> | |
| <key>uploadSymbols</key> | |
| <true/> | |
| <key>destination</key> | |
| <string>upload</string> | |
| </dict> | |
| </plist> | |
| PLIST | |
| xcodebuild -exportArchive \ | |
| -archivePath "$RUNNER_TEMP/FareBot.xcarchive" \ | |
| -exportPath "$RUNNER_TEMP/export" \ | |
| -exportOptionsPlist "$EXPORT_PLIST" \ | |
| -authenticationKeyPath ~/.private_keys/AuthKey_"$APP_STORE_CONNECT_API_KEY_ID".p8 \ | |
| -authenticationKeyID "$APP_STORE_CONNECT_API_KEY_ID" \ | |
| -authenticationKeyIssuerID "$APP_STORE_CONNECT_ISSUER_ID" | |
| - name: Install fastlane | |
| run: brew install fastlane | |
| - name: Upload to TestFlight & distribute to internal testers | |
| env: | |
| APP_STORE_CONNECT_API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }} | |
| APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }} | |
| run: | | |
| # Build the API key JSON that fastlane expects | |
| API_KEY_JSON="$RUNNER_TEMP/api_key.json" | |
| P8_CONTENTS=$(cat ~/.private_keys/AuthKey_"$APP_STORE_CONNECT_API_KEY_ID".p8) | |
| jq -n \ | |
| --arg key_id "$APP_STORE_CONNECT_API_KEY_ID" \ | |
| --arg issuer_id "$APP_STORE_CONNECT_ISSUER_ID" \ | |
| --arg key "$P8_CONTENTS" \ | |
| '{key_id: $key_id, issuer_id: $issuer_id, key: $key, in_house: false}' \ | |
| > "$API_KEY_JSON" | |
| # Find the IPA (xcodebuild may name it differently) | |
| IPA_PATH=$(find "$RUNNER_TEMP/export" -name '*.ipa' -print -quit) | |
| echo "IPA: $IPA_PATH" | |
| [ -f "$IPA_PATH" ] || { echo "No IPA found in $RUNNER_TEMP/export"; ls -la "$RUNNER_TEMP/export"; exit 1; } | |
| fastlane pilot upload \ | |
| --ipa "$IPA_PATH" \ | |
| --api_key_path "$API_KEY_JSON" \ | |
| --distribute_external false \ | |
| --skip_waiting_for_build_processing false \ | |
| --changelog "Build $MARKETING_VERSION ($CURRENT_PROJECT_VERSION)" | |
| - name: Cleanup keychain | |
| if: always() | |
| run: | | |
| KEYCHAIN_PATH="$RUNNER_TEMP/signing.keychain-db" | |
| if [ -f "$KEYCHAIN_PATH" ]; then | |
| security delete-keychain "$KEYCHAIN_PATH" | |
| fi |