بنقرة واحدة
rn-harness-build
Phase 8 — EAS Build for iOS and Android, plus EAS Update (OTA) configuration.
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
Phase 8 — EAS Build for iOS and Android, plus EAS Update (OTA) configuration.
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
| name | rn-harness-build |
| description | Phase 8 — EAS Build for iOS and Android, plus EAS Update (OTA) configuration. |
| allowed-tools | ["Read","Write","Edit","Bash","Glob","Grep","AskUserQuestion"] |
Build iOS/Android binaries via EAS Build and configure EAS Update for OTA.
EAS 무료 플랜이 꽉 찼거나(3-프로젝트 제한) EAS를 쓰지 않으려면 →
rn-harness-build-local(로컬 Gradle + Xcode/fastlane, 자체 호스팅 OTA)을 대신 사용하세요.
Called by the orchestrator as Phase 8.
docs/harness/config.mdeas-cli installed (npm install -g eas-cli)eas login)# Initialize EAS if not already done
eas init
Check if eas.json exists. If not, create with all required config:
{
"cli": {
"version": ">= 15.0.0",
"appVersionSource": "remote"
},
"build": {
"development": {
"developmentClient": true,
"distribution": "internal",
"channel": "development"
},
"preview": {
"distribution": "internal",
"channel": "preview"
},
"production": {
"autoIncrement": true,
"channel": "production",
"env": {
"GRADLE_OPTS": "-Dorg.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=1g"
}
}
},
"submit": {
"production": {
"ios": {
"ascAppId": "",
"appleTeamId": ""
},
"android": {
"serviceAccountKeyPath": "",
"track": "internal"
}
}
}
}
CRITICAL: Android GRADLE_OPTS
The production.env.GRADLE_OPTS setting is mandatory. Without it, Android local builds frequently fail with OutOfMemoryError or Metaspace errors during Gradle compilation:
"env": {
"GRADLE_OPTS": "-Dorg.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=1g"
}
This allocates 4GB heap + 1GB metaspace for the Gradle JVM. Adjust if your machine has less RAM (minimum -Xmx2g).
CRITICAL: Build profile channel binding
Each build profile must declare a channel. A build only receives OTA updates from the channel it was built with — a build with no channel is subscribed to nothing, so eas update will never reach it no matter how many channels you create later. This binding (in eas.json), not eas channel:create alone, is what actually wires OTA to the app.
Channel strategy:
| Profile | Channel | Purpose |
|---|---|---|
development | development | Dev client / internal iteration |
preview | preview | Internal QA / TestFlight / internal track |
production | production | Store releases (live OTA to users) |
Keeping preview and production on separate channels means QA updates never leak to live users, and you can promote a tested update from preview → production (eas update:republish) instead of rebuilding.
Configure EAS Update for over-the-air JS bundle updates (no store re-submission needed):
# Initialize EAS Update
eas update:configure
This adds to app.config.ts:
updates: {
url: "https://u.expo.dev/[PROJECT_ID]",
},
runtimeVersion: {
policy: "appVersion",
},
And adds expo-updates to plugins:
plugins: [
"expo-router",
"expo-updates", // ← added
// ...
],
Install the dependency:
npx expo install expo-updates
Why OTA matters:
Verify all required settings:
name, slug, versionios.bundleIdentifier (from config.md)android.package (same as iOS)android.versionCodeupdates.url, runtimeVersion)By default eas build auto-generates a brand-new Android keystore on the first build. NEVER let this happen silently: if the app is already live on Google Play, a new keystore produces an AAB that Play rejects (signed with the wrong key), and the original key cannot be recovered. Always ask the user first.
AskUserQuestion — "안드로이드 앱 서명 keystore를 어떻게 할까요?":
| 선택지 | 의미 |
|---|---|
| 새로 생성 (신규 앱) | EAS가 keystore 자동 생성. 처음 출시하는 앱일 때만. |
| 기존 keystore 사용 | 보유한 .jks를 EAS에 업로드. 이미 Play에 출시된 앱이면 필수. |
| 잘 모르겠음 | Play Console에 이 앱이 이미 있으면 → 기존 사용. 완전 신규면 → 새로 생성. |
"기존 keystore 사용" 선택 시:
.jks를 credentials/keystore.jks에 배치):
credentials.json 생성:
{
"android": {
"keystore": {
"keystorePath": "credentials/keystore.jks",
"keystorePassword": "...",
"keyAlias": "...",
"keyPassword": "..."
}
}
}
.gitignore에 credentials.json, *.jks, *.keystore가 포함됐는지 확인 (비밀키는 절대 커밋 금지).eas credentials --platform android
# → Keystore → "Set up a new keystore" → "I want to upload my own file"
# → credentials.json 경로 지정 (대화형 — 사용자 안내)
(또는 credentials.json이 있으면 eas build가 로컬 파일을 그대로 사용 — 서버 업로드 없이도 동작.)"새로 생성" 선택 시: 별도 작업 없음. 첫 빌드에서 EAS가 생성한다. 생성 후 반드시 백업하도록 안내 (eas credentials → download). 이 키를 잃으면 향후 업데이트 불가.
HARD GATE: 이미 Play에 출시된 앱에 새 keystore를 쓰면 = FAIL (Play가 업로드 거부). 첫 빌드 전 사용자 확인 필수.
Local build catches errors faster than cloud build:
# Android local build first (catches Gradle/dependency issues)
eas build --local --platform android --profile production
Common Android local build failures and fixes:
| Error | Fix |
|---|---|
OutOfMemoryError / Metaspace | Increase GRADLE_OPTS in eas.json |
SDK location not found | Set ANDROID_HOME env var |
NDK not found | Install NDK via Android Studio SDK Manager |
minSdkVersion conflict | Check expo-build-properties plugin |
Duplicate class | Run cd android && ./gradlew clean |
# iOS local build (requires Xcode + macOS)
eas build --local --platform ios --profile production
Common iOS local build failures:
| Error | Fix |
|---|---|
No signing certificate | EAS manages this automatically in cloud build |
Pod install failed | cd ios && pod install --repo-update |
Xcode version mismatch | Update Xcode or set image in eas.json |
If local build fails with signing issues, skip to cloud build (EAS handles provisioning automatically).
# iOS production build
eas build --platform ios --profile production --non-interactive
# Android production build
eas build --platform android --profile production --non-interactive
Wait for build completion. Record build URLs.
eas build:list --limit 2
Channels are targeted by builds (via the channel in eas.json); branches hold the actual update commits. A channel points at a branch — by default a channel maps to a branch of the same name, which is the convention we use here.
# Create update channels (idempotent — skip any that already exist)
eas channel:create preview
eas channel:create production
# Confirm each channel is mapped to its branch
eas channel:list
# Push the first update to the production branch (→ served on the production channel)
eas update --branch production --message "Initial release" --non-interactive
# Sanity-check the build↔channel wiring: the production build must report channel "production"
eas build:list --limit 1 --platform ios --json --non-interactive | grep -i channel
If eas build:list shows a build with channel: null, the build was created before the channel field was added to eas.json — rebuild so the binding takes effect, otherwise OTA will never reach it.
docs/harness/handoff/build-result.md:
# EAS Build Result
## iOS
- Profile: production
- Status: [SUCCESS/FAILED]
- Build URL: https://expo.dev/...
- Binary: .ipa
## Android
- Profile: production
- Status: [SUCCESS/FAILED]
- Build URL: https://expo.dev/...
- Binary: .aab
## EAS Update
- Channels: preview, production
- Build channel binding: [production build → production / null]
- Runtime Version: [version]
- Status: [CONFIGURED/FAILED]
## Build Config
- bundleIdentifier: com.xxx.xxx
- package: com.xxx.xxx
- version: 1.0.0
- buildNumber/versionCode: 1
- GRADLE_OPTS: -Xmx4g -XX:MaxMetaspaceSize=1g
Git commit:
git add eas.json app.config.ts
git commit -m "chore: configure EAS build + update profiles"
current_phase: screenshot
next_role: rn-harness-screenshot
eas.json must exist with all profilesGRADLE_OPTS must be set in production build envapp.config.ts must have bundleIdentifier/packageexpo-updates installed, updates.url set)eas.json build profile must declare a channel (build↔OTA binding)production build must report channel: "production" (eas build:list) — null blocks OTA deliveryPhase 7 — Smart AdMob ad placement analysis and integration. Guides user through manual ad unit creation.
Phase 5 — Build the React Native + Expo app in 3 sub-phases (scaffold, API, UI). Self-evaluates before handoff.
Phase 7.5 — Human acceptance gate before store build. Runs an automated Maestro E2E smoke test on a real simulator/emulator, then PAUSES for the user to run the app themselves and explicitly approve before any EAS/local build or store submission.
React Native app launch harness — from market research to App Store & Google Play submission. One command takes you from idea to store review.
Phase 8 (EAS-free alternative) — Build & deploy without EAS. For users whose EAS free-plan project slots are full. Local Gradle (Android AAB) + Xcode/fastlane (iOS IPA), self-hosted OTA, then submit via Google Play API + fastlane/Transporter.
Phase 10 — Submit to App Store Connect (iOS) and Google Play (Android). iOS fully automated, Android pauses for manual steps.