diff --git a/.github/workflows/mobile.yml b/.github/workflows/mobile.yml index 8cd4cdf..937fe54 100644 --- a/.github/workflows/mobile.yml +++ b/.github/workflows/mobile.yml @@ -75,12 +75,72 @@ jobs: - name: Bundle (expo export) run: pnpm export + # Produce a standalone, installable companion-app APK without consuming EAS build + # credits. The native project remains generated output and is not committed. + android-apk: + if: github.event_name != 'workflow_dispatch' + runs-on: ubuntu-latest + timeout-minutes: 45 + steps: + - uses: actions/checkout@v5 + + - uses: pnpm/action-setup@v6 + + - uses: actions/setup-node@v5 + with: + node-version-file: .nvmrc + cache: pnpm + + - name: Set up JDK + uses: actions/setup-java@v6 + with: + distribution: temurin + java-version: '17' + + - name: Set up Android SDK + uses: android-actions/setup-android@v4 + with: + log-accepted-android-sdk-licenses: false + packages: 'platform-tools platforms;android-36 build-tools;36.0.0 ndk;27.1.12297006' + + - name: Install + working-directory: . + run: pnpm install --frozen-lockfile + + - name: Generate Android project + run: pnpm exec expo prebuild --platform android --clean --no-install + + # Expo/RN debuggable variants omit the embedded JS bundle and require a + # Metro server. Release embeds the bundle; Expo's generated project signs + # it with the debug keystore, making it installable but not store-ready. + - name: Build standalone APK + run: ./android/gradlew --no-daemon --stacktrace -p android :app:assembleRelease + + - name: Upload standalone APK + uses: actions/upload-artifact@v4 + with: + name: tronbrowser-android-preview-${{ github.sha }} + path: apps/mobile/android/app/build/outputs/apk/release/app-release.apk + if-no-files-found: error + overwrite: true + retention-days: 14 + + - name: Add artifact instructions + run: | + { + echo '### TronBrowser Android preview APK' + echo + echo 'Download the `tronbrowser-android-preview-*` artifact from this workflow run.' + echo 'After extracting it, install with `adb install -r app-release.apk`.' + } >> "$GITHUB_STEP_SUMMARY" + # Cloud build + optional store submit via EAS. Requires the EXPO_TOKEN repo # secret (Expo account access token). iOS/Android signing credentials are # managed by EAS (`eas credentials`) — not stored here. eas-build: if: github.event_name == 'workflow_dispatch' runs-on: ubuntu-latest + timeout-minutes: 120 steps: - uses: actions/checkout@v5 @@ -100,9 +160,16 @@ jobs: eas-version: latest token: ${{ secrets.EXPO_TOKEN }} + - name: Validate EAS request + run: | + if [[ '${{ inputs.submit }}' == 'true' && '${{ inputs.profile }}' != 'production' ]]; then + echo '::error::Store submission requires the production build profile.' + exit 1 + fi + - name: EAS build - run: eas build --platform ${{ inputs.platform }} --profile ${{ inputs.profile }} --non-interactive --no-wait + run: eas build --platform ${{ inputs.platform }} --profile ${{ inputs.profile }} --non-interactive - name: EAS submit if: ${{ inputs.submit }} - run: eas submit --platform ${{ inputs.platform }} --profile ${{ inputs.profile }} --non-interactive --latest + run: eas submit --platform ${{ inputs.platform }} --profile production --non-interactive --latest diff --git a/apps/mobile/BUILD_READINESS.md b/apps/mobile/BUILD_READINESS.md index 998d9cf..d500697 100644 --- a/apps/mobile/BUILD_READINESS.md +++ b/apps/mobile/BUILD_READINESS.md @@ -27,6 +27,25 @@ The exception is declared in `package.json`; typecheck, tests, lint, and both platform bundles remain the CI acceptance checks. Expo Doctor is a network-backed local diagnostic and is not run in CI. +## GitHub Actions preview APK + +Every mobile pull request and push to `main` also generates the Android native +project and runs Gradle on a GitHub-hosted runner. Download the +`tronbrowser-android-preview-*` artifact from the `Mobile` workflow, extract it, +then install the APK on a connected Android device: + +```bash +adb install -r app-release.apk +``` + +If another build of the app is installed with a different signing key, uninstall +that build before installing this preview APK. + +This standalone build embeds its JavaScript bundle and is signed with the +generated debug keystore for sideload testing. It is not store-signed. The job +does not use Expo EAS credits, publish an app, or commit the generated `android/` +directory. + ## EAS preview build The app is linked to the `profullstack/tronbrowserdev` EAS project. Cloud builds @@ -40,6 +59,8 @@ After the secret is present, run the `Mobile` workflow manually with: - profile: `preview` - submit: `false` -The workflow starts an asynchronous EAS build. Its EAS build URL is the handoff -artifact for review. Store submission remains a separate production step and -requires the corresponding Apple or Google developer account and signing setup. +The workflow waits for EAS to finish, so a successful Actions job means the +remote build completed rather than merely entered the queue. Its EAS build URL +is the handoff artifact for review. Store submission is only available with the +`production` build profile and still requires the corresponding Apple or Google +developer account and signing setup. diff --git a/apps/mobile/eas.json b/apps/mobile/eas.json index 7d10efe..06d50a2 100644 --- a/apps/mobile/eas.json +++ b/apps/mobile/eas.json @@ -9,7 +9,10 @@ "distribution": "internal" }, "preview": { - "distribution": "internal" + "distribution": "internal", + "android": { + "buildType": "apk" + } }, "production": { "autoIncrement": true diff --git a/apps/mobile/package.json b/apps/mobile/package.json index 22ad914..f091a02 100644 --- a/apps/mobile/package.json +++ b/apps/mobile/package.json @@ -34,6 +34,7 @@ "devDependencies": { "@babel/core": "^7.25.0", "@types/react": "~19.2.17", + "babel-preset-expo": "~57.0.5", "typescript": "^5.6.3", "vitest": "^2.1.4" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f775307..e453a5c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -92,6 +92,9 @@ importers: '@types/react': specifier: ~19.2.17 version: 19.2.17 + babel-preset-expo: + specifier: ~57.0.5 + version: 57.0.5(@babel/core@7.29.7)(@babel/runtime@7.29.7)(expo@57.0.9)(react-refresh@0.14.2) typescript: specifier: ^5.6.3 version: 5.9.3