chore: bump version to 2.3.11 (241) (#148) #45
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 | |
| on: | |
| push: | |
| branches: | |
| - main | |
| paths: | |
| - 'config.gradle' | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: 'Version to release (e.g., v1.0.0)' | |
| required: true | |
| type: string | |
| track: | |
| description: 'Play Store release track' | |
| required: false | |
| type: choice | |
| default: 'beta' | |
| options: | |
| - internal | |
| - alpha | |
| - beta | |
| - production | |
| status: | |
| description: 'Play Store release status' | |
| required: false | |
| type: choice | |
| default: 'draft' | |
| options: | |
| - draft | |
| - completed | |
| permissions: | |
| contents: write | |
| jobs: | |
| prepare: | |
| name: Prepare Release | |
| runs-on: ubuntu-latest | |
| outputs: | |
| version: ${{ steps.version.outputs.version }} | |
| version_code: ${{ steps.version_info.outputs.version_code }} | |
| should_release: ${{ steps.check_version.outputs.should_release }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 2 # Need history for version comparison | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Check version change | |
| id: check_version | |
| if: github.event_name == 'push' | |
| run: | | |
| # Get current version from config.gradle with error handling | |
| CURRENT_VERSION=$(grep versionName config.gradle | sed -E 's/.*versionName:\s*"([^"]+)".*/\1/' || echo "") | |
| CURRENT_CODE=$(grep versionCode config.gradle | sed -E 's/.*versionCode:\s*([0-9]+).*/\1/' || echo "") | |
| # Validate extraction was successful | |
| if [ -z "$CURRENT_VERSION" ] || [ -z "$CURRENT_CODE" ]; then | |
| echo "❌ Error: Failed to extract version information from config.gradle" | |
| echo "CURRENT_VERSION='$CURRENT_VERSION', CURRENT_CODE='$CURRENT_CODE'" | |
| echo "should_release=false" >> $GITHUB_OUTPUT | |
| exit 1 | |
| fi | |
| # Validate CURRENT_CODE is a valid number | |
| if ! [[ "$CURRENT_CODE" =~ ^[0-9]+$ ]]; then | |
| echo "❌ Error: Version code is not a valid number: $CURRENT_CODE" | |
| echo "should_release=false" >> $GITHUB_OUTPUT | |
| exit 1 | |
| fi | |
| # Get previous version - handle first commit case | |
| if git rev-parse HEAD~1 >/dev/null 2>&1; then | |
| git show HEAD~1:config.gradle > prev_config.gradle 2>/dev/null || echo "No previous config.gradle" | |
| if [ -f prev_config.gradle ]; then | |
| PREV_VERSION=$(grep versionName prev_config.gradle | sed -E 's/.*versionName:\s*"([^"]+)".*/\1/' || echo "") | |
| PREV_CODE=$(grep versionCode prev_config.gradle | sed -E 's/.*versionCode:\s*([0-9]+).*/\1/' || echo "0") | |
| rm prev_config.gradle | |
| else | |
| PREV_VERSION="" | |
| PREV_CODE="0" | |
| fi | |
| else | |
| # First commit in repo | |
| PREV_VERSION="" | |
| PREV_CODE="0" | |
| fi | |
| # Validate PREV_CODE is a valid number (default to 0 if not) | |
| if ! [[ "$PREV_CODE" =~ ^[0-9]+$ ]]; then | |
| PREV_CODE="0" | |
| fi | |
| echo "Previous: v$PREV_VERSION (code: $PREV_CODE)" | |
| echo "Current: v$CURRENT_VERSION (code: $CURRENT_CODE)" | |
| # Check if version increased | |
| if [ "$CURRENT_CODE" -gt "$PREV_CODE" ]; then | |
| echo "✅ Version increased from $PREV_VERSION to $CURRENT_VERSION" | |
| echo "should_release=true" >> $GITHUB_OUTPUT | |
| echo "version=v$CURRENT_VERSION" >> $GITHUB_OUTPUT | |
| echo "version_code=$CURRENT_CODE" >> $GITHUB_OUTPUT | |
| else | |
| echo "ℹ️ No version increase detected" | |
| echo "should_release=false" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| - name: Determine version | |
| id: version | |
| run: | | |
| if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then | |
| VERSION="${{ github.event.inputs.version }}" | |
| echo "should_release=true" >> $GITHUB_OUTPUT | |
| elif [ "${{ steps.check_version.outputs.should_release }}" = "true" ]; then | |
| VERSION="${{ steps.check_version.outputs.version }}" | |
| else | |
| echo "No release needed" | |
| exit 0 | |
| fi | |
| echo "version=$VERSION" >> $GITHUB_OUTPUT | |
| echo "Version: $VERSION" | |
| - name: Extract version code | |
| id: version_info | |
| run: | | |
| # Use version code from check_version step if available, otherwise extract it | |
| if [ -n "${{ steps.check_version.outputs.version_code }}" ]; then | |
| VERSION_CODE="${{ steps.check_version.outputs.version_code }}" | |
| else | |
| # Only extract if not already available (for workflow_dispatch) | |
| VERSION_CODE=$(grep "versionCode:" config.gradle | sed -E 's/.*versionCode:\s*([0-9]+).*/\1/') | |
| fi | |
| echo "version_code=$VERSION_CODE" >> $GITHUB_OUTPUT | |
| echo "Version Code: $VERSION_CODE" | |
| - name: Create and push tag | |
| if: steps.check_version.outputs.should_release == 'true' && github.event_name == 'push' | |
| id: create_tag | |
| run: | | |
| TAG_NAME="${{ steps.version.outputs.version }}" | |
| # Check if tag already exists | |
| if git ls-remote --tags origin | grep -q "refs/tags/${TAG_NAME}$"; then | |
| echo "⚠️ Tag $TAG_NAME already exists, skipping tag creation" | |
| echo "tag_created=false" >> $GITHUB_OUTPUT | |
| echo "tag_exists=true" >> $GITHUB_OUTPUT | |
| else | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| # Create annotated tag | |
| git tag -a "${TAG_NAME}" -m "Release ${TAG_NAME} (version code: ${{ steps.version_info.outputs.version_code }})" | |
| git push origin "${TAG_NAME}" | |
| echo "🏷️ Created and pushed tag: ${TAG_NAME}" | |
| echo "tag_created=true" >> $GITHUB_OUTPUT | |
| echo "tag_exists=false" >> $GITHUB_OUTPUT | |
| fi | |
| build-apk: | |
| name: Build Release APK | |
| needs: prepare | |
| if: needs.prepare.outputs.should_release == 'true' || github.event_name == 'workflow_dispatch' | |
| 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: Grant execute permission for gradlew | |
| run: chmod +x gradlew | |
| - name: Decode Keystore | |
| if: ${{ vars.ENABLE_SIGNING == 'true' && env.KEYSTORE_BASE64 != '' }} | |
| env: | |
| KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }} | |
| run: | | |
| echo "$KEYSTORE_BASE64" | base64 --decode > app/keystore.jks | |
| - name: Debug keystore info | |
| if: ${{ vars.ENABLE_SIGNING == 'true' }} | |
| run: | | |
| echo "Keystore file exists: $([ -f app/keystore.jks ] && echo 'Yes' || echo 'No')" | |
| echo "Keystore size: $([ -f app/keystore.jks ] && ls -la app/keystore.jks | awk '{print $5}' || echo 'N/A')" | |
| echo "Key alias configured: ${{ secrets.KEY_ALIAS != '' && 'Yes' || 'No' }}" | |
| - name: Build release APK | |
| run: | | |
| if [ "${{ vars.ENABLE_SIGNING }}" = "true" ] && [ -f "app/keystore.jks" ]; then | |
| echo "Building signed release APK" | |
| 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 | |
| fi | |
| - name: Clean up keystore | |
| if: always() | |
| run: | | |
| rm -f app/keystore.jks | |
| - name: Upload release APK | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: release-apk | |
| path: app/build/outputs/apk/**/*.apk | |
| retention-days: 30 | |
| - name: APK Summary | |
| run: | | |
| echo "## APK Build Results :package:" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| APK_PATH=$(find app/build/outputs/apk -name "*.apk" | grep -E "release" | head -1) | |
| if [ -f "$APK_PATH" ]; then | |
| APK_SIZE=$(du -h "$APK_PATH" | cut -f1) | |
| echo "- APK Size: $APK_SIZE" >> $GITHUB_STEP_SUMMARY | |
| echo "- APK Name: \`$(basename "$APK_PATH")\`" >> $GITHUB_STEP_SUMMARY | |
| echo "- Signed: ${{ vars.ENABLE_SIGNING == 'true' && 'Yes' || 'No' }}" >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "No APK found" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| build-aab: | |
| name: Build Release Bundle | |
| needs: prepare | |
| if: needs.prepare.outputs.should_release == 'true' || github.event_name == 'workflow_dispatch' | |
| 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: Grant execute permission for gradlew | |
| run: chmod +x gradlew | |
| - name: Decode Keystore | |
| if: ${{ vars.ENABLE_SIGNING == 'true' && env.KEYSTORE_BASE64 != '' }} | |
| env: | |
| KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }} | |
| run: | | |
| echo "$KEYSTORE_BASE64" | base64 --decode > app/keystore.jks | |
| - name: Build release bundle | |
| run: | | |
| if [ "${{ vars.ENABLE_SIGNING }}" = "true" ] && [ -f "app/keystore.jks" ]; then | |
| echo "Building signed release bundle" | |
| ./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: | | |
| rm -f app/keystore.jks | |
| - name: Upload release bundle | |
| if: ${{ vars.ENABLE_SIGNING == 'true' }} | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: release-bundle | |
| path: | | |
| app/build/outputs/bundle/**/*.aab | |
| app/build/outputs/mapping/release/mapping.txt | |
| retention-days: 30 | |
| release: | |
| name: Create GitHub Release | |
| needs: [prepare, build-apk, build-aab] | |
| if: needs.prepare.outputs.should_release == 'true' || github.event_name == 'workflow_dispatch' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Download APK artifact | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: release-apk | |
| path: release-artifacts/ | |
| - name: Download AAB artifact | |
| if: ${{ vars.ENABLE_SIGNING == 'true' }} | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: release-bundle | |
| path: release-artifacts/ | |
| continue-on-error: true | |
| - name: Prepare release assets | |
| id: assets | |
| run: | | |
| # Find APK | |
| APK_PATH=$(find release-artifacts -name "*.apk" | grep -E "release" | head -1) | |
| if [ -f "$APK_PATH" ]; then | |
| APK_NAME="v2er-${{ needs.prepare.outputs.version }}.apk" | |
| mv "$APK_PATH" "$APK_NAME" | |
| echo "apk_path=$APK_NAME" >> $GITHUB_OUTPUT | |
| fi | |
| # Find AAB | |
| AAB_PATH=$(find release-artifacts -name "*.aab" 2>/dev/null | head -1) | |
| if [ -f "$AAB_PATH" ]; then | |
| AAB_NAME="v2er-${{ needs.prepare.outputs.version }}.aab" | |
| mv "$AAB_PATH" "$AAB_NAME" | |
| echo "aab_path=$AAB_NAME" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Generate changelog | |
| id: changelog | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| GITHUB_REPOSITORY: ${{ github.repository }} | |
| run: | | |
| # Define shared functions that will be used in both changelog generation steps | |
| cat > /tmp/release_functions.sh << 'FUNCTIONS_EOF' | |
| # Function to categorize commit message | |
| categorize_commit() { | |
| local msg="$1" | |
| local category="" | |
| local cleaned_msg="" | |
| if [[ "$msg" =~ ^fix(\(.*\))?:\ (.*)$ ]] || [[ "$msg" =~ ^fix\ (.*)$ ]]; then | |
| category="bug" | |
| cleaned_msg="${BASH_REMATCH[-1]}" | |
| elif [[ "$msg" =~ ^feat(\(.*\))?:\ (.*)$ ]] || [[ "$msg" =~ ^feat\ (.*)$ ]]; then | |
| category="feature" | |
| cleaned_msg="${BASH_REMATCH[-1]}" | |
| elif [[ "$msg" =~ ^perf(\(.*\))?:\ (.*)$ ]]; then | |
| category="performance" | |
| cleaned_msg="${BASH_REMATCH[2]}" | |
| elif [[ "$msg" =~ ^refactor(\(.*\))?:\ (.*)$ ]]; then | |
| category="improvement" | |
| cleaned_msg="${BASH_REMATCH[2]}" | |
| elif [[ "$msg" =~ ^chore(\(.*\))?:\ (.*)$ ]]; then | |
| category="maintenance" | |
| cleaned_msg="${BASH_REMATCH[2]}" | |
| elif [[ "$msg" =~ ^docs(\(.*\))?:\ (.*)$ ]]; then | |
| category="documentation" | |
| cleaned_msg="${BASH_REMATCH[2]}" | |
| else | |
| category="other" | |
| cleaned_msg="$msg" | |
| fi | |
| # Remove PR numbers from the end | |
| cleaned_msg=$(echo "$cleaned_msg" | sed 's/ (#[0-9]*)//') | |
| # Capitalize first letter | |
| cleaned_msg="$(echo "${cleaned_msg:0:1}" | tr '[:lower:]' '[:upper:]')${cleaned_msg:1}" | |
| echo "$category:$cleaned_msg" | |
| } | |
| # Function to get GitHub username from commit | |
| get_github_username() { | |
| local commit_sha="$1" | |
| local github_repo="${GITHUB_REPOSITORY}" | |
| # Try to get the GitHub username from the commit using gh api | |
| local username=$(gh api "repos/${github_repo}/commits/${commit_sha}" --jq '.author.login // empty' 2>/dev/null || echo "") | |
| if [ -n "$username" ]; then | |
| echo "@$username" | |
| else | |
| # Fallback: try to get committer login if author login is not available | |
| local committer=$(gh api "repos/${github_repo}/commits/${commit_sha}" --jq '.committer.login // empty' 2>/dev/null || echo "") | |
| if [ -n "$committer" ]; then | |
| echo "@$committer" | |
| else | |
| # Last resort: use hardcoded mapping for known authors | |
| local git_author=$(git show -s --format='%an' $commit_sha) | |
| case "$git_author" in | |
| "Gray Zhang" | "gray" | "Gray") | |
| echo "@graycreate" | |
| ;; | |
| "github-actions[bot]") | |
| echo "@github-actions[bot]" | |
| ;; | |
| *) | |
| # If no mapping found, use git author name without @ | |
| echo "$git_author" | |
| ;; | |
| esac | |
| fi | |
| fi | |
| } | |
| FUNCTIONS_EOF | |
| # Source the functions | |
| source /tmp/release_functions.sh | |
| # Get commits since last tag | |
| LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "") | |
| CURRENT_TAG="${{ needs.prepare.outputs.version }}" | |
| # Collect commits and categorize them | |
| declare -A features | |
| declare -A bugs | |
| declare -A improvements | |
| declare -A performance | |
| declare -A maintenance | |
| if [ -n "$LAST_TAG" ]; then | |
| RANGE="$LAST_TAG..HEAD" | |
| else | |
| RANGE="HEAD" | |
| fi | |
| # Process commits | |
| while IFS= read -r line; do | |
| sha=$(echo "$line" | cut -d' ' -f1) | |
| msg=$(echo "$line" | cut -d' ' -f2-) | |
| # Skip version bump and merge commits | |
| if [[ "$msg" =~ "bump version" ]] || [[ "$msg" =~ "Merge pull request" ]] || [[ "$msg" =~ "Merge branch" ]]; then | |
| continue | |
| fi | |
| categorized=$(categorize_commit "$msg") | |
| category=$(echo "$categorized" | cut -d':' -f1) | |
| clean_msg=$(echo "$categorized" | cut -d':' -f2-) | |
| author=$(get_github_username "$sha") | |
| # Store in associative arrays | |
| case "$category" in | |
| feature) | |
| features["$clean_msg"]="$author" | |
| ;; | |
| bug) | |
| bugs["$clean_msg"]="$author" | |
| ;; | |
| improvement) | |
| improvements["$clean_msg"]="$author" | |
| ;; | |
| performance) | |
| performance["$clean_msg"]="$author" | |
| ;; | |
| maintenance) | |
| maintenance["$clean_msg"]="$author" | |
| ;; | |
| esac | |
| done < <(git log --oneline --no-merges $RANGE) | |
| # Generate GitHub Release Notes | |
| echo "## What's Changed" > CHANGELOG.md | |
| echo "" >> CHANGELOG.md | |
| if [ ${#features[@]} -gt 0 ]; then | |
| echo "### 🚀 New Features" >> CHANGELOG.md | |
| for msg in "${!features[@]}"; do | |
| author="${features[$msg]}" | |
| if [ -n "$author" ]; then | |
| echo "* $msg by $author" >> CHANGELOG.md | |
| else | |
| echo "* $msg" >> CHANGELOG.md | |
| fi | |
| done | |
| echo "" >> CHANGELOG.md | |
| fi | |
| if [ ${#bugs[@]} -gt 0 ]; then | |
| echo "### 🐛 Bug Fixes" >> CHANGELOG.md | |
| for msg in "${!bugs[@]}"; do | |
| author="${bugs[$msg]}" | |
| if [ -n "$author" ]; then | |
| echo "* $msg by $author" >> CHANGELOG.md | |
| else | |
| echo "* $msg" >> CHANGELOG.md | |
| fi | |
| done | |
| echo "" >> CHANGELOG.md | |
| fi | |
| if [ ${#improvements[@]} -gt 0 ]; then | |
| echo "### 💪 Improvements" >> CHANGELOG.md | |
| for msg in "${!improvements[@]}"; do | |
| author="${improvements[$msg]}" | |
| if [ -n "$author" ]; then | |
| echo "* $msg by $author" >> CHANGELOG.md | |
| else | |
| echo "* $msg" >> CHANGELOG.md | |
| fi | |
| done | |
| echo "" >> CHANGELOG.md | |
| fi | |
| if [ ${#performance[@]} -gt 0 ]; then | |
| echo "### ⚡ Performance" >> CHANGELOG.md | |
| for msg in "${!performance[@]}"; do | |
| author="${performance[$msg]}" | |
| if [ -n "$author" ]; then | |
| echo "* $msg by $author" >> CHANGELOG.md | |
| else | |
| echo "* $msg" >> CHANGELOG.md | |
| fi | |
| done | |
| echo "" >> CHANGELOG.md | |
| fi | |
| echo "" >> CHANGELOG.md | |
| echo "**Full Changelog**: https://github.com/${{ github.repository }}/compare/${LAST_TAG}...${{ needs.prepare.outputs.version }}" >> CHANGELOG.md | |
| - name: Create Release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: ${{ needs.prepare.outputs.version }} | |
| name: Release ${{ needs.prepare.outputs.version }} | |
| body_path: CHANGELOG.md | |
| draft: false | |
| prerelease: false | |
| files: | | |
| ${{ steps.assets.outputs.apk_path }} | |
| ${{ steps.assets.outputs.aab_path }} | |
| fail_on_unmatched_files: false | |
| play-store-upload: | |
| name: Upload to Play Store | |
| needs: [prepare, build-aab] | |
| if: ${{ (needs.prepare.outputs.should_release == 'true' || github.event_name == 'workflow_dispatch') && vars.ENABLE_PLAY_STORE_UPLOAD == 'true' && vars.ENABLE_SIGNING == 'true' }} | |
| 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 bundle and symbols | |
| id: find-files | |
| run: | | |
| AAB_PATH=$(find release-artifacts -name "*.aab" | head -1) | |
| echo "aab_path=$AAB_PATH" >> $GITHUB_OUTPUT | |
| # 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 | |
| # Look for ProGuard/R8 mapping file | |
| MAPPING_PATH=$(find release-artifacts -name "mapping.txt" 2>/dev/null | head -1) | |
| if [ -n "$MAPPING_PATH" ]; then | |
| echo "mapping_path=$MAPPING_PATH" >> $GITHUB_OUTPUT | |
| echo "Found ReTrace mapping file at: $MAPPING_PATH" | |
| else | |
| echo "No ReTrace mapping file 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 - use beta (public test track) | |
| TRACK="beta" | |
| STATUS="completed" | |
| fi | |
| echo "track=$TRACK" >> $GITHUB_OUTPUT | |
| echo "status=$STATUS" >> $GITHUB_OUTPUT | |
| echo "Deploying to track: $TRACK with status: $STATUS" | |
| - name: Create whatsnew directory | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| GITHUB_REPOSITORY: ${{ github.repository }} | |
| run: | | |
| mkdir -p whatsnew | |
| # Define shared functions (duplicated from release job since jobs run in isolation) | |
| cat > /tmp/release_functions.sh << 'FUNCTIONS_EOF' | |
| # Function to categorize commit message | |
| categorize_commit() { | |
| local msg="$1" | |
| local category="" | |
| local cleaned_msg="" | |
| if [[ "$msg" =~ ^fix(\(.*\))?:\ (.*)$ ]] || [[ "$msg" =~ ^fix\ (.*)$ ]]; then | |
| category="bug" | |
| cleaned_msg="${BASH_REMATCH[-1]}" | |
| elif [[ "$msg" =~ ^feat(\(.*\))?:\ (.*)$ ]] || [[ "$msg" =~ ^feat\ (.*)$ ]]; then | |
| category="feature" | |
| cleaned_msg="${BASH_REMATCH[-1]}" | |
| elif [[ "$msg" =~ ^perf(\(.*\))?:\ (.*)$ ]]; then | |
| category="performance" | |
| cleaned_msg="${BASH_REMATCH[2]}" | |
| elif [[ "$msg" =~ ^refactor(\(.*\))?:\ (.*)$ ]]; then | |
| category="improvement" | |
| cleaned_msg="${BASH_REMATCH[2]}" | |
| elif [[ "$msg" =~ ^chore(\(.*\))?:\ (.*)$ ]]; then | |
| category="maintenance" | |
| cleaned_msg="${BASH_REMATCH[2]}" | |
| elif [[ "$msg" =~ ^docs(\(.*\))?:\ (.*)$ ]]; then | |
| category="documentation" | |
| cleaned_msg="${BASH_REMATCH[2]}" | |
| else | |
| category="other" | |
| cleaned_msg="$msg" | |
| fi | |
| # Remove PR numbers from the end | |
| cleaned_msg=$(echo "$cleaned_msg" | sed 's/ (#[0-9]*)//') | |
| # Capitalize first letter | |
| cleaned_msg="$(echo "${cleaned_msg:0:1}" | tr '[:lower:]' '[:upper:]')${cleaned_msg:1}" | |
| echo "$category:$cleaned_msg" | |
| } | |
| # Function to get GitHub username from commit | |
| get_github_username() { | |
| local commit_sha="$1" | |
| local github_repo="${GITHUB_REPOSITORY}" | |
| # Try to get the GitHub username from the commit using gh api | |
| local username=$(gh api "repos/${github_repo}/commits/${commit_sha}" --jq '.author.login // empty' 2>/dev/null || echo "") | |
| if [ -n "$username" ]; then | |
| echo "@$username" | |
| else | |
| # Fallback: try to get committer login if author login is not available | |
| local committer=$(gh api "repos/${github_repo}/commits/${commit_sha}" --jq '.committer.login // empty' 2>/dev/null || echo "") | |
| if [ -n "$committer" ]; then | |
| echo "@$committer" | |
| else | |
| # Last resort: use hardcoded mapping for known authors | |
| local git_author=$(git show -s --format='%an' $commit_sha) | |
| case "$git_author" in | |
| "Gray Zhang" | "gray" | "Gray") | |
| echo "@graycreate" | |
| ;; | |
| "github-actions[bot]") | |
| echo "@github-actions[bot]" | |
| ;; | |
| *) | |
| # If no mapping found, use git author name without @ | |
| echo "$git_author" | |
| ;; | |
| esac | |
| fi | |
| fi | |
| } | |
| FUNCTIONS_EOF | |
| # Source the shared functions | |
| source /tmp/release_functions.sh | |
| # Get commits since last tag for categorization | |
| LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "") | |
| # Collect and categorize commits | |
| declare -A features | |
| declare -A bugs | |
| declare -A improvements | |
| declare -A performance | |
| declare -A contributors | |
| if [ -n "$LAST_TAG" ]; then | |
| RANGE="$LAST_TAG..HEAD" | |
| else | |
| RANGE="HEAD~5..HEAD" | |
| fi | |
| # Process commits | |
| while IFS= read -r line; do | |
| sha=$(echo "$line" | cut -d' ' -f1) | |
| msg=$(echo "$line" | cut -d' ' -f2-) | |
| # Skip version bump and merge commits | |
| if [[ "$msg" =~ "bump version" ]] || [[ "$msg" =~ "Merge pull request" ]] || [[ "$msg" =~ "Merge branch" ]]; then | |
| continue | |
| fi | |
| categorized=$(categorize_commit "$msg") | |
| category=$(echo "$categorized" | cut -d':' -f1) | |
| clean_msg=$(echo "$categorized" | cut -d':' -f2-) | |
| # Get author for this commit | |
| author=$(get_github_username "$sha") | |
| if [ -n "$author" ]; then | |
| contributors["$author"]="1" | |
| fi | |
| case "$category" in | |
| feature) | |
| features["$clean_msg"]="$author" | |
| ;; | |
| bug) | |
| bugs["$clean_msg"]="$author" | |
| ;; | |
| improvement) | |
| improvements["$clean_msg"]="$author" | |
| ;; | |
| performance) | |
| performance["$clean_msg"]="$author" | |
| ;; | |
| esac | |
| done < <(git log --oneline --no-merges $RANGE) | |
| # Generate English release notes | |
| echo "V2er ${{ needs.prepare.outputs.version }}" > whatsnew/whatsnew-en-US | |
| echo "" >> whatsnew/whatsnew-en-US | |
| if [ ${#features[@]} -gt 0 ]; then | |
| echo "🚀 New Features:" >> whatsnew/whatsnew-en-US | |
| for msg in "${!features[@]}"; do | |
| author="${features[$msg]}" | |
| if [ -n "$author" ]; then | |
| echo "• $msg (by $author)" >> whatsnew/whatsnew-en-US | |
| else | |
| echo "• $msg" >> whatsnew/whatsnew-en-US | |
| fi | |
| done | |
| echo "" >> whatsnew/whatsnew-en-US | |
| fi | |
| if [ ${#bugs[@]} -gt 0 ]; then | |
| echo "🐛 Bug Fixes:" >> whatsnew/whatsnew-en-US | |
| for msg in "${!bugs[@]}"; do | |
| author="${bugs[$msg]}" | |
| if [ -n "$author" ]; then | |
| echo "• $msg (by $author)" >> whatsnew/whatsnew-en-US | |
| else | |
| echo "• $msg" >> whatsnew/whatsnew-en-US | |
| fi | |
| done | |
| echo "" >> whatsnew/whatsnew-en-US | |
| fi | |
| if [ ${#improvements[@]} -gt 0 ]; then | |
| echo "💪 Improvements:" >> whatsnew/whatsnew-en-US | |
| for msg in "${!improvements[@]}"; do | |
| author="${improvements[$msg]}" | |
| if [ -n "$author" ]; then | |
| echo "• $msg (by $author)" >> whatsnew/whatsnew-en-US | |
| else | |
| echo "• $msg" >> whatsnew/whatsnew-en-US | |
| fi | |
| done | |
| echo "" >> whatsnew/whatsnew-en-US | |
| fi | |
| if [ ${#performance[@]} -gt 0 ]; then | |
| echo "⚡ Performance:" >> whatsnew/whatsnew-en-US | |
| for msg in "${!performance[@]}"; do | |
| author="${performance[$msg]}" | |
| if [ -n "$author" ]; then | |
| echo "• $msg (by $author)" >> whatsnew/whatsnew-en-US | |
| else | |
| echo "• $msg" >> whatsnew/whatsnew-en-US | |
| fi | |
| done | |
| echo "" >> whatsnew/whatsnew-en-US | |
| fi | |
| # Add contributors section if there are any | |
| if [ ${#contributors[@]} -gt 0 ]; then | |
| echo "👥 Contributors: ${!contributors[@]}" >> whatsnew/whatsnew-en-US | |
| echo "" >> whatsnew/whatsnew-en-US | |
| fi | |
| echo "Thank you for using V2er! Please report any issues on GitHub." >> whatsnew/whatsnew-en-US | |
| # Generate Chinese release notes | |
| echo "V2er ${{ needs.prepare.outputs.version }}" > whatsnew/whatsnew-zh-CN | |
| echo "" >> whatsnew/whatsnew-zh-CN | |
| if [ ${#features[@]} -gt 0 ]; then | |
| echo "🚀 新功能:" >> whatsnew/whatsnew-zh-CN | |
| for msg in "${!features[@]}"; do | |
| author="${features[$msg]}" | |
| if [ -n "$author" ]; then | |
| echo "• $msg (贡献者 $author)" >> whatsnew/whatsnew-zh-CN | |
| else | |
| echo "• $msg" >> whatsnew/whatsnew-zh-CN | |
| fi | |
| done | |
| echo "" >> whatsnew/whatsnew-zh-CN | |
| fi | |
| if [ ${#bugs[@]} -gt 0 ]; then | |
| echo "🐛 问题修复:" >> whatsnew/whatsnew-zh-CN | |
| for msg in "${!bugs[@]}"; do | |
| author="${bugs[$msg]}" | |
| if [ -n "$author" ]; then | |
| echo "• $msg (贡献者 $author)" >> whatsnew/whatsnew-zh-CN | |
| else | |
| echo "• $msg" >> whatsnew/whatsnew-zh-CN | |
| fi | |
| done | |
| echo "" >> whatsnew/whatsnew-zh-CN | |
| fi | |
| if [ ${#improvements[@]} -gt 0 ]; then | |
| echo "💪 改进优化:" >> whatsnew/whatsnew-zh-CN | |
| for msg in "${!improvements[@]}"; do | |
| author="${improvements[$msg]}" | |
| if [ -n "$author" ]; then | |
| echo "• $msg (贡献者 $author)" >> whatsnew/whatsnew-zh-CN | |
| else | |
| echo "• $msg" >> whatsnew/whatsnew-zh-CN | |
| fi | |
| done | |
| echo "" >> whatsnew/whatsnew-zh-CN | |
| fi | |
| if [ ${#performance[@]} -gt 0 ]; then | |
| echo "⚡ 性能优化:" >> whatsnew/whatsnew-zh-CN | |
| for msg in "${!performance[@]}"; do | |
| author="${performance[$msg]}" | |
| if [ -n "$author" ]; then | |
| echo "• $msg (贡献者 $author)" >> whatsnew/whatsnew-zh-CN | |
| else | |
| echo "• $msg" >> whatsnew/whatsnew-zh-CN | |
| fi | |
| done | |
| echo "" >> whatsnew/whatsnew-zh-CN | |
| fi | |
| # Add contributors section if there are any | |
| if [ ${#contributors[@]} -gt 0 ]; then | |
| echo "👥 贡献者:${!contributors[@]}" >> whatsnew/whatsnew-zh-CN | |
| echo "" >> whatsnew/whatsnew-zh-CN | |
| fi | |
| echo "感谢您使用 V2er!如遇问题请在 GitHub 上反馈。" >> 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.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 }} | |
| debugSymbols: ${{ steps.find-files.outputs.symbols_path }} | |
| mappingFile: ${{ steps.find-files.outputs.mapping_path }} | |
| whatsNewDirectory: whatsnew/ | |
| 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 }} | |
| mappingFile: ${{ steps.find-files.outputs.mapping_path }} | |
| whatsNewDirectory: whatsnew/ | |
| - 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 | |
| if [ "${{ steps.release-config.outputs.track }}" = "beta" ]; then | |
| echo "### Beta Testing" >> $GITHUB_STEP_SUMMARY | |
| echo "This release is now available for beta testing on Google Play." >> $GITHUB_STEP_SUMMARY | |
| echo "Beta testers can install it from: [Google Play Beta](https://play.google.com/store/apps/details?id=me.ghui.v2er)" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "[View in Play Console](https://play.google.com/console/u/0/app/me.ghui.v2er)" >> $GITHUB_STEP_SUMMARY | |
| download-signed-apk: | |
| name: Download Google Play Signed APK | |
| needs: [prepare, play-store-upload] | |
| if: ${{ (needs.prepare.outputs.should_release == 'true' || github.event_name == 'workflow_dispatch') && vars.ENABLE_PLAY_STORE_UPLOAD == 'true' && vars.ENABLE_SIGNING == 'true' }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.x' | |
| - name: Install dependencies | |
| run: | | |
| pip install google-api-python-client google-auth-httplib2 google-auth-oauthlib requests | |
| - name: Check and wait for Google Play processing | |
| env: | |
| PLAY_STORE_SERVICE_ACCOUNT_JSON: ${{ secrets.PLAY_STORE_SERVICE_ACCOUNT_JSON }} | |
| run: | | |
| VERSION_CODE="${{ needs.prepare.outputs.version_code }}" | |
| PACKAGE_NAME="me.ghui.v2er" | |
| # Create Python script to check if APK exists | |
| cat > check_apk_exists.py << 'EOF' | |
| import json | |
| import os | |
| import sys | |
| import time | |
| from google.oauth2 import service_account | |
| from googleapiclient.discovery import build | |
| def check_apk_exists(): | |
| try: | |
| # Load service account credentials | |
| service_account_info = json.loads(os.environ['PLAY_STORE_SERVICE_ACCOUNT_JSON']) | |
| credentials = service_account.Credentials.from_service_account_info( | |
| service_account_info, | |
| scopes=['https://www.googleapis.com/auth/androidpublisher'] | |
| ) | |
| # Build the service | |
| service = build('androidpublisher', 'v3', credentials=credentials) | |
| package_name = os.environ['PACKAGE_NAME'] | |
| version_code = int(os.environ['VERSION_CODE']) | |
| print(f"Checking if signed APK exists for {package_name} version {version_code}") | |
| # Try to get the generated APKs list | |
| result = service.generatedapks().list( | |
| packageName=package_name, | |
| versionCode=version_code | |
| ).execute() | |
| if 'generatedApks' not in result or not result['generatedApks']: | |
| print(f"No generated APKs found for version {version_code}") | |
| return False | |
| print(f"Found {len(result['generatedApks'])} generated APK groups") | |
| # Check if we can find a universal APK | |
| for apk in result['generatedApks']: | |
| if 'generatedUniversalApk' in apk: | |
| universal_apk = apk['generatedUniversalApk'] | |
| download_id = universal_apk.get('downloadId') | |
| if download_id: | |
| print(f"✅ Universal APK found with downloadId: {download_id}") | |
| return True | |
| print("❌ No universal APK found") | |
| return False | |
| except Exception as e: | |
| print(f"Error checking APK: {str(e)}") | |
| return False | |
| if __name__ == "__main__": | |
| exists = check_apk_exists() | |
| sys.exit(0 if exists else 1) | |
| EOF | |
| # Set environment variables for the script | |
| export PACKAGE_NAME="$PACKAGE_NAME" | |
| export VERSION_CODE="$VERSION_CODE" | |
| # Check if APK already exists | |
| echo "Checking if Google Play signed APK is ready..." | |
| if python3 check_apk_exists.py; then | |
| echo "✅ APK is already available, skipping wait" | |
| else | |
| echo "⏳ APK not ready yet, waiting for Google Play to process..." | |
| # Smart waiting with periodic checks | |
| MAX_WAIT=600 # Maximum 10 minutes | |
| CHECK_INTERVAL=30 # Check every 30 seconds | |
| elapsed=0 | |
| while [ $elapsed -lt $MAX_WAIT ]; do | |
| sleep $CHECK_INTERVAL | |
| elapsed=$((elapsed + CHECK_INTERVAL)) | |
| echo "⏱️ Waited ${elapsed}s, checking again..." | |
| if python3 check_apk_exists.py; then | |
| echo "✅ APK is now available after ${elapsed}s" | |
| break | |
| fi | |
| if [ $elapsed -ge $MAX_WAIT ]; then | |
| echo "⚠️ Maximum wait time (${MAX_WAIT}s) reached" | |
| echo "APK may still be processing, will attempt download anyway" | |
| fi | |
| done | |
| fi | |
| # AAB artifact not needed for Google Play signed APK download | |
| - name: Download Google Play Signed APK | |
| id: download-apk | |
| env: | |
| PLAY_STORE_SERVICE_ACCOUNT_JSON: ${{ secrets.PLAY_STORE_SERVICE_ACCOUNT_JSON }} | |
| run: | | |
| VERSION_NAME="${{ needs.prepare.outputs.version }}" | |
| VERSION_CODE="${{ needs.prepare.outputs.version_code }}" | |
| PACKAGE_NAME="me.ghui.v2er" | |
| # Create Python script to download signed universal APK | |
| cat > download_signed_apk.py << 'EOF' | |
| import json | |
| import os | |
| import sys | |
| import requests | |
| from google.oauth2 import service_account | |
| from googleapiclient.discovery import build | |
| def download_signed_apk(): | |
| try: | |
| # Load service account credentials | |
| service_account_info = json.loads(os.environ['PLAY_STORE_SERVICE_ACCOUNT_JSON']) | |
| credentials = service_account.Credentials.from_service_account_info( | |
| service_account_info, | |
| scopes=['https://www.googleapis.com/auth/androidpublisher'] | |
| ) | |
| # Build the service | |
| service = build('androidpublisher', 'v3', credentials=credentials) | |
| package_name = os.environ['PACKAGE_NAME'] | |
| version_code = int(os.environ['VERSION_CODE']) | |
| print(f"Attempting to download signed APK for {package_name} version {version_code}") | |
| # Step 1: Get the generated APKs list to find downloadId | |
| print("Getting generated APKs list...") | |
| result = service.generatedapks().list( | |
| packageName=package_name, | |
| versionCode=version_code | |
| ).execute() | |
| if 'generatedApks' not in result or not result['generatedApks']: | |
| print("No generated APKs found. App may not be processed yet by Google Play.") | |
| return False | |
| print(f"Found {len(result['generatedApks'])} generated APKs") | |
| # Debug: Print all APK structures | |
| for i, apk in enumerate(result['generatedApks']): | |
| print(f"APK {i} structure:") | |
| for key, value in apk.items(): | |
| print(f" {key}: {value}") | |
| print() | |
| # Find universal APK using the correct API structure | |
| download_id = None | |
| universal_apk = None | |
| # First, try to find a universal APK in generatedUniversalApk | |
| for apk in result['generatedApks']: | |
| if 'generatedUniversalApk' in apk: | |
| universal_apk = apk['generatedUniversalApk'] | |
| download_id = universal_apk.get('downloadId') | |
| print(f"Found universal APK: {universal_apk}") | |
| break | |
| if not download_id: | |
| print("No universal APK found") | |
| print("Available APK structure:") | |
| print(json.dumps(result['generatedApks'], indent=2)) | |
| return False | |
| print(f"Found universal APK with downloadId: {download_id}") | |
| # Step 2: Download the APK using the downloadId | |
| print("Downloading APK binary...") | |
| # Use alt=media to get the actual binary content instead of metadata | |
| download_request = service.generatedapks().download( | |
| packageName=package_name, | |
| versionCode=version_code, | |
| downloadId=download_id | |
| ) | |
| # Add alt=media parameter correctly (URL already has query params, so use &) | |
| if '?' in download_request.uri: | |
| download_request.uri += '&alt=media' | |
| else: | |
| download_request.uri += '?alt=media' | |
| output_filename = f"v2er-{os.environ['VERSION_NAME']}_google_play_signed.apk" | |
| # Use media download with googleapiclient.http to handle binary content | |
| import io | |
| from googleapiclient.http import MediaIoBaseDownload | |
| file_io = io.BytesIO() | |
| downloader = MediaIoBaseDownload(file_io, download_request) | |
| done = False | |
| while done is False: | |
| status, done = downloader.next_chunk() | |
| if status: | |
| print(f"Download progress: {int(status.progress() * 100)}%") | |
| # Write to file | |
| with open(output_filename, 'wb') as f: | |
| f.write(file_io.getvalue()) | |
| print(f"Successfully downloaded: {output_filename}") | |
| print(f"apk_path={output_filename}") | |
| return True | |
| except Exception as e: | |
| print(f"Error downloading signed APK: {str(e)}") | |
| print("This may be because:") | |
| print("1. The app hasn't been processed by Google Play yet") | |
| print("2. The version hasn't been released to any track") | |
| print("3. API permissions are insufficient") | |
| return False | |
| if __name__ == "__main__": | |
| success = download_signed_apk() | |
| sys.exit(0 if success else 1) | |
| EOF | |
| # Set environment variables for the script | |
| export PACKAGE_NAME="$PACKAGE_NAME" | |
| export VERSION_CODE="$VERSION_CODE" | |
| export VERSION_NAME="$VERSION_NAME" | |
| # Run the download script | |
| echo "Attempting to download Google Play signed APK..." | |
| if python3 download_signed_apk.py > download_output.txt 2>&1; then | |
| echo "Successfully downloaded Google Play signed APK" | |
| cat download_output.txt | |
| # Extract the APK path from output | |
| APK_PATH=$(grep "apk_path=" download_output.txt | cut -d'=' -f2) | |
| if [ -f "$APK_PATH" ]; then | |
| echo "apk_path=$APK_PATH" >> $GITHUB_OUTPUT | |
| echo "found=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "APK file not found after download" | |
| echo "found=false" >> $GITHUB_OUTPUT | |
| fi | |
| else | |
| echo "Failed to download Google Play signed APK" | |
| cat download_output.txt | |
| echo "found=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Upload signed APK to GitHub Release | |
| if: steps.download-apk.outputs.found == 'true' | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: ${{ needs.prepare.outputs.version }} | |
| files: | | |
| ${{ steps.download-apk.outputs.apk_path }} | |
| fail_on_unmatched_files: false | |
| - name: Summary | |
| if: always() | |
| run: | | |
| echo "## Google Play Signed APK :package:" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| if [ "${{ steps.download-apk.outputs.found }}" = "true" ]; then | |
| echo "✅ **Universal APK generated successfully**" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Version**: ${{ needs.prepare.outputs.version }}" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Version Code**: ${{ needs.prepare.outputs.version_code }}" >> $GITHUB_STEP_SUMMARY | |
| echo "- **File**: v2er-${{ needs.prepare.outputs.version }}_google_play_signed.apk" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### Notes" >> $GITHUB_STEP_SUMMARY | |
| echo "- This APK is generated from the AAB uploaded to Google Play" >> $GITHUB_STEP_SUMMARY | |
| echo "- When installed from Play Store, it will use Google Play's signing certificate" >> $GITHUB_STEP_SUMMARY | |
| echo "- The APK has been uploaded to the GitHub Release" >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "⚠️ **Google Play signed APK download failed**" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "This may be because:" >> $GITHUB_STEP_SUMMARY | |
| echo "- Google Play is still processing the upload" >> $GITHUB_STEP_SUMMARY | |
| echo "- The version hasn't been released to any track yet" >> $GITHUB_STEP_SUMMARY | |
| echo "- API permissions are insufficient" >> $GITHUB_STEP_SUMMARY | |
| fi |