Native iOS TestFlight #10
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: Native iOS TestFlight | |
| 'on': | |
| push: | |
| tags: | |
| - 'ios-v*' | |
| workflow_dispatch: | |
| inputs: | |
| git_ref: | |
| description: "Git ref to build (branch, tag, or SHA)" | |
| required: false | |
| default: "" | |
| upload_to_testflight: | |
| description: "Upload exported IPA to TestFlight" | |
| required: true | |
| type: boolean | |
| default: true | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: native-ios-testflight-${{ github.ref }} | |
| cancel-in-progress: false | |
| jobs: | |
| build-and-upload: | |
| name: Build signed IPA | |
| runs-on: macos-15 | |
| env: | |
| APPLE_TEAM_ID: MM5YXC7T6E | |
| BUNDLE_ID: com.shinycomputers.everycodecompanion | |
| SCHEME: CodeNativeiOSDemo | |
| XCODE_PROJECT: native/CodeNativeiOS/CodeNativeiOSDemo.xcodeproj | |
| ARCHIVE_PATH: /tmp/ecc-ios/EveryCodeCompanion.xcarchive | |
| EXPORT_PATH: /tmp/ecc-ios/export | |
| EXPORT_OPTIONS_PATH: /tmp/ecc-ios/ExportOptions.plist | |
| PROFILE_PATH: /tmp/ecc-ios/every-code-companion.mobileprovision | |
| steps: | |
| - name: Resolve checkout ref | |
| id: resolve_ref | |
| run: | | |
| set -euo pipefail | |
| ref="$GITHUB_REF" | |
| if [ "${{ github.event_name }}" = "workflow_dispatch" ] \ | |
| && [ -n "${{ github.event.inputs.git_ref }}" ]; then | |
| ref="${{ github.event.inputs.git_ref }}" | |
| fi | |
| echo "value=$ref" >> "$GITHUB_OUTPUT" | |
| - name: Prepare build directories | |
| run: | | |
| set -euo pipefail | |
| mkdir -p "$(dirname "$ARCHIVE_PATH")" | |
| mkdir -p "$EXPORT_PATH" | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| ref: ${{ steps.resolve_ref.outputs.value }} | |
| - name: Select Xcode | |
| uses: maxim-lobanov/setup-xcode@v1 | |
| with: | |
| xcode-version: latest-stable | |
| - name: Validate required secrets | |
| env: | |
| IOS_DIST_CERT_P12_BASE64: ${{ secrets.IOS_DIST_CERT_P12_BASE64 }} | |
| IOS_DIST_CERT_PASSWORD: ${{ secrets.IOS_DIST_CERT_PASSWORD }} | |
| IOS_APPSTORE_PROFILE_BASE64: | |
| ${{ secrets.IOS_APPSTORE_PROFILE_BASE64 }} | |
| IOS_APPSTORE_PROFILE_NAME: ${{ secrets.IOS_APPSTORE_PROFILE_NAME }} | |
| APP_STORE_CONNECT_KEY_ID: ${{ secrets.APP_STORE_CONNECT_KEY_ID }} | |
| APP_STORE_CONNECT_ISSUER_ID: | |
| ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }} | |
| APP_STORE_CONNECT_PRIVATE_KEY: | |
| ${{ secrets.APP_STORE_CONNECT_PRIVATE_KEY }} | |
| run: | | |
| set -euo pipefail | |
| missing=() | |
| for name in \ | |
| IOS_DIST_CERT_P12_BASE64 \ | |
| IOS_DIST_CERT_PASSWORD \ | |
| IOS_APPSTORE_PROFILE_BASE64 \ | |
| IOS_APPSTORE_PROFILE_NAME \ | |
| APP_STORE_CONNECT_KEY_ID \ | |
| APP_STORE_CONNECT_ISSUER_ID \ | |
| APP_STORE_CONNECT_PRIVATE_KEY; do | |
| if [ -z "${!name:-}" ]; then | |
| missing+=("$name") | |
| fi | |
| done | |
| if [ "${#missing[@]}" -gt 0 ]; then | |
| printf 'Missing required secrets:\n' >&2 | |
| printf ' - %s\n' "${missing[@]}" >&2 | |
| exit 1 | |
| fi | |
| - name: Import Apple Distribution certificate | |
| uses: apple-actions/import-codesign-certs@v3 | |
| with: | |
| p12-file-base64: ${{ secrets.IOS_DIST_CERT_P12_BASE64 }} | |
| p12-password: ${{ secrets.IOS_DIST_CERT_PASSWORD }} | |
| - name: Install App Store provisioning profile | |
| env: | |
| IOS_APPSTORE_PROFILE_BASE64: | |
| ${{ secrets.IOS_APPSTORE_PROFILE_BASE64 }} | |
| run: | | |
| set -euo pipefail | |
| mkdir -p "$HOME/Library/MobileDevice/Provisioning Profiles" | |
| echo "$IOS_APPSTORE_PROFILE_BASE64" | base64 --decode \ | |
| > "$PROFILE_PATH" | |
| profile_plist=$(security cms -D -i "$PROFILE_PATH") | |
| profile_uuid=$( | |
| /usr/libexec/PlistBuddy -c 'Print UUID' /dev/stdin \ | |
| <<<"$profile_plist" | |
| ) | |
| profiles_dir="$HOME/Library/MobileDevice/Provisioning Profiles" | |
| cp "$PROFILE_PATH" "$profiles_dir/$profile_uuid.mobileprovision" | |
| - name: Write export options | |
| env: | |
| IOS_APPSTORE_PROFILE_NAME: ${{ secrets.IOS_APPSTORE_PROFILE_NAME }} | |
| run: | | |
| set -euo pipefail | |
| cat > "$EXPORT_OPTIONS_PATH" <<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>destination</key> | |
| <string>export</string> | |
| <key>method</key> | |
| <string>app-store</string> | |
| <key>signingStyle</key> | |
| <string>manual</string> | |
| <key>stripSwiftSymbols</key> | |
| <true/> | |
| <key>teamID</key> | |
| <string>${APPLE_TEAM_ID}</string> | |
| <key>provisioningProfiles</key> | |
| <dict> | |
| <key>${BUNDLE_ID}</key> | |
| <string>${IOS_APPSTORE_PROFILE_NAME}</string> | |
| </dict> | |
| </dict> | |
| </plist> | |
| EOF | |
| - name: Archive iOS app | |
| env: | |
| IOS_APPSTORE_PROFILE_NAME: ${{ secrets.IOS_APPSTORE_PROFILE_NAME }} | |
| run: | | |
| set -euo pipefail | |
| xcodebuild \ | |
| -project "$XCODE_PROJECT" \ | |
| -scheme "$SCHEME" \ | |
| -configuration Release \ | |
| -destination "generic/platform=iOS" \ | |
| -archivePath "$ARCHIVE_PATH" \ | |
| DEVELOPMENT_TEAM="$APPLE_TEAM_ID" \ | |
| PRODUCT_BUNDLE_IDENTIFIER="$BUNDLE_ID" \ | |
| CODE_SIGN_STYLE=Manual \ | |
| CODE_SIGN_IDENTITY="Apple Distribution" \ | |
| PROVISIONING_PROFILE_SPECIFIER="$IOS_APPSTORE_PROFILE_NAME" \ | |
| clean archive | |
| - name: Export IPA | |
| run: | | |
| set -euo pipefail | |
| xcodebuild \ | |
| -exportArchive \ | |
| -archivePath "$ARCHIVE_PATH" \ | |
| -exportPath "$EXPORT_PATH" \ | |
| -exportOptionsPlist "$EXPORT_OPTIONS_PATH" | |
| ipa_path=$(find "$EXPORT_PATH" -maxdepth 1 -name '*.ipa' -print -quit) | |
| if [ -z "$ipa_path" ]; then | |
| echo "No IPA produced by export" >&2 | |
| exit 1 | |
| fi | |
| mv "$ipa_path" "$EXPORT_PATH/EveryCodeCompanion.ipa" | |
| - name: Upload IPA artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: EveryCodeCompanion-ipa | |
| path: | | |
| ${{ env.EXPORT_PATH }}/EveryCodeCompanion.ipa | |
| ${{ env.ARCHIVE_PATH }}/dSYMs | |
| if-no-files-found: error | |
| - name: Upload to TestFlight | |
| if: >- | |
| ${{ github.event_name == 'push' | |
| || github.event.inputs.upload_to_testflight == 'true' }} | |
| uses: apple-actions/upload-testflight-build@v3 | |
| with: | |
| app-path: ${{ env.EXPORT_PATH }}/EveryCodeCompanion.ipa | |
| issuer-id: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }} | |
| api-key-id: ${{ secrets.APP_STORE_CONNECT_KEY_ID }} | |
| api-private-key: ${{ secrets.APP_STORE_CONNECT_PRIVATE_KEY }} |