|
| 1 | +# UAT / internal-tester builds on merge to `dev` (or manual dispatch). |
| 2 | +# |
| 3 | +# OPTIONAL cloud path. The FAST path is local: `scripts/uat.sh` on your Mac (minutes). |
| 4 | +# This runs the SAME script on a single macOS runner so CI and local are ONE pipeline |
| 5 | +# (one source of truth): it computes the next beta version, generates release notes, |
| 6 | +# builds every artifact BEFORE publishing any, uploads Android -> Play "internal" + |
| 7 | +# iOS -> TestFlight, then cuts the GitHub prerelease tag with the APK + AAB attached. |
| 8 | +# |
| 9 | +# Cost note: iOS on GitHub's free macOS runners is slow (~30-90 min) and macOS minutes |
| 10 | +# bill at 10x. That's the price of a fully-consistent CI path; the local script stays the |
| 11 | +# fast default. Use workflow_dispatch with platform=android for a quick Play-only run. |
| 12 | +# |
| 13 | +# Uses the credentials ALREADY stored as repo secrets (nothing new to add): |
| 14 | +# Android: ANDROID_KEYSTORE_BASE64, ANDROID_KEYSTORE_PASSWORD, ANDROID_KEY_ALIAS, |
| 15 | +# ANDROID_KEY_PASSWORD, PLAY_STORE_JSON_KEY |
| 16 | +# iOS: ASC_API_KEY_P8, ASC_KEY_ID, ASC_ISSUER_ID (TestFlight upload) |
| 17 | +# IOS_CERTIFICATE_P12, IOS_CERTIFICATE_PASSWORD, IOS_PROVISION_PROFILE, |
| 18 | +# KEYCHAIN_PASSWORD (signing) |
| 19 | +name: UAT build (dev → internal testers) |
| 20 | + |
| 21 | +on: |
| 22 | + push: |
| 23 | + branches: [dev] |
| 24 | + workflow_dispatch: |
| 25 | + inputs: |
| 26 | + platform: |
| 27 | + description: Which platform(s) to build |
| 28 | + type: choice |
| 29 | + default: both |
| 30 | + options: [both, ios, android] |
| 31 | + |
| 32 | +concurrency: |
| 33 | + group: uat-${{ github.ref }} |
| 34 | + # Do NOT cancel a run in flight: once it starts publishing (Play + TestFlight), a mid-run |
| 35 | + # cancel could half-publish. Let it finish; the next push queues behind it. |
| 36 | + cancel-in-progress: false |
| 37 | + |
| 38 | +permissions: |
| 39 | + contents: write # uat.sh pushes the beta bump commit + tag and creates the GH release |
| 40 | + |
| 41 | +jobs: |
| 42 | + beta: |
| 43 | + # Single macOS runner: it has Xcode AND the Android SDK, so one machine builds both and |
| 44 | + # runs scripts/uat.sh end to end (bump -> build all -> publish all -> tag -> GH release). |
| 45 | + # Skip runs triggered by the bot's back-merge of main -> dev (that push isn't a real |
| 46 | + # change to ship a beta for); real human pushes to dev and manual dispatch still run. |
| 47 | + if: ${{ github.event_name == 'workflow_dispatch' || github.actor != 'github-actions[bot]' }} |
| 48 | + runs-on: macos-latest |
| 49 | + steps: |
| 50 | + - uses: actions/checkout@v4 |
| 51 | + with: |
| 52 | + submodules: false # pro/ is a private repo — fetch it below with a PAT |
| 53 | + fetch-depth: 0 # uat.sh needs full tag history to compute the next beta N |
| 54 | + - name: Check out pro submodule (private) |
| 55 | + # pro/ lives in a separate private repo; the default GITHUB_TOKEN can't read it, so |
| 56 | + # authenticate with PRO_SUBMODULE_PAT (same mechanism as ci.yml). uat.sh's build needs |
| 57 | + # the real @offgrid/pro package present. |
| 58 | + env: |
| 59 | + PRO_PAT: ${{ secrets.PRO_SUBMODULE_PAT }} |
| 60 | + run: | |
| 61 | + if [ -z "$PRO_PAT" ]; then echo "PRO_SUBMODULE_PAT not set — cannot fetch private pro/ submodule"; exit 1; fi |
| 62 | + git config --global url."https://x-access-token:${PRO_PAT}@github.com/".insteadOf "https://github.com/" |
| 63 | + git submodule update --init --recursive pro |
| 64 | + git config --global --unset url."https://x-access-token:${PRO_PAT}@github.com/".insteadOf || true |
| 65 | + - uses: actions/setup-node@v4 |
| 66 | + with: { node-version: '20', cache: npm } |
| 67 | + - run: npm ci |
| 68 | + - uses: actions/setup-java@v4 |
| 69 | + with: { distribution: temurin, java-version: '17' } |
| 70 | + - uses: ruby/setup-ruby@v1 |
| 71 | + with: { ruby-version: '3.3', bundler-cache: true } |
| 72 | + - name: pod install |
| 73 | + # Restore Podfile.lock afterward: a CI CocoaPods version can rewrite it, which would |
| 74 | + # dirty the tree and trip uat.sh's clean-tree pre-check. The pods are already installed, |
| 75 | + # so the lock's content doesn't affect the build. |
| 76 | + run: | |
| 77 | + cd ios && pod install |
| 78 | + cd .. && git checkout -- ios/Podfile.lock 2>/dev/null || true |
| 79 | + - name: Decode signing secrets |
| 80 | + run: | |
| 81 | + echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | base64 --decode > "$RUNNER_TEMP/release.keystore" |
| 82 | + # PLAY_STORE_JSON_KEY may be stored raw-JSON or base64 — handle both. |
| 83 | + echo "${{ secrets.PLAY_STORE_JSON_KEY }}" | base64 --decode > "$RUNNER_TEMP/play-key.json" \ |
| 84 | + || printf '%s' '${{ secrets.PLAY_STORE_JSON_KEY }}' > "$RUNNER_TEMP/play-key.json" |
| 85 | + - name: Configure git for the beta bump commit + push |
| 86 | + # actions/checkout leaves HEAD detached; uat.sh commits the bump and `git push`es it, |
| 87 | + # which needs a real branch with an upstream. Re-attach to the triggering branch. |
| 88 | + run: | |
| 89 | + git config user.name "offgrid-ci" |
| 90 | + git config user.email "ci@offgridmobile.ai" |
| 91 | + git checkout -B "$GITHUB_REF_NAME" |
| 92 | + git branch --set-upstream-to="origin/$GITHUB_REF_NAME" "$GITHUB_REF_NAME" || true |
| 93 | + - name: Ship beta (scripts/uat.sh) |
| 94 | + env: |
| 95 | + # Which platform(s): push events build both; manual dispatch honors the picker. |
| 96 | + UAT_PLATFORM_ARG: ${{ github.event_name == 'workflow_dispatch' && inputs.platform != 'both' && format('--{0}', inputs.platform) || '' }} |
| 97 | + # gh release create / git push |
| 98 | + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
| 99 | + # Android signing — build.gradle reads these via project.hasProperty(...), so pass |
| 100 | + # them as Gradle project properties (ORG_GRADLE_PROJECT_<name>), not plain env. |
| 101 | + ORG_GRADLE_PROJECT_OFFGRID_UPLOAD_STORE_FILE: ${{ runner.temp }}/release.keystore |
| 102 | + ORG_GRADLE_PROJECT_OFFGRID_UPLOAD_STORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} |
| 103 | + ORG_GRADLE_PROJECT_OFFGRID_UPLOAD_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} |
| 104 | + ORG_GRADLE_PROJECT_OFFGRID_UPLOAD_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} |
| 105 | + # Play upload |
| 106 | + PLAY_STORE_JSON_KEY_PATH: ${{ runner.temp }}/play-key.json |
| 107 | + # iOS auth + signing (imported into a temp keychain by the Fastfile's setup_signing) |
| 108 | + ASC_API_KEY_P8: ${{ secrets.ASC_API_KEY_P8 }} |
| 109 | + ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }} |
| 110 | + ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }} |
| 111 | + IOS_CERTIFICATE_P12: ${{ secrets.IOS_CERTIFICATE_P12 }} |
| 112 | + IOS_CERTIFICATE_PASSWORD: ${{ secrets.IOS_CERTIFICATE_PASSWORD }} |
| 113 | + IOS_PROVISION_PROFILE: ${{ secrets.IOS_PROVISION_PROFILE }} |
| 114 | + KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} |
| 115 | + FASTLANE_SKIP_UPDATE_CHECK: '1' |
| 116 | + FASTLANE_HIDE_CHANGELOG: '1' |
| 117 | + run: bash scripts/uat.sh $UAT_PLATFORM_ARG |
0 commit comments