feat: add GitHub Actions workflow to build Android APK via EAS#46
feat: add GitHub Actions workflow to build Android APK via EAS#46chakrihacker wants to merge 3 commits into
Conversation
Adds .github/workflows/build-apk.yml that: - Triggers manually (workflow_dispatch) with profile choice (preview/production) - Triggers automatically on version tags (v*.*.*) - Builds Android APK using EAS Build cloud service - Downloads the built APK and uploads it as a GitHub Actions artifact (30-day retention) - Reuses the existing setup-node-bun-install composite action and staging environment https://claude.ai/code/session_01AogoKQvPk5RwVsZ52ps165
Replaces the EAS-based build-apk.yml with a self-contained workflow that builds natively on GitHub Actions runners (no EAS cloud required): - Android (ubuntu-latest): expo prebuild → assembleDebug → APK artifact - iOS (macos-latest): expo prebuild → pod install → xcodebuild unsigned archive → IPA artifact Triggers: manual workflow_dispatch (all/android/ios) or push to v*.*.* tags. Both jobs run in parallel. Gradle and CocoaPods caches speed up repeat builds. iOS IPA is unsigned so it can be re-signed by tools like LiveContainer. https://claude.ai/code/session_01AogoKQvPk5RwVsZ52ps165
Replaces manual Gradle/xcodebuild steps with `eas build --local`, which runs the EAS build pipeline on the GitHub Actions runner (no cloud credits) while using EAS for credential management and build configuration. - Android (ubuntu-latest): eas build --local --platform android --profile preview - iOS (macos-latest): eas build --local --platform ios --profile preview - Both jobs use --output flag to produce a predictable artifact path - expo/expo-github-action installs EAS CLI and authenticates via EXPO_TOKEN https://claude.ai/code/session_01AogoKQvPk5RwVsZ52ps165
|
CodeAnt AI is reviewing your PR. Thanks for using CodeAnt! 🎉We're free for open-source projects. if you're enjoying it, help us grow by sharing. Share on X · |
Reviewer's GuideAdds a new GitHub Actions workflow to locally build Android APK and iOS IPA via EAS, with manual and tag-based triggers, environment setup, caching, and artifact upload for both platforms. File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
WalkthroughA new GitHub Actions workflow file is introduced to automate building Android APK and iOS IPA artifacts. The workflow supports manual trigger with platform selection or automatic trigger on version tag pushes. It includes separate jobs for Android and iOS builds using EAS CLI with dependency caching. Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Suggested labels
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
PR Compliance Guide 🔍Below is a summary of compliance checks for this PR:
Compliance status legend🟢 - Fully Compliant🟡 - Partial Compliant 🔴 - Not Compliant ⚪ - Requires Further Human Verification 🏷️ - Compliance label |
|||||||||||||||||||||||||
There was a problem hiding this comment.
Hey - I've found 2 issues, and left some high level feedback:
- The
if: ${{ inputs.platform != 'ios' }}/!= 'android'conditions rely oninputs.platform, which is only present forworkflow_dispatchevents; for tagpushevents this input is undefined—consider guarding ongithub.event_name == 'workflow_dispatch'or using a default/fallback so the jobs behave predictably on tag pushes. - Both Android and iOS builds are hardcoded to use the
previewEAS profile; if you intend different behavior for tag builds or want flexibility, consider making the profile configurable via workflow inputs or derived from the tag/branch. - The Gradle and CocoaPods cache keys are based on
package.json; using more specific lockfiles (e.g., Gradle wrapper/build.gradle,Podfile.lock) would make cache invalidation better aligned with the actual native dependency changes.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The `if: ${{ inputs.platform != 'ios' }}` / `!= 'android'` conditions rely on `inputs.platform`, which is only present for `workflow_dispatch` events; for tag `push` events this input is undefined—consider guarding on `github.event_name == 'workflow_dispatch'` or using a default/fallback so the jobs behave predictably on tag pushes.
- Both Android and iOS builds are hardcoded to use the `preview` EAS profile; if you intend different behavior for tag builds or want flexibility, consider making the profile configurable via workflow inputs or derived from the tag/branch.
- The Gradle and CocoaPods cache keys are based on `package.json`; using more specific lockfiles (e.g., Gradle wrapper/`build.gradle`, `Podfile.lock`) would make cache invalidation better aligned with the actual native dependency changes.
## Individual Comments
### Comment 1
<location> `.github/workflows/build-apk-ipa.yml:60-65` </location>
<code_context>
+ path: |
+ ~/.gradle/caches
+ ~/.gradle/wrapper
+ key: ${{ runner.os }}-gradle-${{ hashFiles('package.json') }}
+ restore-keys: ${{ runner.os }}-gradle-
+
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Gradle cache key should be based on Gradle-related files instead of `package.json`.
Keying the Gradle cache off `package.json` means changes in `android/build.gradle`, `android/app/build.gradle`, or `gradle-wrapper.properties` won’t invalidate the cache, while unrelated JS changes will. Prefer something like:
```yaml
key: ${{ runner.os }}-gradle-${{ hashFiles('android/**/build.gradle', 'android/**/gradle-wrapper.properties') }}
```
Optionally include `package.json` too, but ensure Gradle files are part of the hash so cache invalidation matches Gradle dependency changes.
```suggestion
- name: Cache Gradle dependencies
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('android/**/build.gradle', 'android/**/gradle-wrapper.properties', 'package.json') }}
restore-keys: ${{ runner.os }}-gradle-
```
</issue_to_address>
### Comment 2
<location> `.github/workflows/build-apk-ipa.yml:109-112` </location>
<code_context>
+ uses: actions/cache@v4
+ with:
+ path: ~/Library/Caches/CocoaPods
+ key: ${{ runner.os }}-cocoapods-${{ hashFiles('package.json') }}
+ restore-keys: ${{ runner.os }}-cocoapods-
+
</code_context>
<issue_to_address>
**suggestion (performance):** CocoaPods cache key would be more precise if it used Pod-related files instead of `package.json`.
CocoaPods resolution is driven by `ios/Podfile` and especially `ios/Podfile.lock`, not `package.json`. Using `package.json` here can cause both unnecessary cache invalidations and stale CocoaPods caches. Prefer something like:
```yaml
key: ${{ runner.os }}-cocoapods-${{ hashFiles('ios/Podfile.lock', 'ios/Podfile') }}
```
```suggestion
- name: Cache CocoaPods
uses: actions/cache@v4
with:
path: ~/Library/Caches/CocoaPods
key: ${{ runner.os }}-cocoapods-${{ hashFiles('ios/Podfile.lock', 'ios/Podfile') }}
restore-keys: |
${{ runner.os }}-cocoapods-
```
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| - name: Cache Gradle dependencies | ||
| uses: actions/cache@v4 | ||
| with: | ||
| path: | | ||
| ~/.gradle/caches | ||
| ~/.gradle/wrapper |
There was a problem hiding this comment.
suggestion (bug_risk): Gradle cache key should be based on Gradle-related files instead of package.json.
Keying the Gradle cache off package.json means changes in android/build.gradle, android/app/build.gradle, or gradle-wrapper.properties won’t invalidate the cache, while unrelated JS changes will. Prefer something like:
key: ${{ runner.os }}-gradle-${{ hashFiles('android/**/build.gradle', 'android/**/gradle-wrapper.properties') }}Optionally include package.json too, but ensure Gradle files are part of the hash so cache invalidation matches Gradle dependency changes.
| - name: Cache Gradle dependencies | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.gradle/caches | |
| ~/.gradle/wrapper | |
| - name: Cache Gradle dependencies | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.gradle/caches | |
| ~/.gradle/wrapper | |
| key: ${{ runner.os }}-gradle-${{ hashFiles('android/**/build.gradle', 'android/**/gradle-wrapper.properties', 'package.json') }} | |
| restore-keys: ${{ runner.os }}-gradle- |
| - name: Cache CocoaPods | ||
| uses: actions/cache@v4 | ||
| with: | ||
| path: ~/Library/Caches/CocoaPods |
There was a problem hiding this comment.
suggestion (performance): CocoaPods cache key would be more precise if it used Pod-related files instead of package.json.
CocoaPods resolution is driven by ios/Podfile and especially ios/Podfile.lock, not package.json. Using package.json here can cause both unnecessary cache invalidations and stale CocoaPods caches. Prefer something like:
key: ${{ runner.os }}-cocoapods-${{ hashFiles('ios/Podfile.lock', 'ios/Podfile') }}| - name: Cache CocoaPods | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/Library/Caches/CocoaPods | |
| - name: Cache CocoaPods | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/Library/Caches/CocoaPods | |
| key: ${{ runner.os }}-cocoapods-${{ hashFiles('ios/Podfile.lock', 'ios/Podfile') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cocoapods- |
|
CodeAnt AI finished reviewing your PR. |
PR Code Suggestions ✨Explore these optional code suggestions:
|
||||||||||||||||
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (5)
.github/workflows/build-apk-ipa.yml (5)
20-22: Verifycancel-in-progress: trueis appropriate for both tag and manual triggers.The concurrency group is
${{ github.workflow }}-${{ github.ref }}. For different tags (e.g.,refs/tags/v1.0.0vsrefs/tags/v1.0.1) the groups are distinct, so cross-tag cancellation won't occur. However, if the same tag is somehow re-triggered (e.g., a manual re-run after a runner failure),cancel-in-progress: truewill cancel the in-flight build. For release tags where a completed artifact is required, you may want to set this tofalse, or scope it differently forpushvsworkflow_dispatchevents.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/build-apk-ipa.yml around lines 20 - 22, The workflow's concurrency uses group: `${{ github.workflow }}-${{ github.ref }}` with `cancel-in-progress: true`, which can cancel in-flight builds for the same ref (e.g., manual re-runs of a tag); update the concurrency config to avoid unwanted cancellation for release/tag runs by either setting `cancel-in-progress: false` for tag-triggered runs or by separating groups based on event type (e.g., include `${{ github.event_name }}` or conditionally set `cancel-in-progress` for `workflow_dispatch` vs `push`), modifying the `concurrency` block and its `group`/`cancel-in-progress` settings accordingly so tag releases are not canceled unintentionally.
109-114: CocoaPods cache key hashespackage.jsoninstead ofios/Podfile.lock.The authoritative source for CocoaPods dependency resolution is
ios/Podfile.lock. Hashingpackage.jsonmeans the cache won't be invalidated when pod versions change independently of JS packages.♻️ Proposed fix
- key: ${{ runner.os }}-cocoapods-${{ hashFiles('package.json') }} + key: ${{ runner.os }}-cocoapods-${{ hashFiles('ios/Podfile.lock') }} restore-keys: ${{ runner.os }}-cocoapods-🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/build-apk-ipa.yml around lines 109 - 114, The "Cache CocoaPods" step is using hashFiles('package.json') for the cache key so CocoaPods caches won't invalidate when iOS Pod dependencies change; update the key expression in that step (the key under the step named "Cache CocoaPods" that currently uses hashFiles('package.json')) to hashFiles('ios/Podfile.lock') so the cache is tied to the Podfile.lock content (you can keep the same restore-keys pattern and actions/cache@v4 usage).
54-58: Duplicated.envcreation block in both jobs.The three-line
.envinjection is identical inbuild-androidandbuild-ios. Consider extracting it into the existing.github/actions/setup-node-bun-installcomposite action (if env setup is always needed there), or into a dedicated composite action, to avoid divergence.Also applies to: 103-107
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/build-apk-ipa.yml around lines 54 - 58, The .env creation block is duplicated in both build-android and build-ios jobs; move the three echo lines into the existing composite action .github/actions/setup-node-bun-install (or create a new composite action like .github/actions/setup-env) and have both jobs call that action instead of repeating the lines. Update the composite action to accept vars.EXPO_PUBLIC_* as inputs or read from environment and write the same .env file, then remove the echoed .env lines from the build-android and build-ios job steps so the env setup is centralized and DRY.
60-67: Gradle cache key hashespackage.jsoninstead of Gradle build files.
package.jsonchanges do not track Android native dependency changes (e.g., edits toandroid/build.gradleorandroid/app/build.gradle). This will produce stale cache hits after Gradle dependency updates and misses after irrelevant JS package changes.♻️ Proposed fix
key: ${{ runner.os }}-gradle-${{ hashFiles('package.json') }} + key: ${{ runner.os }}-gradle-${{ hashFiles('android/**/*.gradle*', 'android/gradle/wrapper/gradle-wrapper.properties') }} restore-keys: ${{ runner.os }}-gradle-🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/build-apk-ipa.yml around lines 60 - 67, The Gradle cache step ("name: Cache Gradle dependencies" using actions/cache@v4) currently keys the cache by hashFiles('package.json'), which causes JS changes to influence the Gradle cache; change the key to hash the Android Gradle files instead (for example use hashFiles('android/build.gradle','android/app/build.gradle','android/settings.gradle','android/gradle.properties','android/gradle/wrapper/gradle-wrapper.properties')) so the key becomes something like ${{ runner.os }}-gradle-${{ hashFiles(...) }}, and keep a broader restore-keys like ${{ runner.os }}-gradle- to allow partial restores.
116-117: Consider adding App Store Connect API key for credential repair resilience.The
eas build --local --non-interactivecommand retrieves iOS credentials from EAS servers usingEXPO_TOKEN(line 101) without interactive Apple login. If credentials in EAS need repair—such as an expired provisioning profile or re-signed certificate—eas buildwill fail silently because--non-interactiveprevents prompts and noEXPO_ASC_API_KEY_PATHorEXPO_ASC_API_KEY_ID/EXPO_ASC_API_KEY_ISSUER_IDare provided for automated credential repair.This is only a concern if credentials are not pre-registered or need periodic maintenance in EAS. For added resilience, optionally pass App Store Connect API credentials as environment variables to the
Build IPA (local)step when credentials may require repair.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/build-apk-ipa.yml around lines 116 - 117, The "Build IPA (local)" step runs "eas build --local --platform ios --profile preview --non-interactive --output build.ipa" and can fail silently if EAS needs to repair iOS credentials; update this step to accept App Store Connect API credentials so automated repair can occur by supplying EXPO_ASC_API_KEY_PATH (or EXPO_ASC_API_KEY_ID and EXPO_ASC_API_KEY_ISSUER_ID) alongside the existing EXPO_TOKEN environment variable, and ensure the workflow conditionally uses these env vars (or secrets) when present so "eas build" can perform credential repair non-interactively.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.github/workflows/build-apk-ipa.yml:
- Line 70: Add a new workflow_dispatch input named "profile" (default
"preview"), create a job-level env var PROFILE that chooses "production" when
the run is a version tag (e.g., startsWith(github.ref, 'refs/tags/v')) otherwise
uses the provided inputs.profile or "preview", and replace the hardcoded
"--profile preview" in both eas build run steps with "--profile ${{ env.PROFILE
}}"; refer to the existing workflow_dispatch definition, the run lines that
invoke "eas build --local --platform android --profile preview --profile ..."
and the job env PROFILE to locate where to add the input, expression, and
substitution.
---
Nitpick comments:
In @.github/workflows/build-apk-ipa.yml:
- Around line 20-22: The workflow's concurrency uses group: `${{ github.workflow
}}-${{ github.ref }}` with `cancel-in-progress: true`, which can cancel
in-flight builds for the same ref (e.g., manual re-runs of a tag); update the
concurrency config to avoid unwanted cancellation for release/tag runs by either
setting `cancel-in-progress: false` for tag-triggered runs or by separating
groups based on event type (e.g., include `${{ github.event_name }}` or
conditionally set `cancel-in-progress` for `workflow_dispatch` vs `push`),
modifying the `concurrency` block and its `group`/`cancel-in-progress` settings
accordingly so tag releases are not canceled unintentionally.
- Around line 109-114: The "Cache CocoaPods" step is using
hashFiles('package.json') for the cache key so CocoaPods caches won't invalidate
when iOS Pod dependencies change; update the key expression in that step (the
key under the step named "Cache CocoaPods" that currently uses
hashFiles('package.json')) to hashFiles('ios/Podfile.lock') so the cache is tied
to the Podfile.lock content (you can keep the same restore-keys pattern and
actions/cache@v4 usage).
- Around line 54-58: The .env creation block is duplicated in both build-android
and build-ios jobs; move the three echo lines into the existing composite action
.github/actions/setup-node-bun-install (or create a new composite action like
.github/actions/setup-env) and have both jobs call that action instead of
repeating the lines. Update the composite action to accept vars.EXPO_PUBLIC_* as
inputs or read from environment and write the same .env file, then remove the
echoed .env lines from the build-android and build-ios job steps so the env
setup is centralized and DRY.
- Around line 60-67: The Gradle cache step ("name: Cache Gradle dependencies"
using actions/cache@v4) currently keys the cache by hashFiles('package.json'),
which causes JS changes to influence the Gradle cache; change the key to hash
the Android Gradle files instead (for example use
hashFiles('android/build.gradle','android/app/build.gradle','android/settings.gradle','android/gradle.properties','android/gradle/wrapper/gradle-wrapper.properties'))
so the key becomes something like ${{ runner.os }}-gradle-${{ hashFiles(...) }},
and keep a broader restore-keys like ${{ runner.os }}-gradle- to allow partial
restores.
- Around line 116-117: The "Build IPA (local)" step runs "eas build --local
--platform ios --profile preview --non-interactive --output build.ipa" and can
fail silently if EAS needs to repair iOS credentials; update this step to accept
App Store Connect API credentials so automated repair can occur by supplying
EXPO_ASC_API_KEY_PATH (or EXPO_ASC_API_KEY_ID and EXPO_ASC_API_KEY_ISSUER_ID)
alongside the existing EXPO_TOKEN environment variable, and ensure the workflow
conditionally uses these env vars (or secrets) when present so "eas build" can
perform credential repair non-interactively.
| restore-keys: ${{ runner.os }}-gradle- | ||
|
|
||
| - name: Build APK (local) | ||
| run: eas build --local --platform android --profile preview --non-interactive --output build.apk |
There was a problem hiding this comment.
Hardcoded --profile preview prevents production builds from ever being triggered.
Both build commands hard-code --profile preview. Version-tag pushes (v*.*.*) signal a production release intent, yet they will always produce a preview artifact. There is also no profile input on workflow_dispatch, so there is no path to a production build through this workflow at all.
Add a profile input to workflow_dispatch and derive it dynamically for tag-triggered runs:
🛠 Proposed fix – add `profile` input and use it in both build commands
workflow_dispatch:
inputs:
platform:
description: "Platform to build"
required: true
default: "all"
type: choice
options:
- all
- android
- ios
+ profile:
+ description: "EAS build profile"
+ required: true
+ default: "preview"
+ type: choice
+ options:
+ - preview
+ - productionThen add a job-level env (or evaluate inline) to pick the profile at run time:
+env:
+ BUILD_PROFILE: ${{ github.event_name == 'push' && 'production' || inputs.profile }}- run: eas build --local --platform android --profile preview --non-interactive --output build.apk
+ run: eas build --local --platform android --profile ${{ env.BUILD_PROFILE }} --non-interactive --output build.apk- run: eas build --local --platform ios --profile preview --non-interactive --output build.ipa
+ run: eas build --local --platform ios --profile ${{ env.BUILD_PROFILE }} --non-interactive --output build.ipaAlso applies to: 117-117
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/build-apk-ipa.yml at line 70, Add a new workflow_dispatch
input named "profile" (default "preview"), create a job-level env var PROFILE
that chooses "production" when the run is a version tag (e.g.,
startsWith(github.ref, 'refs/tags/v')) otherwise uses the provided
inputs.profile or "preview", and replace the hardcoded "--profile preview" in
both eas build run steps with "--profile ${{ env.PROFILE }}"; refer to the
existing workflow_dispatch definition, the run lines that invoke "eas build
--local --platform android --profile preview --profile ..." and the job env
PROFILE to locate where to add the input, expression, and substitution.
User description
User description
Adds .github/workflows/build-apk.yml that:
https://claude.ai/code/session_01AogoKQvPk5RwVsZ52ps165
PR Type
Enhancement
Description
Add GitHub Actions workflow for building Android APK and iOS IPA
Uses
eas build --localto build natively on runnersSupports manual trigger with platform selection or automatic tag-based builds
Implements caching for Gradle and CocoaPods dependencies
Diagram Walkthrough
File Walkthrough
build-apk-ipa.yml
GitHub Actions workflow for local APK and IPA builds.github/workflows/build-apk-ipa.yml
choice) or version tags
eas build --localtimes
CodeAnt-AI Description
Add GitHub Actions workflow to produce APK and IPA artifacts locally
What Changed
Impact
✅ Builds produce downloadable APK and IPA artifacts✅ CI builds run without using EAS cloud credits✅ Manual, platform-specific workflow dispatch and tag-triggered releases💡 Usage Guide
Checking Your Pull Request
Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.
Talking to CodeAnt AI
Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:
This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.
Example
Preserve Org Learnings with CodeAnt
You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:
This helps CodeAnt AI learn and adapt to your team's coding style and standards.
Example
Retrigger review
Ask CodeAnt AI to review the PR again, by typing:
Check Your Repository Health
To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.
Summary by CodeRabbit