Skip to content

Release Android

Release Android #9

name: Release Android
# Required secrets:
# ANDROID_SIGNING_KEY_BASE64 base64 of upload-keystore.jks
# ANDROID_KEY_STORE_PASSWORD
# ANDROID_KEY_ALIAS
# ANDROID_KEY_PASSWORD
# PLAY_SERVICE_ACCOUNT_JSON Play service-account JSON (raw, not base64). Optional; if absent, Play upload is skipped.
on:
workflow_dispatch:
inputs:
version_name:
description: "Version name (e.g. 1.0.0)"
required: true
type: string
version_code:
description: "Version code (integer, defaults to a Unix timestamp)"
required: false
type: string
jobs:
release-android:
runs-on: ubuntu-latest
timeout-minutes: 60
permissions:
contents: write
env:
PACKAGE_NAME: com.muxy.app
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Validate inputs
id: validate
run: |
VERSION_NAME="${{ inputs.version_name }}"
if ! [[ "$VERSION_NAME" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Error: version_name must be X.Y.Z"
exit 1
fi
if [[ -n "${{ inputs.version_code }}" ]]; then
VERSION_CODE="${{ inputs.version_code }}"
else
VERSION_CODE=$(date +%s)
fi
if ! [[ "$VERSION_CODE" =~ ^[0-9]+$ ]]; then
echo "Error: version_code must be numeric"
exit 1
fi
MAJOR="${VERSION_NAME%%.*}"
if [[ "$MAJOR" -ge 1 ]]; then
TRACK="production"
else
TRACK="alpha"
fi
echo "Resolved track: $TRACK (versionName=$VERSION_NAME)"
echo "version_name=$VERSION_NAME" >> "$GITHUB_OUTPUT"
echo "version_code=$VERSION_CODE" >> "$GITHUB_OUTPUT"
echo "track=$TRACK" >> "$GITHUB_OUTPUT"
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '17'
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v4
- name: Install dependencies
run: npm ci
- name: Set version in app.json
env:
VERSION_NAME: ${{ steps.validate.outputs.version_name }}
VERSION_CODE: ${{ steps.validate.outputs.version_code }}
run: |
node -e '
const fs = require("fs");
const path = "app.json";
const cfg = JSON.parse(fs.readFileSync(path, "utf8"));
cfg.expo.version = process.env.VERSION_NAME;
cfg.expo.android = cfg.expo.android || {};
cfg.expo.android.versionCode = parseInt(process.env.VERSION_CODE, 10);
fs.writeFileSync(path, JSON.stringify(cfg, null, 2) + "\n");
'
- name: Expo prebuild (Android)
run: npx expo prebuild --platform android --no-install --clean --non-interactive
- name: Make gradlew executable
working-directory: android
run: chmod +x ./gradlew
- name: Decode signing key
env:
SIGNING_KEY_BASE64: ${{ secrets.ANDROID_SIGNING_KEY_BASE64 }}
run: |
if [[ -z "$SIGNING_KEY_BASE64" ]]; then
echo "Error: ANDROID_SIGNING_KEY_BASE64 secret is required"
exit 1
fi
echo "$SIGNING_KEY_BASE64" | base64 --decode > android/app/upload-keystore.jks
- name: Patch android/app/build.gradle with release signing config
run: node scripts/lib/patch_signing.js android/app/build.gradle
- name: Build signed Release AAB
working-directory: android
env:
ANDROID_KEY_STORE_PASSWORD: ${{ secrets.ANDROID_KEY_STORE_PASSWORD }}
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
run: ./gradlew bundleRelease -PreactNativeArchitectures=arm64-v8a,armeabi-v7a
- name: Upload AAB artifact
uses: actions/upload-artifact@v4
with:
name: android-release-${{ steps.validate.outputs.version_name }}-${{ steps.validate.outputs.version_code }}
path: android/app/build/outputs/bundle/release/*.aab
if-no-files-found: error
- name: Check Play upload secret
id: play_secret
env:
PLAY_JSON: ${{ secrets.PLAY_SERVICE_ACCOUNT_JSON }}
run: |
if [[ -n "$PLAY_JSON" ]]; then
echo "present=true" >> "$GITHUB_OUTPUT"
else
echo "present=false" >> "$GITHUB_OUTPUT"
echo "PLAY_SERVICE_ACCOUNT_JSON not set; skipping Play upload."
fi
- name: Upload to Play Store
if: steps.play_secret.outputs.present == 'true'
uses: r0adkll/upload-google-play@v1
with:
serviceAccountJsonPlainText: ${{ secrets.PLAY_SERVICE_ACCOUNT_JSON }}
packageName: com.muxy.app
releaseFiles: android/app/build/outputs/bundle/release/*.aab
track: ${{ steps.validate.outputs.track }}
status: draft
changesNotSentForReview: ${{ steps.validate.outputs.track == 'production' }}
- name: Create GitHub Release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
PREV_TAG=$(gh release list \
--repo "${{ github.repository }}" \
--limit 100 \
--json tagName \
-q '[.[] | select(.tagName | startswith("android-v"))][0].tagName')
if [[ -n "$PREV_TAG" ]]; then
RANGE="$PREV_TAG..HEAD"
else
RANGE="HEAD"
fi
git log --no-merges -i --grep='^android:' --format='%s' "$RANGE" \
| sed -E 's/^[Aa]ndroid:[[:space:]]*/- /' \
> notes.md
if [[ ! -s notes.md ]]; then
echo "- No Android-facing changes since ${PREV_TAG:-initial release}" > notes.md
fi
gh release create "android-v${{ steps.validate.outputs.version_name }}" \
--repo "${{ github.repository }}" \
--title "Android v${{ steps.validate.outputs.version_name }}" \
--notes-file notes.md \
--latest=false \
--target "${{ github.sha }}"