Deploy iOS to TestFlight #8
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: Deploy iOS to TestFlight | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: "Version number (e.g. 1.7.0)" | |
| required: true | |
| type: string | |
| jobs: | |
| build-and-deploy: | |
| name: Build & Upload to TestFlight | |
| runs-on: macos-latest | |
| timeout-minutes: 45 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Set up Flutter | |
| uses: subosito/flutter-action@v2 | |
| with: | |
| channel: stable | |
| - name: Set up Xcode | |
| uses: maxim-lobanov/setup-xcode@v1 | |
| with: | |
| xcode-version: latest-stable | |
| - name: Create .env file | |
| env: | |
| GOOGLE_IOS_CLIENT_ID: ${{ secrets.GOOGLE_IOS_CLIENT_ID }} | |
| DEEPGRAM_API_KEY: ${{ secrets.DEEPGRAM_API_KEY }} | |
| run: | | |
| echo "GOOGLE_IOS_CLIENT_ID=$GOOGLE_IOS_CLIENT_ID" > .env | |
| echo "DEEPGRAM_API_KEY=$DEEPGRAM_API_KEY" >> .env | |
| - name: Install Flutter dependencies | |
| run: flutter pub get | |
| - name: Install CocoaPods dependencies | |
| working-directory: ios | |
| run: pod install | |
| - name: Import signing certificate | |
| env: | |
| MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }} | |
| MACOS_CERTIFICATE_PWD: ${{ secrets.MACOS_CERTIFICATE_PWD }} | |
| KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} | |
| run: | | |
| # Decode certificate | |
| CERTIFICATE_PATH=$RUNNER_TEMP/certificate.p12 | |
| echo -n "$MACOS_CERTIFICATE" | base64 --decode -o "$CERTIFICATE_PATH" | |
| # Create temporary keychain | |
| KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db | |
| security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" | |
| security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH" | |
| security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" | |
| # Import certificate into keychain | |
| security import "$CERTIFICATE_PATH" \ | |
| -P "$MACOS_CERTIFICATE_PWD" \ | |
| -A \ | |
| -t cert \ | |
| -f pkcs12 \ | |
| -k "$KEYCHAIN_PATH" | |
| # Allow codesign to access the keychain | |
| security set-key-partition-list -S apple-tool:,apple:,codesign: \ | |
| -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" | |
| # Add keychain to search list | |
| security list-keychains -d user -s "$KEYCHAIN_PATH" $(security list-keychains -d user | tr -d '"') | |
| # Extract signing identity name from imported certificate | |
| SIGNING_IDENTITY=$(security find-identity -v -p codesigning "$KEYCHAIN_PATH" | head -1 | sed 's/.*"\(.*\)"/\1/') | |
| echo "::add-mask::$SIGNING_IDENTITY" | |
| echo "SIGNING_IDENTITY=$SIGNING_IDENTITY" >> $GITHUB_ENV | |
| - name: Install provisioning profile | |
| env: | |
| IOS_PROVISIONING_PROFILE_BASE64: ${{ secrets.IOS_PROVISIONING_PROFILE_BASE64 }} | |
| run: | | |
| # Decode provisioning profile | |
| PROFILE_PATH=$RUNNER_TEMP/profile.mobileprovision | |
| echo -n "$IOS_PROVISIONING_PROFILE_BASE64" | base64 --decode -o "$PROFILE_PATH" | |
| # Extract UUID from provisioning profile | |
| PROFILE_UUID=$(/usr/libexec/PlistBuddy -c "Print :UUID" /dev/stdin <<< \ | |
| $(security cms -D -i "$PROFILE_PATH")) | |
| echo "PROFILE_UUID=$PROFILE_UUID" >> $GITHUB_ENV | |
| # Install provisioning profile | |
| mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles | |
| cp "$PROFILE_PATH" ~/Library/MobileDevice/Provisioning\ Profiles/"$PROFILE_UUID".mobileprovision | |
| - name: Set build number | |
| run: | | |
| BUILD_NUMBER=$(date +%Y%m%d%H%M) | |
| echo "BUILD_NUMBER=$BUILD_NUMBER" >> $GITHUB_ENV | |
| echo "Version: ${{ inputs.version }}+$BUILD_NUMBER" | |
| - name: Build Flutter iOS (no codesign) | |
| run: | | |
| flutter build ios \ | |
| --release \ | |
| --no-codesign \ | |
| --build-name=${{ inputs.version }} \ | |
| --build-number=$BUILD_NUMBER | |
| - name: Archive with Xcode | |
| env: | |
| APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} | |
| run: | | |
| set -euo pipefail | |
| xcodebuild archive \ | |
| -workspace ios/Runner.xcworkspace \ | |
| -scheme Runner \ | |
| -configuration Release \ | |
| -archivePath "$RUNNER_TEMP/Runner.xcarchive" \ | |
| -destination "generic/platform=iOS" \ | |
| CODE_SIGN_STYLE=Manual \ | |
| CODE_SIGN_IDENTITY="$SIGNING_IDENTITY" \ | |
| PROVISIONING_PROFILE_SPECIFIER="" \ | |
| PROVISIONING_PROFILE="$PROFILE_UUID" \ | |
| DEVELOPMENT_TEAM="$APPLE_TEAM_ID" | |
| - name: Generate ExportOptions.plist | |
| env: | |
| APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} | |
| run: | | |
| cat > $RUNNER_TEMP/ExportOptions.plist <<PLISTEOF | |
| <?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>teamID</key> | |
| <string>$APPLE_TEAM_ID</string> | |
| <key>uploadSymbols</key> | |
| <true/> | |
| <key>provisioningProfiles</key> | |
| <dict> | |
| <key>com.kajabi.noa</key> | |
| <string>$PROFILE_UUID</string> | |
| </dict> | |
| </dict> | |
| </plist> | |
| PLISTEOF | |
| - name: Export IPA | |
| run: | | |
| xcodebuild -exportArchive \ | |
| -archivePath "$RUNNER_TEMP/Runner.xcarchive" \ | |
| -exportOptionsPlist "$RUNNER_TEMP/ExportOptions.plist" \ | |
| -exportPath "$RUNNER_TEMP/export" | |
| - name: Upload to TestFlight | |
| env: | |
| APPLE_ID: ${{ secrets.APPLE_ID }} | |
| APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }} | |
| run: | | |
| # Find the IPA | |
| IPA_FILE=$(find $RUNNER_TEMP/export -name "*.ipa" -print -quit) | |
| xcrun altool --upload-app \ | |
| --type ios \ | |
| --file "$IPA_FILE" \ | |
| -u "$APPLE_ID" \ | |
| -p "$APPLE_ID_PASSWORD" | |
| - name: Create GitHub Release | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| # Find the IPA file (name may vary) | |
| IPA_FILE=$(find $RUNNER_TEMP/export -name "*.ipa" -print -quit) | |
| gh release create "v${{ inputs.version }}" \ | |
| "$IPA_FILE#noa-${{ inputs.version }}.ipa" \ | |
| --title "v${{ inputs.version }}" \ | |
| --notes "Release v${{ inputs.version }} - deployed to TestFlight" \ | |
| --generate-notes | |
| - name: Cleanup | |
| if: always() | |
| run: | | |
| # Delete temporary keychain | |
| KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db | |
| if [ -f "$KEYCHAIN_PATH" ]; then | |
| security delete-keychain "$KEYCHAIN_PATH" 2>/dev/null || true | |
| fi | |
| # Remove provisioning profile | |
| if [ -n "$PROFILE_UUID" ]; then | |
| rm -f ~/Library/MobileDevice/Provisioning\ Profiles/"$PROFILE_UUID".mobileprovision 2>/dev/null || true | |
| fi | |
| # Remove certificate | |
| rm -f $RUNNER_TEMP/certificate.p12 2>/dev/null || true | |
| # Remove .env | |
| rm -f .env 2>/dev/null || true |