Bump android API version, fix missing app icon and background not bla… #75
Workflow file for this run
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: | |
| tags: | |
| - "[0-9]+.[0-9]+.[0-9]+" | |
| jobs: | |
| # ============================================ | |
| # Find and wait for watchOS build | |
| # ============================================ | |
| find-and-wait: | |
| name: Find watchOS Build | |
| runs-on: ubuntu-latest | |
| outputs: | |
| run_id: ${{ steps.find.outputs.run_id }} | |
| steps: | |
| - name: Find or wait for main build | |
| id: find | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| COMMIT_SHA="${{ github.sha }}" | |
| MAX_WAIT=3600 | |
| POLL_INTERVAL=30 | |
| WAITED=0 | |
| while [ $WAITED -lt $MAX_WAIT ]; do | |
| RESULT=$(gh api repos/${{ github.repository }}/actions/workflows/build.yml/runs \ | |
| --jq ".workflow_runs[] | select(.head_sha==\"$COMMIT_SHA\") | {id, status, conclusion}" \ | |
| | head -1) | |
| if [ -z "$RESULT" ]; then | |
| echo "::error::No build found for commit $COMMIT_SHA" | |
| exit 1 | |
| fi | |
| STATUS=$(echo "$RESULT" | jq -r '.status') | |
| CONCLUSION=$(echo "$RESULT" | jq -r '.conclusion') | |
| RUN_ID=$(echo "$RESULT" | jq -r '.id') | |
| if [ "$STATUS" = "completed" ]; then | |
| if [ "$CONCLUSION" = "success" ]; then | |
| echo "run_id=$RUN_ID" >> $GITHUB_OUTPUT | |
| echo "Found successful build: $RUN_ID" | |
| exit 0 | |
| else | |
| echo "::error::Build $RUN_ID failed: $CONCLUSION" | |
| exit 1 | |
| fi | |
| fi | |
| echo "Build $RUN_ID still running ($STATUS). Waiting ${POLL_INTERVAL}s..." | |
| sleep $POLL_INTERVAL | |
| WAITED=$((WAITED + POLL_INTERVAL)) | |
| done | |
| echo "::error::Timeout waiting for build" | |
| exit 1 | |
| # ============================================ | |
| # Build Garmin .iq package | |
| # ============================================ | |
| build-garmin: | |
| name: Build Garmin App | |
| runs-on: ubuntu-latest | |
| container: | |
| image: ghcr.io/matco/connectiq-tester:latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Check for Garmin credentials | |
| id: check_creds | |
| env: | |
| GARMIN_DEVELOPER_KEY: ${{ secrets.GARMIN_DEVELOPER_KEY }} | |
| run: | | |
| if [ -z "$GARMIN_DEVELOPER_KEY" ]; then | |
| echo "has_creds=false" >> $GITHUB_OUTPUT | |
| echo "::notice::GARMIN_DEVELOPER_KEY not configured - skipping Garmin build" | |
| else | |
| echo "has_creds=true" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Install developer key | |
| if: steps.check_creds.outputs.has_creds == 'true' | |
| env: | |
| GARMIN_DEVELOPER_KEY: ${{ secrets.GARMIN_DEVELOPER_KEY }} | |
| run: | | |
| mkdir -p ~/.Garmin | |
| echo "$GARMIN_DEVELOPER_KEY" | base64 -d > ~/.Garmin/developer_key.der | |
| - name: Generate Garmin secrets | |
| if: steps.check_creds.outputs.has_creds == 'true' | |
| env: | |
| TRAINTIME_API_KEY: ${{ secrets.TRAINTIME_API_KEY }} | |
| run: | | |
| cat > garmin/TrainTime/source/Secrets.mc <<EOF | |
| module Secrets { | |
| const API_KEY = "$TRAINTIME_API_KEY"; | |
| } | |
| EOF | |
| - name: Build .iq package | |
| if: steps.check_creds.outputs.has_creds == 'true' | |
| working-directory: garmin/TrainTime | |
| run: | | |
| monkeyc -e -f monkey.jungle -o ../TrainTime.iq -y ~/.Garmin/developer_key.der -r | |
| echo "Built TrainTime.iq ($(stat -c%s ../TrainTime.iq) bytes)" | |
| - name: Upload Garmin artifact | |
| if: steps.check_creds.outputs.has_creds == 'true' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: garmin-iq | |
| path: garmin/TrainTime.iq | |
| retention-days: 30 | |
| # ============================================ | |
| # Build Android .aab package | |
| # ============================================ | |
| build-android: | |
| name: Build Android App | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Check for Android signing credentials | |
| id: check_creds | |
| env: | |
| ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} | |
| run: | | |
| if [ -z "$ANDROID_KEYSTORE_BASE64" ]; then | |
| echo "has_creds=false" >> $GITHUB_OUTPUT | |
| echo "::notice::ANDROID_KEYSTORE_BASE64 not configured - skipping Android build" | |
| else | |
| echo "has_creds=true" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Checkout | |
| if: steps.check_creds.outputs.has_creds == 'true' | |
| uses: actions/checkout@v4 | |
| - name: Verify tag matches versionName | |
| if: steps.check_creds.outputs.has_creds == 'true' | |
| run: | | |
| VN=$(grep 'versionName = ' android/app/build.gradle.kts | sed 's/.*"\(.*\)".*/\1/') | |
| VN_WEAR=$(grep 'versionName = ' android/wear/build.gradle.kts | sed 's/.*"\(.*\)".*/\1/') | |
| echo "Tag: ${{ github.ref_name }} | app versionName: $VN | wear versionName: $VN_WEAR" | |
| if [ "$VN" != "${{ github.ref_name }}" ]; then | |
| echo "::error::Tag ${{ github.ref_name }} does not match versionName $VN. Bump versionName (and versionCode) in android/app/build.gradle.kts before tagging." | |
| exit 1 | |
| fi | |
| if [ "$VN_WEAR" != "${{ github.ref_name }}" ]; then | |
| echo "::error::Tag ${{ github.ref_name }} does not match wear versionName $VN_WEAR. Bump versionName (and versionCode) in android/wear/build.gradle.kts before tagging." | |
| exit 1 | |
| fi | |
| - name: Setup Java | |
| if: steps.check_creds.outputs.has_creds == 'true' | |
| uses: actions/setup-java@v4 | |
| with: | |
| distribution: temurin | |
| java-version: "17" | |
| - name: Setup Gradle | |
| if: steps.check_creds.outputs.has_creds == 'true' | |
| uses: gradle/actions/setup-gradle@v4 | |
| - name: Decode signing keystore | |
| if: steps.check_creds.outputs.has_creds == 'true' | |
| working-directory: android | |
| env: | |
| ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} | |
| run: echo "$ANDROID_KEYSTORE_BASE64" | base64 -d > upload-keystore.jks | |
| - name: Build release AAB | |
| if: steps.check_creds.outputs.has_creds == 'true' | |
| working-directory: android | |
| env: | |
| TRAINTIME_API_KEY: ${{ secrets.TRAINTIME_API_KEY }} | |
| KEYSTORE_FILE: upload-keystore.jks | |
| KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} | |
| KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} | |
| KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} | |
| run: | | |
| ./gradlew :app:bundleRelease :wear:bundleRelease | |
| mkdir -p dist | |
| cp app/build/outputs/bundle/release/app-release.aab dist/ | |
| cp wear/build/outputs/bundle/release/wear-release.aab dist/ | |
| echo "Built app-release.aab ($(stat -c%s dist/app-release.aab) bytes), wear-release.aab ($(stat -c%s dist/wear-release.aab) bytes)" | |
| - name: Upload Android artifact | |
| if: steps.check_creds.outputs.has_creds == 'true' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: android-aab | |
| path: android/dist/*.aab | |
| retention-days: 30 | |
| # ============================================ | |
| # Create Release | |
| # ============================================ | |
| release: | |
| name: Create Release | |
| needs: [find-and-wait, build-garmin, build-android] | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| outputs: | |
| version: ${{ steps.version.outputs.version }} | |
| ipa_path: ${{ steps.prepare.outputs.ipa_path }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Get version from tag | |
| id: version | |
| run: echo "version=${{ github.ref_name }}" >> $GITHUB_OUTPUT | |
| - name: Download iOS/watchOS artifacts from main build | |
| uses: actions/download-artifact@v4 | |
| with: | |
| run-id: ${{ needs.find-and-wait.outputs.run_id }} | |
| github-token: ${{ github.token }} | |
| path: artifacts | |
| - name: Download Garmin artifact | |
| uses: actions/download-artifact@v4 | |
| continue-on-error: true | |
| with: | |
| name: garmin-iq | |
| path: artifacts/garmin | |
| - name: Download Android artifact | |
| uses: actions/download-artifact@v4 | |
| continue-on-error: true | |
| with: | |
| name: android-aab | |
| path: artifacts/android | |
| - name: Prepare release artifacts | |
| id: prepare | |
| run: | | |
| mkdir -p release | |
| VERSION="${{ steps.version.outputs.version }}" | |
| echo "=== Downloaded artifacts ===" | |
| find artifacts -type f | |
| echo "=============================" | |
| IPA=$(find artifacts -name "*.ipa" | head -1) | |
| if [ -n "$IPA" ]; then | |
| IPA_DEST="release/traintime-${VERSION}.ipa" | |
| cp "$IPA" "$IPA_DEST" | |
| echo "ipa_path=$IPA_DEST" >> $GITHUB_OUTPUT | |
| echo "IPA: traintime-${VERSION}.ipa" | |
| else | |
| echo "::warning::No IPA found in artifacts" | |
| fi | |
| IQ=$(find artifacts -name "*.iq" | head -1) | |
| if [ -n "$IQ" ]; then | |
| cp "$IQ" "release/TrainTime-${VERSION}.iq" | |
| echo "Garmin: TrainTime-${VERSION}.iq" | |
| else | |
| echo "::notice::No Garmin .iq found" | |
| fi | |
| AAB=$(find artifacts -name "app-release.aab" | head -1) | |
| if [ -n "$AAB" ]; then | |
| cp "$AAB" "release/traintime-${VERSION}.aab" | |
| echo "Android: traintime-${VERSION}.aab" | |
| else | |
| echo "::notice::No Android .aab found" | |
| fi | |
| WEAR_AAB=$(find artifacts -name "wear-release.aab" | head -1) | |
| if [ -n "$WEAR_AAB" ]; then | |
| cp "$WEAR_AAB" "release/traintime-wear-${VERSION}.aab" | |
| echo "Wear OS: traintime-wear-${VERSION}.aab" | |
| else | |
| echo "::notice::No Wear OS .aab found" | |
| fi | |
| echo "" | |
| echo "=== Release artifacts ===" | |
| ls -la release/ | |
| - name: Create GitHub Release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| name: TrainTime v${{ steps.version.outputs.version }} | |
| body: | | |
| ## TrainTime v${{ steps.version.outputs.version }} | |
| ## Downloads | |
| - **iOS IPA**: For TestFlight / App Store (includes iOS app, watchOS companion, widget) | |
| - **Garmin .iq**: Upload to [Connect IQ Store](https://apps.garmin.com/developer/dashboard) | |
| - **Android .aab**: Signed release bundle for the [Play Console](https://play.google.com/console) | |
| - **Wear OS .aab**: Signed watch bundle, uploaded to the same Play release | |
| --- | |
| Built from commit: ${{ github.sha }} | |
| files: release/* | |
| draft: false | |
| prerelease: false | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Upload IPA for deployment | |
| if: steps.prepare.outputs.ipa_path != '' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: release-ipa | |
| path: ${{ steps.prepare.outputs.ipa_path }} | |
| retention-days: 1 | |
| # ============================================ | |
| # Deploy to TestFlight | |
| # ============================================ | |
| deploy-testflight: | |
| name: Deploy to TestFlight | |
| needs: release | |
| runs-on: macos-26 | |
| if: needs.release.outputs.ipa_path != '' | |
| steps: | |
| - name: Check for TestFlight credentials | |
| id: check_creds | |
| env: | |
| ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }} | |
| ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }} | |
| ASC_API_KEY: ${{ secrets.ASC_API_KEY }} | |
| run: | | |
| if [ -z "$ASC_KEY_ID" ] || [ -z "$ASC_ISSUER_ID" ] || [ -z "$ASC_API_KEY" ]; then | |
| echo "skip=true" >> $GITHUB_OUTPUT | |
| echo "::notice::Skipping TestFlight - credentials not configured" | |
| else | |
| echo "skip=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Checkout repository | |
| if: steps.check_creds.outputs.skip != 'true' | |
| uses: actions/checkout@v4 | |
| - name: Setup Ruby | |
| if: steps.check_creds.outputs.skip != 'true' | |
| uses: ruby/setup-ruby@v1 | |
| with: | |
| ruby-version: "3.2" | |
| working-directory: apple/fastlane | |
| bundler-cache: true | |
| - name: Download IPA | |
| if: steps.check_creds.outputs.skip != 'true' | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: release-ipa | |
| path: release/ | |
| - name: Upload to TestFlight | |
| if: steps.check_creds.outputs.skip != 'true' | |
| working-directory: apple/fastlane | |
| env: | |
| ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }} | |
| ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }} | |
| ASC_API_KEY: ${{ secrets.ASC_API_KEY }} | |
| IPA_PATH: ${{ github.workspace }}/release/traintime-${{ needs.release.outputs.version }}.ipa | |
| run: | | |
| if [ ! -f "$IPA_PATH" ]; then | |
| echo "Error: IPA file not found at $IPA_PATH" | |
| ls -la "${{ github.workspace }}/release/" || echo "Directory not found" | |
| exit 1 | |
| fi | |
| bundle exec fastlane ios beta ipa:"$IPA_PATH" | |
| - name: Summary | |
| if: steps.check_creds.outputs.skip != 'true' | |
| run: | | |
| echo "## TestFlight Deployment" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Version:** ${{ needs.release.outputs.version }}" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Track:** Internal TestFlight" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "Build will be available in [App Store Connect](https://appstoreconnect.apple.com) shortly." >> $GITHUB_STEP_SUMMARY | |
| # ============================================ | |
| # Prepare App Store version + upload metadata | |
| # ============================================ | |
| prepare-appstore: | |
| name: Prepare App Store version | |
| needs: release | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Check for App Store Connect credentials | |
| id: check_creds | |
| env: | |
| ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }} | |
| ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }} | |
| ASC_API_KEY: ${{ secrets.ASC_API_KEY }} | |
| run: | | |
| if [ -z "$ASC_KEY_ID" ] || [ -z "$ASC_ISSUER_ID" ] || [ -z "$ASC_API_KEY" ]; then | |
| echo "skip=true" >> $GITHUB_OUTPUT | |
| echo "::notice::Skipping App Store metadata - credentials not configured" | |
| else | |
| echo "skip=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Checkout repository | |
| if: steps.check_creds.outputs.skip != 'true' | |
| uses: actions/checkout@v4 | |
| - name: Verify tag matches MARKETING_VERSION | |
| if: steps.check_creds.outputs.skip != 'true' | |
| run: | | |
| MV=$(grep 'MARKETING_VERSION' apple/TrainTimeWatch.xcodeproj/project.pbxproj | head -1 | sed 's/.*= *\(.*\);/\1/' | tr -d ' ') | |
| echo "Tag: ${{ github.ref_name }} | MARKETING_VERSION: $MV" | |
| if [ "$MV" != "${{ github.ref_name }}" ]; then | |
| echo "::error::Tag ${{ github.ref_name }} does not match MARKETING_VERSION $MV. Bump the version in Xcode before tagging." | |
| exit 1 | |
| fi | |
| - name: Setup Ruby | |
| if: steps.check_creds.outputs.skip != 'true' | |
| uses: ruby/setup-ruby@v1 | |
| with: | |
| ruby-version: "3.2" | |
| working-directory: apple/fastlane | |
| bundler-cache: true | |
| - name: Create App Store version and upload metadata | |
| if: steps.check_creds.outputs.skip != 'true' | |
| working-directory: apple/fastlane | |
| env: | |
| ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }} | |
| ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }} | |
| ASC_API_KEY: ${{ secrets.ASC_API_KEY }} | |
| run: bundle exec fastlane ios sync_metadata version:${{ github.ref_name }} | |
| - name: Summary | |
| if: steps.check_creds.outputs.skip != 'true' | |
| run: | | |
| echo "## App Store Version Prepared" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Version:** ${{ github.ref_name }} (Prepare for Submission, not submitted)" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Metadata:** uploaded from apple/fastlane/metadata/" >> $GITHUB_STEP_SUMMARY | |
| # ============================================ | |
| # Deploy to Google Play | |
| # ============================================ | |
| deploy-android: | |
| name: Deploy to Google Play | |
| needs: [release, build-android] | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 25 | |
| steps: | |
| - name: Check for Play credentials | |
| id: check_creds | |
| env: | |
| PLAY_SERVICE_ACCOUNT_JSON: ${{ secrets.PLAY_SERVICE_ACCOUNT_JSON }} | |
| run: | | |
| if [ -z "$PLAY_SERVICE_ACCOUNT_JSON" ]; then | |
| echo "skip=true" >> $GITHUB_OUTPUT | |
| echo "::notice::Skipping Google Play - PLAY_SERVICE_ACCOUNT_JSON not configured" | |
| else | |
| echo "skip=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Checkout repository | |
| if: steps.check_creds.outputs.skip != 'true' | |
| uses: actions/checkout@v4 | |
| - name: Setup Ruby | |
| if: steps.check_creds.outputs.skip != 'true' | |
| uses: ruby/setup-ruby@v1 | |
| with: | |
| ruby-version: "3.2" | |
| working-directory: android | |
| bundler-cache: true | |
| - name: Download Android AAB | |
| if: steps.check_creds.outputs.skip != 'true' | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: android-aab | |
| path: android/release | |
| - name: Write Play credentials | |
| if: steps.check_creds.outputs.skip != 'true' | |
| working-directory: android | |
| env: | |
| PLAY_SERVICE_ACCOUNT_JSON: ${{ secrets.PLAY_SERVICE_ACCOUNT_JSON }} | |
| run: echo "$PLAY_SERVICE_ACCOUNT_JSON" > play-service-account.json | |
| - name: Upload to Google Play | |
| if: steps.check_creds.outputs.skip != 'true' | |
| working-directory: android | |
| env: | |
| SUPPLY_JSON_KEY: ${{ github.workspace }}/android/play-service-account.json | |
| run: bundle exec fastlane android ci_release aab:"${{ github.workspace }}/android/release/app-release.aab" wear_aab:"${{ github.workspace }}/android/release/wear-release.aab" | |
| - name: Summary | |
| if: steps.check_creds.outputs.skip != 'true' | |
| run: | | |
| echo "## Google Play Deployment" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Version:** ${{ github.ref_name }}" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Internal testing:** published (live to internal testers, phone + Wear OS)" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Open testing:** staged as a draft (review and roll out in the console)" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "Build will appear in the [Play Console](https://play.google.com/console) shortly." >> $GITHUB_STEP_SUMMARY |