# Tag-driven release. Builds standalone rather than consuming ci.yml # artifacts: a tag run has no upstream run to pull from, and a release # should be reproducible from the tagged tree alone. name: android / release on: push: tags: ['v*.*.*'] permissions: contents: write concurrency: group: android-release-${{ github.ref }} cancel-in-progress: false jobs: release: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 with: persist-credentials: false # Full history + tags for the versionCode guard below. fetch-depth: 0 # Play rejects an upload whose versionCode is not higher than the last # one, but only after the whole build has run — fail fast instead. # Compares this tag's versionCode against the previous v*.*.* tag, # reading the wrapper-era path for tags that predate the native app. - name: Guard against an unbumped versionCode run: | set -eu current=$(grep -oP 'versionCode = \K\d+' android/app/build.gradle.kts) echo "versionCode at ${GITHUB_REF_NAME}: $current" prev_tag=$(git tag --list 'v*.*.*' --sort=-v:refname | grep -Fvx "${GITHUB_REF_NAME}" | head -n1 || true) if [ -z "$prev_tag" ]; then echo "No previous release tag; skipping comparison." exit 0 fi prev=$(git show "$prev_tag:android/app/build.gradle.kts" 2>/dev/null | grep -oP 'versionCode = \K\d+' || true) if [ -z "$prev" ]; then # Wrapper-era layout (Capacitor project under android/android/). prev=$(git show "$prev_tag:android/android/app/build.gradle" 2>/dev/null | grep -oP 'versionCode \K\d+' || true) fi if [ -z "$prev" ]; then echo "::warning::Could not read versionCode at $prev_tag; skipping comparison." exit 0 fi echo "versionCode at $prev_tag: $prev" if [ "$current" -le "$prev" ]; then echo "::error::versionCode $current at ${GITHUB_REF_NAME} must be greater than $prev at $prev_tag. Bump it in android/app/build.gradle.kts before tagging." exit 1 fi - uses: ./.github/actions/setup-android # ci.yml does not run on tags, so this is the only gate before signing. - name: Lint and test run: ./gradlew :app:lint :app:test working-directory: android - name: Decode keystore env: KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }} run: printf '%s' "$KEYSTORE_BASE64" | base64 --decode > "$GITHUB_WORKSPACE/keystore.p12" - name: Build signed AAB + APK working-directory: android env: LOTO_KEYSTORE_PATH: ${{ github.workspace }}/keystore.p12 LOTO_KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }} LOTO_KEY_ALIAS: ${{ secrets.KEY_ALIAS }} LOTO_KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }} run: ./gradlew :app:bundleRelease :app:assembleRelease - name: Upload to GitHub Release uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2 with: files: | android/app/build/outputs/apk/release/*.apk android/app/build/outputs/bundle/release/*.aab # Auto-publish to Google Play (gated on the service-account secret). # Skips silently if PLAY_SERVICE_ACCOUNT_JSON is not configured, so # tagging a release before Play setup still produces a GH Release. # Uploads to "alpha", the closed-testing track. Internal testing is a # separate track and does not count toward the 12-tester requirement # for production access, so releases have to land here to reach the # testers. An unknown track name fails the step with the list of # valid tracks rather than publishing somewhere unintended. - name: Check Play Store config id: play env: PLAY_SERVICE_ACCOUNT_JSON: ${{ secrets.PLAY_SERVICE_ACCOUNT_JSON }} run: | if [ -n "$PLAY_SERVICE_ACCOUNT_JSON" ]; then echo "configured=true" >> "$GITHUB_OUTPUT" else echo "configured=false" >> "$GITHUB_OUTPUT" echo "::notice::PLAY_SERVICE_ACCOUNT_JSON not set; skipping Play Store upload." fi - name: Upload to Google Play (closed testing track) if: steps.play.outputs.configured == 'true' uses: r0adkll/upload-google-play@e738b9dd8f2476ea806d921b64aacd24f34515a5 # v1.1.5 with: serviceAccountJsonPlainText: ${{ secrets.PLAY_SERVICE_ACCOUNT_JSON }} packageName: com.miti99.loto releaseFiles: android/app/build/outputs/bundle/release/*.aab tracks: alpha status: completed # versionCode is bumped manually in android/app/build.gradle.kts # before tagging; the guard step above catches a forgotten bump.