一键导入
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 职业分类
Phase 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.
| 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 delivery