From fb187c34d0367144dafdc9ab6674a254e87282a5 Mon Sep 17 00:00:00 2001 From: Gray Zhang Date: Tue, 9 Sep 2025 14:33:42 +0800 Subject: [PATCH 1/2] feat: add Google Play Store automated deployment pipeline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Enhanced release workflow with Play Store upload support - Added Fastlane configuration for flexible deployment options - Support for multiple release tracks (internal, alpha, beta, production) - Configurable release status (draft, completed) - Debug symbols upload support - Comprehensive deployment documentation - Security best practices with service account authentication 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .github/workflows/fastlane-deploy.yml | 106 +++++++++ .github/workflows/release.yml | 142 ++++++++++-- .gitignore | 16 +- .gitignore.fastlane | 26 +++ Gemfile | 4 + docs/PLAY_STORE_DEPLOYMENT.md | 299 ++++++++++++++++++++++++++ fastlane/Appfile | 8 + fastlane/Fastfile | 174 +++++++++++++++ 8 files changed, 754 insertions(+), 21 deletions(-) create mode 100644 .github/workflows/fastlane-deploy.yml create mode 100644 .gitignore.fastlane create mode 100644 Gemfile create mode 100644 docs/PLAY_STORE_DEPLOYMENT.md create mode 100644 fastlane/Appfile create mode 100644 fastlane/Fastfile diff --git a/.github/workflows/fastlane-deploy.yml b/.github/workflows/fastlane-deploy.yml new file mode 100644 index 00000000..41b78508 --- /dev/null +++ b/.github/workflows/fastlane-deploy.yml @@ -0,0 +1,106 @@ +name: Fastlane Deploy + +on: + workflow_dispatch: + inputs: + track: + description: 'Deployment track' + required: true + type: choice + default: 'internal' + options: + - internal + - alpha + - beta + - production + release_status: + description: 'Release status' + required: false + type: choice + default: 'draft' + options: + - draft + - completed + +jobs: + deploy: + name: Deploy with Fastlane + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + cache: gradle + + - name: Setup Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.2' + bundler-cache: true + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Decode Keystore + env: + KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }} + run: | + echo "$KEYSTORE_BASE64" | base64 --decode > ${{ github.workspace }}/keystore.jks + echo "KEYSTORE_PATH=${{ github.workspace }}/keystore.jks" >> $GITHUB_ENV + + - name: Setup Play Store credentials + env: + PLAY_STORE_SERVICE_ACCOUNT_JSON: ${{ secrets.PLAY_STORE_SERVICE_ACCOUNT_JSON }} + run: | + echo "$PLAY_STORE_SERVICE_ACCOUNT_JSON" > ${{ github.workspace }}/fastlane/play-store-key.json + echo "PLAY_STORE_JSON_KEY_PATH=${{ github.workspace }}/fastlane/play-store-key.json" >> $GITHUB_ENV + + - name: Install Fastlane + run: | + bundle install + bundle exec fastlane --version + + - name: Deploy to Play Store + env: + KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }} + KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }} + KEY_ALIAS: ${{ secrets.KEY_ALIAS }} + SUPPLY_RELEASE_STATUS: ${{ github.event.inputs.release_status }} + run: | + case "${{ github.event.inputs.track }}" in + internal) + bundle exec fastlane android deploy_internal + ;; + alpha) + bundle exec fastlane android deploy_alpha + ;; + beta) + bundle exec fastlane android deploy_beta + ;; + production) + bundle exec fastlane android deploy_production + ;; + esac + + - name: Clean up sensitive files + if: always() + run: | + rm -f ${{ github.workspace }}/keystore.jks + rm -f ${{ github.workspace }}/fastlane/play-store-key.json + + - name: Deployment Summary + if: success() + run: | + echo "## Fastlane Deployment Complete :rocket:" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "- **Track**: ${{ github.event.inputs.track }}" >> $GITHUB_STEP_SUMMARY + echo "- **Status**: ${{ github.event.inputs.release_status }}" >> $GITHUB_STEP_SUMMARY + echo "- **Package**: me.ghui.v2er" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "[View in Play Console](https://play.google.com/console)" >> $GITHUB_STEP_SUMMARY \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 97c5cd32..119f377b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,6 +10,24 @@ on: description: 'Version to release (e.g., v1.0.0)' required: true type: string + track: + description: 'Play Store release track' + required: false + type: choice + default: 'internal' + options: + - internal + - alpha + - beta + - production + status: + description: 'Play Store release status' + required: false + type: choice + default: 'draft' + options: + - draft + - completed permissions: contents: write @@ -68,18 +86,20 @@ jobs: - name: Build release APK env: - KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }} - KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }} - KEY_ALIAS: ${{ secrets.KEY_ALIAS }} - KEYSTORE_PATH: ${{ vars.ENABLE_SIGNING == 'true' && 'keystore.jks' || '' }} + GHUI_KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }} + GHUI_KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }} run: | if [ "${{ vars.ENABLE_SIGNING }}" = "true" ] && [ -f "app/keystore.jks" ]; then echo "Building signed release APK" - echo "Using key alias: ${KEY_ALIAS:-ghui}" - ./gradlew assembleRelease --stacktrace + echo "Using key alias: ${{ secrets.KEY_ALIAS }}" + ./gradlew assembleRelease \ + -Pandroid.injected.signing.store.file=${{ github.workspace }}/app/keystore.jks \ + -Pandroid.injected.signing.store.password=${{ secrets.KEYSTORE_PASSWORD }} \ + -Pandroid.injected.signing.key.alias=${{ secrets.KEY_ALIAS }} \ + -Pandroid.injected.signing.key.password=${{ secrets.KEY_PASSWORD }} else echo "Building unsigned release APK" - ./gradlew assembleRelease --stacktrace || ./gradlew assembleDebug --stacktrace + ./gradlew assembleRelease --stacktrace fi - name: Clean up keystore @@ -137,18 +157,28 @@ jobs: - name: Build release bundle env: - KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }} - KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }} - KEY_ALIAS: ${{ secrets.KEY_ALIAS }} - KEYSTORE_PATH: ${{ vars.ENABLE_SIGNING == 'true' && 'keystore.jks' || '' }} + GHUI_KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }} + GHUI_KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }} run: | if [ "${{ vars.ENABLE_SIGNING }}" = "true" ] && [ -f "app/keystore.jks" ]; then echo "Building signed release bundle" - ./gradlew bundleRelease --stacktrace + ./gradlew bundleRelease \ + -Pandroid.injected.signing.store.file=${{ github.workspace }}/app/keystore.jks \ + -Pandroid.injected.signing.store.password=${{ secrets.KEYSTORE_PASSWORD }} \ + -Pandroid.injected.signing.key.alias=${{ secrets.KEY_ALIAS }} \ + -Pandroid.injected.signing.key.password=${{ secrets.KEY_PASSWORD }} else echo "Skipping bundle build - signing not configured" fi + - name: Generate debug symbols + if: ${{ vars.ENABLE_SIGNING == 'true' }} + run: | + echo "Checking for debug symbols..." + find app/build/outputs -name "*.zip" -type f | grep -i debug || echo "No debug symbol zips found" + find app/build/outputs -name "*symbols*" -type f || echo "No symbol files found" + ls -la app/build/outputs/bundle/release/ || true + - name: Clean up keystore if: always() run: | @@ -242,24 +272,96 @@ jobs: runs-on: ubuntu-latest steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Download AAB artifact uses: actions/download-artifact@v4 with: name: release-bundle path: release-artifacts/ - - name: Find AAB file - id: find-aab + - name: Find bundle and symbols + id: find-files run: | AAB_PATH=$(find release-artifacts -name "*.aab" | head -1) echo "aab_path=$AAB_PATH" >> $GITHUB_OUTPUT - - name: Upload to Play Store + # Look for debug symbols + SYMBOLS_PATH=$(find release-artifacts -name "native-debug-symbols.zip" 2>/dev/null | head -1) + if [ -n "$SYMBOLS_PATH" ]; then + echo "symbols_path=$SYMBOLS_PATH" >> $GITHUB_OUTPUT + echo "Found debug symbols at: $SYMBOLS_PATH" + else + echo "No debug symbols found" + fi + + - name: Determine release track and status + id: release-config + run: | + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + TRACK="${{ github.event.inputs.track }}" + STATUS="${{ github.event.inputs.status }}" + else + # Default for tag pushes + TRACK="internal" + STATUS="draft" + fi + echo "track=$TRACK" >> $GITHUB_OUTPUT + echo "status=$STATUS" >> $GITHUB_OUTPUT + echo "Deploying to track: $TRACK with status: $STATUS" + + - name: Create whatsnew directory + run: | + mkdir -p whatsnew + + # Generate release notes + echo "Release ${{ needs.prepare.outputs.version }}" > whatsnew/whatsnew-en-US + echo "" >> whatsnew/whatsnew-en-US + + # Get recent commits + git log --pretty=format:"• %s" -5 >> whatsnew/whatsnew-en-US + + # Chinese version + echo "版本 ${{ needs.prepare.outputs.version }}" > whatsnew/whatsnew-zh-CN + echo "" >> whatsnew/whatsnew-zh-CN + git log --pretty=format:"• %s" -5 >> whatsnew/whatsnew-zh-CN + + - name: Upload to Play Store (with debug symbols) + if: steps.find-files.outputs.symbols_path != '' uses: r0adkll/upload-google-play@v1 with: - serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }} + serviceAccountJsonPlainText: ${{ secrets.PLAY_STORE_SERVICE_ACCOUNT_JSON }} packageName: me.ghui.v2er - releaseFiles: ${{ steps.find-aab.outputs.aab_path }} - track: internal - status: completed - whatsNewDirectory: whatsnew/ \ No newline at end of file + releaseFiles: ${{ steps.find-files.outputs.aab_path }} + track: ${{ steps.release-config.outputs.track }} + status: ${{ steps.release-config.outputs.status }} + debugSymbols: ${{ steps.find-files.outputs.symbols_path }} + whatsNewDirectory: whatsnew/ + changesNotSentForReview: true + continue-on-error: true + id: upload-with-symbols + + - name: Upload to Play Store (without debug symbols) + if: steps.find-files.outputs.symbols_path == '' || steps.upload-with-symbols.outcome == 'failure' + uses: r0adkll/upload-google-play@v1 + with: + serviceAccountJsonPlainText: ${{ secrets.PLAY_STORE_SERVICE_ACCOUNT_JSON }} + packageName: me.ghui.v2er + releaseFiles: ${{ steps.find-files.outputs.aab_path }} + track: ${{ steps.release-config.outputs.track }} + status: ${{ steps.release-config.outputs.status }} + whatsNewDirectory: whatsnew/ + changesNotSentForReview: true + + - name: Play Store Upload Summary + if: success() + run: | + echo "## Play Store Upload Complete :rocket:" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "- **Version**: ${{ needs.prepare.outputs.version }}" >> $GITHUB_STEP_SUMMARY + echo "- **Track**: ${{ steps.release-config.outputs.track }}" >> $GITHUB_STEP_SUMMARY + echo "- **Status**: ${{ steps.release-config.outputs.status }}" >> $GITHUB_STEP_SUMMARY + echo "- **Package**: me.ghui.v2er" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "[View in Play Console](https://play.google.com/console/u/0/developers/your-developer-id/app/me.ghui.v2er)" >> $GITHUB_STEP_SUMMARY \ No newline at end of file diff --git a/.gitignore b/.gitignore index ad349a78..f6ce7d50 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,18 @@ *.keystore *.base64.txt keystore.jks -app/keystore.jks \ No newline at end of file +app/keystore.jks + +# Fastlane +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots +fastlane/test_output +fastlane/readme.md +fastlane/play-store-key.json +fastlane/*.json + +# Bundle +vendor/bundle/ +.bundle/ +Gemfile.lock \ No newline at end of file diff --git a/.gitignore.fastlane b/.gitignore.fastlane new file mode 100644 index 00000000..da5628d6 --- /dev/null +++ b/.gitignore.fastlane @@ -0,0 +1,26 @@ +# Fastlane specific +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots +fastlane/test_output +fastlane/readme.md +fastlane/play-store-key.json +fastlane/*.json + +# Bundle +vendor/bundle/ +.bundle/ + +# Ruby +*.gem +*.rbc +/.config +/coverage/ +/InstalledFiles +/pkg/ +/spec/reports/ +/spec/examples.txt +/test/tmp/ +/test/version_tmp/ +/tmp/ +Gemfile.lock \ No newline at end of file diff --git a/Gemfile b/Gemfile new file mode 100644 index 00000000..fd6bfe8f --- /dev/null +++ b/Gemfile @@ -0,0 +1,4 @@ +source "https://rubygems.org" + +gem "fastlane" +gem "fastlane-plugin-firebase_app_distribution", "~> 0.7.0" # Optional: for Firebase distribution \ No newline at end of file diff --git a/docs/PLAY_STORE_DEPLOYMENT.md b/docs/PLAY_STORE_DEPLOYMENT.md new file mode 100644 index 00000000..83451580 --- /dev/null +++ b/docs/PLAY_STORE_DEPLOYMENT.md @@ -0,0 +1,299 @@ +# Google Play Store Deployment Guide + +This guide explains how to set up automatic deployment to Google Play Store for the V2er Android app. + +## Prerequisites + +1. Google Play Developer Account +2. App already published on Play Store (at least once manually) +3. Google Play Console API access enabled +4. Service Account with appropriate permissions + +## Setting Up Google Play Console + +### 1. Enable Google Play Console API + +1. Go to [Google Cloud Console](https://console.cloud.google.com) +2. Create a new project or select existing one +3. Enable "Google Play Android Developer API" +4. Go to APIs & Services > Google Play Android Developer API +5. Click "Enable" + +### 2. Create Service Account + +1. In Google Cloud Console, go to "IAM & Admin" > "Service Accounts" +2. Click "Create Service Account" +3. Name: `v2er-github-actions` +4. Description: "Service account for GitHub Actions deployments" +5. Click "Create and Continue" +6. Skip optional permissions (we'll set them in Play Console) +7. Click "Done" + +### 3. Generate Service Account Key + +1. Click on the created service account +2. Go to "Keys" tab +3. Click "Add Key" > "Create new key" +4. Choose JSON format +5. Download the key file (keep it secure!) + +### 4. Grant Permissions in Play Console + +1. Go to [Google Play Console](https://play.google.com/console) +2. Go to "Users and permissions" +3. Click "Invite new users" +4. Email: Use the service account email (format: `name@project-id.iam.gserviceaccount.com`) +5. Grant these permissions: + - **App access**: Select "V2er" app + - **Release management**: + - Manage production releases + - Manage testing track releases + - View app information + - **Financial data**: View financial data (optional, for reports) + +## GitHub Repository Setup + +### Required Secrets + +Add these secrets to your GitHub repository (Settings > Secrets and variables > Actions): + +| Secret Name | Description | How to Get | +|------------|-------------|------------| +| `KEYSTORE_BASE64` | Base64 encoded keystore file | `base64 -i your-keystore.jks` (Mac) or `base64 your-keystore.jks` (Linux) | +| `KEYSTORE_PASSWORD` | Keystore password | Your keystore password | +| `KEY_ALIAS` | Key alias in keystore | Usually "ghui" or your chosen alias | +| `KEY_PASSWORD` | Key password | Your key password (often same as keystore password) | +| `PLAY_STORE_SERVICE_ACCOUNT_JSON` | Service account JSON content | Copy entire content of downloaded JSON file | + +### Required Variables + +Add these repository variables (Settings > Secrets and variables > Actions > Variables): + +| Variable Name | Value | Description | +|--------------|-------|-------------| +| `ENABLE_SIGNING` | `true` | Enable APK/AAB signing | +| `ENABLE_PLAY_STORE_UPLOAD` | `true` | Enable automatic Play Store upload | + +## Deployment Workflows + +### 1. Automatic Deployment (Tag Push) + +When you push a version tag, the app is automatically built and uploaded: + +```bash +# Create and push a version tag +git tag v1.2.3 +git push origin v1.2.3 +``` + +This will: +- Build signed APK and AAB +- Create GitHub Release with artifacts +- Upload AAB to Play Store (Internal track, Draft status) + +### 2. Manual Deployment (Workflow Dispatch) + +Trigger deployment manually from GitHub Actions: + +1. Go to Actions tab +2. Select "Release" workflow +3. Click "Run workflow" +4. Choose: + - Version (e.g., v1.2.3) + - Track (internal/alpha/beta/production) + - Status (draft/completed) + +### 3. Fastlane Deployment (Alternative) + +Use Fastlane for more control: + +```bash +# Local deployment +bundle install +bundle exec fastlane android deploy_internal + +# Via GitHub Actions +# Go to Actions > Fastlane Deploy > Run workflow +``` + +## Release Tracks + +### Track Strategy + +1. **Internal** (`internal`) + - For internal testing team + - Quick iteration and testing + - Status: Usually `draft` + +2. **Alpha** (`alpha`) + - Early testing with limited users + - Major feature testing + - Status: `completed` + +3. **Beta** (`beta`) + - Wider testing audience + - Pre-release validation + - Status: `completed` + +4. **Production** (`production`) + - Full release to all users + - Staged rollout recommended + - Status: `completed` + +### Promotion Flow + +``` +Internal -> Alpha -> Beta -> Production +``` + +Use Fastlane lanes to promote: + +```bash +# Promote from internal to alpha +bundle exec fastlane android promote_to_alpha + +# Promote from alpha to beta +bundle exec fastlane android promote_to_beta + +# Promote from beta to production (10% rollout) +bundle exec fastlane android promote_to_production + +# Complete production rollout (100%) +bundle exec fastlane android complete_rollout +``` + +## Version Management + +### Version Code and Name + +Update in `app/build.gradle`: + +```gradle +android { + defaultConfig { + versionCode 123 // Increment for each release + versionName "1.2.3" // User-visible version + } +} +``` + +### Semantic Versioning + +Follow semantic versioning (MAJOR.MINOR.PATCH): +- MAJOR: Breaking changes +- MINOR: New features +- PATCH: Bug fixes + +## Release Notes + +### Automated Release Notes + +Place release notes in `whatsnew/` directory: + +``` +whatsnew/ +├── whatsnew-en-US # English +├── whatsnew-zh-CN # Simplified Chinese +└── whatsnew-zh-TW # Traditional Chinese +``` + +Maximum 500 characters per file. + +### Metadata Management + +For full metadata management with Fastlane: + +``` +fastlane/ +└── metadata/ + └── android/ + ├── en-US/ + │ ├── title.txt + │ ├── short_description.txt + │ ├── full_description.txt + │ └── changelogs/ + │ └── 123.txt # Version code + └── zh-CN/ + └── ... +``` + +## Troubleshooting + +### Common Issues + +1. **Authentication Failed** + - Verify service account email in Play Console + - Check JSON key is correctly copied to GitHub secret + - Ensure permissions are granted in Play Console + +2. **Version Code Already Exists** + - Increment versionCode in build.gradle + - Each upload must have unique version code + +3. **Signing Issues** + - Verify keystore base64 encoding + - Check key alias matches + - Ensure passwords are correct + +4. **Upload Fails** + - Check app package name matches + - Verify AAB is correctly signed + - Ensure track exists in Play Console + +### Debug Commands + +```bash +# Verify keystore +keytool -list -v -keystore your-keystore.jks + +# Test service account locally +export GOOGLE_APPLICATION_CREDENTIALS=path/to/service-account.json +bundle exec fastlane supply init + +# Check AAB signing +bundletool build-apks --bundle=app.aab --output=app.apks +unzip -l app.apks +``` + +## Security Best Practices + +1. **Rotate Service Account Keys** + - Regenerate keys periodically + - Remove old keys from Google Cloud Console + +2. **Limit Permissions** + - Grant minimum required permissions + - Use separate accounts for different environments + +3. **Protect Secrets** + - Never commit secrets to repository + - Use GitHub encrypted secrets + - Rotate passwords regularly + +4. **Audit Access** + - Review Play Console users regularly + - Monitor service account usage + - Check GitHub Actions logs + +## CI/CD Best Practices + +1. **Test Before Release** + - Run tests in CI before deployment + - Use internal track for validation + - Gradual rollout for production + +2. **Version Control** + - Tag releases in git + - Keep changelog updated + - Document breaking changes + +3. **Monitoring** + - Monitor crash reports + - Track user reviews + - Check vitals in Play Console + +## Support + +For issues or questions: +- GitHub Issues: [v2er-app/V2er-Android](https://github.com/v2er-app/V2er-Android/issues) +- Play Console Help: [support.google.com/googleplay/android-developer](https://support.google.com/googleplay/android-developer) \ No newline at end of file diff --git a/fastlane/Appfile b/fastlane/Appfile new file mode 100644 index 00000000..32024a37 --- /dev/null +++ b/fastlane/Appfile @@ -0,0 +1,8 @@ +# For more information about the Appfile, see: +# https://docs.fastlane.tools/advanced/Appfile + +# Package name +package_name("me.ghui.v2er") + +# Path to the json key file for Google Play Console API +json_key_file(ENV["PLAY_STORE_JSON_KEY_PATH"] || "fastlane/play-store-key.json") \ No newline at end of file diff --git a/fastlane/Fastfile b/fastlane/Fastfile new file mode 100644 index 00000000..69207a63 --- /dev/null +++ b/fastlane/Fastfile @@ -0,0 +1,174 @@ +# This file contains the fastlane.tools configuration +# You can find the documentation at https://docs.fastlane.tools +# +# For a list of all available actions, check out +# https://docs.fastlane.tools/actions +# +# For a list of all available plugins, check out +# https://docs.fastlane.tools/plugins/available-plugins + +default_platform(:android) + +platform :android do + desc "Build debug APK" + lane :debug do + gradle( + task: "assembleDebug", + print_command: true + ) + end + + desc "Build release APK" + lane :build_apk do + gradle( + task: "assembleRelease", + properties: { + "android.injected.signing.store.file" => ENV["KEYSTORE_PATH"], + "android.injected.signing.store.password" => ENV["KEYSTORE_PASSWORD"], + "android.injected.signing.key.alias" => ENV["KEY_ALIAS"], + "android.injected.signing.key.password" => ENV["KEY_PASSWORD"] + }, + print_command: false + ) + end + + desc "Build release bundle (AAB)" + lane :build_bundle do + gradle( + task: "bundleRelease", + properties: { + "android.injected.signing.store.file" => ENV["KEYSTORE_PATH"], + "android.injected.signing.store.password" => ENV["KEYSTORE_PASSWORD"], + "android.injected.signing.key.alias" => ENV["KEY_ALIAS"], + "android.injected.signing.key.password" => ENV["KEY_PASSWORD"] + }, + print_command: false + ) + end + + desc "Deploy to Google Play Store (Internal Track)" + lane :deploy_internal do + build_bundle + upload_to_play_store( + track: "internal", + release_status: "draft", + skip_upload_metadata: true, + skip_upload_images: true, + skip_upload_screenshots: true, + aab: "app/build/outputs/bundle/release/app-release.aab" + ) + end + + desc "Deploy to Google Play Store (Alpha Track)" + lane :deploy_alpha do + build_bundle + upload_to_play_store( + track: "alpha", + release_status: "completed", + skip_upload_metadata: true, + skip_upload_images: true, + skip_upload_screenshots: true, + aab: "app/build/outputs/bundle/release/app-release.aab" + ) + end + + desc "Deploy to Google Play Store (Beta Track)" + lane :deploy_beta do + build_bundle + upload_to_play_store( + track: "beta", + release_status: "completed", + skip_upload_metadata: false, + skip_upload_images: false, + skip_upload_screenshots: false, + aab: "app/build/outputs/bundle/release/app-release.aab" + ) + end + + desc "Deploy to Google Play Store (Production)" + lane :deploy_production do + build_bundle + upload_to_play_store( + track: "production", + release_status: "completed", + skip_upload_metadata: false, + skip_upload_images: false, + skip_upload_screenshots: false, + aab: "app/build/outputs/bundle/release/app-release.aab" + ) + end + + desc "Promote from internal to alpha" + lane :promote_to_alpha do + upload_to_play_store( + track: "internal", + track_promote_to: "alpha", + skip_upload_apk: true, + skip_upload_aab: true, + skip_upload_metadata: true, + skip_upload_images: true, + skip_upload_screenshots: true + ) + end + + desc "Promote from alpha to beta" + lane :promote_to_beta do + upload_to_play_store( + track: "alpha", + track_promote_to: "beta", + skip_upload_apk: true, + skip_upload_aab: true, + skip_upload_metadata: true, + skip_upload_images: true, + skip_upload_screenshots: true + ) + end + + desc "Promote from beta to production" + lane :promote_to_production do + upload_to_play_store( + track: "beta", + track_promote_to: "production", + rollout: "0.1", # Start with 10% rollout + skip_upload_apk: true, + skip_upload_aab: true, + skip_upload_metadata: true, + skip_upload_images: true, + skip_upload_screenshots: true + ) + end + + desc "Complete production rollout" + lane :complete_rollout do + upload_to_play_store( + track: "production", + rollout: "1.0", # 100% rollout + skip_upload_apk: true, + skip_upload_aab: true, + skip_upload_metadata: true, + skip_upload_images: true, + skip_upload_screenshots: true + ) + end + + desc "Run tests" + lane :test do + gradle(task: "test") + end + + desc "Run lint" + lane :lint do + gradle(task: "lint") + end + + desc "Submit a new build to Crashlytics for testing" + lane :crashlytics_beta do + build_apk + # Assuming Firebase Crashlytics is configured + # firebase_app_distribution( + # app: ENV["FIREBASE_APP_ID"], + # groups: "testers", + # release_notes: "New beta build" + # ) + end +end \ No newline at end of file From b34e685782eb3eed0fd2173e7851cdd8b7fb99ba Mon Sep 17 00:00:00 2001 From: Gray Zhang Date: Tue, 9 Sep 2025 15:00:09 +0800 Subject: [PATCH 2/2] fix: address PR review comments from Copilot MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Update Fastfile rollout comment to clarify decimal representation - Remove unused GHUI_ environment variables from release workflow - Fix Play Console URL by removing placeholder developer ID 🤖 Generated with Claude Code (https://claude.ai/code) Co-Authored-By: Claude --- .github/workflows/release.yml | 8 +------- fastlane/Fastfile | 2 +- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 119f377b..9126050a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -85,9 +85,6 @@ jobs: echo "Key alias configured: ${{ secrets.KEY_ALIAS != '' && 'Yes' || 'No' }}" - name: Build release APK - env: - GHUI_KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }} - GHUI_KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }} run: | if [ "${{ vars.ENABLE_SIGNING }}" = "true" ] && [ -f "app/keystore.jks" ]; then echo "Building signed release APK" @@ -156,9 +153,6 @@ jobs: echo "$KEYSTORE_BASE64" | base64 --decode > app/keystore.jks - name: Build release bundle - env: - GHUI_KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }} - GHUI_KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }} run: | if [ "${{ vars.ENABLE_SIGNING }}" = "true" ] && [ -f "app/keystore.jks" ]; then echo "Building signed release bundle" @@ -364,4 +358,4 @@ jobs: echo "- **Status**: ${{ steps.release-config.outputs.status }}" >> $GITHUB_STEP_SUMMARY echo "- **Package**: me.ghui.v2er" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY - echo "[View in Play Console](https://play.google.com/console/u/0/developers/your-developer-id/app/me.ghui.v2er)" >> $GITHUB_STEP_SUMMARY \ No newline at end of file + echo "[View in Play Console](https://play.google.com/console/u/0/app/me.ghui.v2er)" >> $GITHUB_STEP_SUMMARY \ No newline at end of file diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 69207a63..df9d5a55 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -129,7 +129,7 @@ platform :android do upload_to_play_store( track: "beta", track_promote_to: "production", - rollout: "0.1", # Start with 10% rollout + rollout: "0.1", # Start with 10% rollout (0.1 = 10%) skip_upload_apk: true, skip_upload_aab: true, skip_upload_metadata: true,