Release Workflow #165
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: Release Workflow | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| version_type: | |
| description: "Type of version bump (major, minor, patch, premajor, preminor, prepatch, prerelease)" | |
| required: true | |
| default: "patch" | |
| type: choice | |
| options: | |
| - major | |
| - minor | |
| - patch | |
| - premajor | |
| - preminor | |
| - prepatch | |
| - prerelease | |
| ios_only: | |
| description: "iOS only: skip version bump + Android, build TestFlight from current ref" | |
| required: false | |
| default: false | |
| type: boolean | |
| jobs: | |
| # Job 1: Version Bumping and Android Build | |
| build_android: | |
| if: ${{ !inputs.ios_only }} | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: 'write' # Allows workflow to checkout repository code | |
| id-token: 'write' # Required for Google Cloud Workload Identity Federation authentication (OIDC token generation) | |
| steps: | |
| # Step 1: Checkout the code | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 # Important for git history | |
| ssh-key: ${{ secrets.DEPLOY_KEY }} | |
| # Step 2: Set up JDK 17 | |
| - name: Set up JDK 17 | |
| uses: actions/setup-java@v3 | |
| with: | |
| distribution: 'temurin' | |
| java-version: '17' | |
| # Step 3: Set up Node.js | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v3 | |
| with: | |
| node-version: '22.21.0' # Use the specified Node.js version | |
| cache: 'yarn' | |
| # Step 4: Install dependencies using Yarn | |
| - name: Install dependencies | |
| run: yarn install | |
| # Step 5: Set up Ruby and Bundler | |
| - name: Set up Ruby | |
| uses: ruby/setup-ruby@v1 | |
| with: | |
| ruby-version: '3.2.3' # Specify a Ruby version | |
| bundler-cache: true | |
| # Step 6: Combined version bump | |
| - name: Bump versions | |
| working-directory: ${{ github.workspace }} | |
| run: bundle exec fastlane bump_version version_type:${{ github.event_name == 'workflow_dispatch' && github.event.inputs.version_type || 'patch' }} | |
| # Step 7: Commit and push version changes | |
| - name: Commit and push version changes | |
| run: | | |
| VERSION=$(cat .version) | |
| echo "VERSION=$VERSION" >> $GITHUB_ENV | |
| git config user.name "GitHub Actions" | |
| git config user.email "actions@github.com" | |
| git add .version package.json android/app/build.gradle ios/PocketPal.xcodeproj/project.pbxproj | |
| git commit -m "chore(release): bump version to $VERSION" | |
| git push | |
| git tag "v$VERSION" | |
| git push origin "v$VERSION" | |
| # Step 8: Set up Android Keystore | |
| - name: Set up Android Keystore | |
| working-directory: android | |
| run: | | |
| echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | base64 --decode > app/pocketpal-release-key.keystore | |
| # Step 9: Authenticate to Google Cloud | |
| - name: Authenticate to Google Cloud | |
| id: auth | |
| uses: google-github-actions/auth@v2 | |
| with: | |
| service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }} | |
| workload_identity_provider: ${{ secrets.WORKLOAD_IDENTITY_PROVIDER }} | |
| create_credentials_file: true | |
| # Add this step before the Android build | |
| - name: Create .env file | |
| run: | | |
| cat > .env << 'EOL' | |
| FIREBASE_FUNCTIONS_URL=${{ vars.FIREBASE_FUNCTIONS_URL }} | |
| # PalsHub/Supabase Configuration | |
| SUPABASE_URL=${{ vars.SUPABASE_URL }} | |
| SUPABASE_ANON_KEY=${{ secrets.SUPABASE_ANON_KEY }} | |
| PALSHUB_API_BASE_URL=${{ vars.PALSHUB_API_BASE_URL }} | |
| # App Configuration | |
| APP_URL=pocketpal://app | |
| # Feature Flags (true/false) | |
| ENABLE_PALSHUB_INTEGRATION=true | |
| ENABLE_AUTHENTICATION=true | |
| ENABLE_OFFLINE_MODE=true | |
| # Google Sign-In Configuration | |
| GOOGLE_IOS_CLIENT_ID=${{ vars.GOOGLE_IOS_CLIENT_ID }} | |
| GOOGLE_WEB_CLIENT_ID=${{ vars.GOOGLE_WEB_CLIENT_ID }} | |
| EOL | |
| # Step 10: Build and upload Android app to Alpha track (includes building APK and Bundle) | |
| - name: Build and upload Android app | |
| working-directory: android | |
| env: | |
| KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} | |
| KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} | |
| KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} | |
| GRADLE_USER_HOME: ${{ runner.temp }}/.gradle | |
| # GOOGLE_APPLICATION_CREDENTIALS: ${{ steps.auth.outputs.credentials_file_path }} # This is not supported by fastlane, we need to replace it with PLAY_STORE_JSON_KEY in the future. | |
| PLAY_STORE_JSON_KEY: ${{ secrets.PLAY_STORE_SERVICE_ACCOUNT_JSON }} | |
| GOOGLE_SERVICES_JSON: ${{ secrets.GOOGLE_SERVICES_JSON }} | |
| run: | | |
| echo "$PLAY_STORE_JSON_KEY" > play-store-key.json | |
| bundle exec fastlane release_android_alpha | |
| # Step 11: Create GitHub Release with APK | |
| - name: Create GitHub Release | |
| uses: softprops/action-gh-release@v1 | |
| with: | |
| files: android/app/build/outputs/apk/prod/release/app-prod-release.apk | |
| tag_name: "v${{ env.VERSION }}" | |
| name: "Release v${{ env.VERSION }}" | |
| draft: false | |
| prerelease: false | |
| generate_release_notes: true | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # Job 2: iOS Build and Upload (runs on macOS) | |
| build_ios: | |
| runs-on: macos-26 # macOS 26 includes an iOS 26-capable Xcode toolchain | |
| needs: build_android | |
| if: ${{ always() && (needs.build_android.result == 'success' || needs.build_android.result == 'skipped') }} | |
| env: | |
| NODE_OPTIONS: '--max-old-space-size=8192' | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ github.ref }} # This ensures we get the latest changes including the version bump | |
| fetch-depth: 0 | |
| # Step 1: Select the default Xcode shipped on macOS 26. | |
| # App Store Connect now requires uploads built with the iOS 26 SDK or later. | |
| - name: Setup Xcode | |
| run: | | |
| sudo xcode-select -s /Applications/Xcode.app | |
| xcodebuild -version | |
| # Step 2: Set up Node.js | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v3 | |
| with: | |
| node-version: '22.21.0' | |
| cache: 'yarn' | |
| # Step 3: Install dependencies using Yarn | |
| - name: Install dependencies | |
| run: yarn install | |
| # Step 4: Set up Ruby and Bundler | |
| - name: Set up Ruby | |
| uses: ruby/setup-ruby@v1 | |
| with: | |
| ruby-version: '3.2.3' # Specify a Ruby version | |
| bundler-cache: true | |
| # Step 5: Install CocoaPods dependencies | |
| - name: Install CocoaPods dependencies | |
| working-directory: ios | |
| run: pod install | |
| # Step 6: Create Env.xcconfig | |
| - name: Create Env.xcconfig | |
| run: | | |
| cat > ios/Config/Env.xcconfig << 'EOL' | |
| // This file contains environment-specific build settings. | |
| GOOGLE_WEB_CLIENT_ID = ${{ vars.GOOGLE_WEB_CLIENT_ID }} | |
| REVERSED_GOOGLE_IOS_CLIENT_ID = ${{ vars.REVERSED_GOOGLE_IOS_CLIENT_ID }} | |
| EOL | |
| # Step 7: Create .env file | |
| - name: Create .env file | |
| run: | | |
| cat > .env << 'EOL' | |
| FIREBASE_FUNCTIONS_URL=${{ vars.FIREBASE_FUNCTIONS_URL }} | |
| # PalsHub/Supabase Configuration | |
| SUPABASE_URL=${{ vars.SUPABASE_URL }} | |
| SUPABASE_ANON_KEY=${{ secrets.SUPABASE_ANON_KEY }} | |
| PALSHUB_API_BASE_URL=${{ vars.PALSHUB_API_BASE_URL }} | |
| # App Configuration | |
| APP_URL=pocketpal://app | |
| # Feature Flags (true/false) | |
| ENABLE_PALSHUB_INTEGRATION=true | |
| ENABLE_AUTHENTICATION=true | |
| ENABLE_OFFLINE_MODE=true | |
| # Google Sign-In Configuration | |
| GOOGLE_IOS_CLIENT_ID=${{ vars.GOOGLE_IOS_CLIENT_ID }} | |
| GOOGLE_WEB_CLIENT_ID=${{ vars.GOOGLE_WEB_CLIENT_ID }} | |
| EOL | |
| # Step 8: Build and upload iOS app to TestFlight | |
| - name: Build and upload iOS app | |
| working-directory: ios | |
| env: | |
| MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} | |
| MATCH_GIT_URL: ${{ secrets.MATCH_GIT_URL }} | |
| MATCH_GITHUB_TOKEN: ${{ secrets.MATCH_GITHUB_TOKEN }} | |
| APP_STORE_CONNECT_API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }} | |
| APP_STORE_CONNECT_API_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_ISSUER_ID }} | |
| APP_STORE_CONNECT_API_KEY_CONTENT: ${{ secrets.APP_STORE_CONNECT_API_KEY_CONTENT }} | |
| APP_STORE_CONNECT_USER_ID: ${{ secrets.APP_STORE_CONNECT_USER_ID }} | |
| GOOGLE_SERVICES_PLIST: ${{ secrets.GOOGLE_SERVICES_PLIST }} | |
| run: bundle exec fastlane release_ios_testflight | |
| # Step 9: Extract iOS version info for dSYM artifact naming | |
| - name: Extract iOS version info | |
| id: version | |
| if: always() | |
| run: | | |
| MARKETING_VERSION=$(cat .version | tr -d '[:space:]') | |
| BUILD_NUMBER=$(grep -m1 'CURRENT_PROJECT_VERSION' ios/PocketPal.xcodeproj/project.pbxproj | awk -F'= ' '{print $2}' | tr -d ' ;') | |
| echo "marketing_version=${MARKETING_VERSION}" >> "$GITHUB_OUTPUT" | |
| echo "build_number=${BUILD_NUMBER}" >> "$GITHUB_OUTPUT" | |
| echo "Extracted: v${MARKETING_VERSION} build ${BUILD_NUMBER}" | |
| # Step 10: Retain dSYM as a workflow artifact for crash symbolication (FOU-86) | |
| - name: Upload iOS dSYM | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: ios-dsym-v${{ steps.version.outputs.marketing_version }}-build${{ steps.version.outputs.build_number }} | |
| path: ios/build/PocketPal.app.dSYM.zip | |
| retention-days: 90 | |
| if-no-files-found: warn |