Complete workflow from local build → App Store Connect upload → TestFlight / review submission.
Basic Policy
- Make local builds the default(Do not use EAS Build)
- EAS Free tier gives 30 builds per month. It gets used up instantly with debug iterations
- EAS is based on git clone → files not in git are not included in the build → a source of accidents
- Local builds use the filesystem directly → safe, fast, unlimited
Phase 0: Pre-build checks
0.1 Identify the app
# どのアプリをビルドするか確認
ls -d */app.json
# → muednote-mobile/app.json, muedear/app.json
0.2 Check the branch
| Purpose | Branch |
|---|---|
| App Store review submission | main |
| TestFlight testing | feature branch OK |
git branch --show-current
git status
0.3 Check version and build number
# app.json から現在の version と buildNumber を確認
cat <APP_DIR>/app.json | grep -E '"version"|"buildNumber"'
Rules:
version: The version shown on the App Store (e.g., 0.8.4)buildNumber: Unique within the same version.Always set it to the existing maximum value + 1- A buildNumber already used on App Store Connect cannot be reused
- Once the version train is closed (due to rejection, etc.), the version must be increased
0.4 .env management
Important: The .env values are injected by the Metro bundler at xcodebuild archive time, not at prebuild time.
# 現在の .env を確認
cat <APP_DIR>/.env
# バックアップ(初回のみ)
cp <APP_DIR>/.env <APP_DIR>/.env.backup
| Purpose | CLERK_KEY | API_URL |
|---|---|---|
| Production (App Store) | pk_live_... |
https://mued.jp |
| Test (TestFlight) | pk_test_... |
preview deploy URL |
For TestFlight builds:
# .env を TestFlight 用に変更
# EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
# EXPO_PUBLIC_API_URL=https://<preview-url>.vercel.app
For production builds:
# .env が本番値であることを確認
# EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_live_...
# EXPO_PUBLIC_API_URL=https://mued.jp
Always restore to production values after building:
cp <APP_DIR>/.env.backup <APP_DIR>/.env
Phase 1: Local build
1.1 Install dependencies & Prebuild
cd <APP_DIR>
npm install
npx expo prebuild --clean
--clean は ios/ Delete and regenerate. Always do this.
1.2 Archive
xcodebuild -workspace ios/<SCHEME>.xcworkspace \
-scheme <SCHEME> \
-configuration Release \
-archivePath build/<SCHEME>.xcarchive \
-destination "generic/platform=iOS" \
DEVELOPMENT_TEAM=F529L4WT3V \
CODE_SIGN_STYLE=Automatic \
-allowProvisioningUpdates \
archive
Per-app values:
| App | SCHEME | workspace |
|---|---|---|
| MUEDnote | MUEDnote | ios/MUEDnote.xcworkspace |
| MUEDear | MUEDear | ios/MUEDear.xcworkspace |
1.3 Export & Upload
xcodebuild -exportArchive \
-archivePath build/<SCHEME>.xcarchive \
-exportPath build \
-exportOptionsPlist ExportOptions.plist \
-allowProvisioningUpdates
-allowProvisioningUpdates Without it, you get a "No profiles found" error.
1.4 ExportOptions.plist
Already placed in each app directory. If missing, create it:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>method</key>
<string>app-store-connect</string>
<key>signingStyle</key>
<string>automatic</string>
<key>teamID</key>
<string>F529L4WT3V</string>
<key>uploadSymbols</key>
<true/>
<key>destination</key>
<string>upload</string>
</dict>
</plist>
1.5 Restore .env after building
cp <APP_DIR>/.env.backup <APP_DIR>/.env
Phase 2: App Store Connect operations
After the upload completes, processing finishes in 5–15 minutes.
2.1 When using the Chrome extension
You can operate App Store Connect with the Claude in Chrome extension.
1. tabs_context_mcp で接続確認
2. appstoreconnect.apple.com に navigate
3. ユーザーにログインしてもらう(パスワード入力は禁止)
4. ログイン後、アプリ一覧からスクリーンショットで状態確認
Note:
- The Chrome extension connection is unstable. If it drops,
tabs_context_mcpto reconnect - Never enter the Apple ID / password. Leave it to the user
- For irreversible operations such as "Submit for Review", get user confirmation before executing
2.2 TestFlight distribution (for testing)
- App Store Connect → App → TestFlight tab
- Confirm that the build is "Finished" (processing complete)
- Internal Testers Check the group
- If it has been added automatically, testing is possible immediately
- If not, add the build from the "Builds" tab
Export compliance:
ITSAppUsesNonExemptEncryption: falseIf it is already set in app.json, it is answered automatically- If not set, manually select "No"
2.3 App Store review submission
⚠ Remove debug remnants before building:
- Diagnose button on the Settings screen → delete
apiClient.tsDebugconsole.logsuch as → delete- Temporary debug UI (status banner, etc.) → delete
.envis the production value → confirm
- App Store Connect → App → Distribution tab
- Create a new version (+ button → enter version number)
- Check required fields:
- Screenshots: Inherited from the previous version, but may need to be saved
- Build: Select the relevant build from "Add Build"
- What's New in This Version: Describe the updates in English
- Save → Add for Review → Submit for Review
Common errors:
| Error | Resolution |
|---|---|
| "Please select a build" | The build has not been saved. Add again → Save |
| "What's New in This Version is required" | Fill in What's New |
| "Screenshots are required" | For a new version, screenshots are inherited after the first save |
| "Version X's train is closed" | Bump the version (0.8.3 → 0.8.4) |
Phase 3: TestFlight Verification (UI Debugging)
In TestFlight, console.log is not visible. A debug display on the UI is essential for isolating the problem.
3.0 Implementing Debug Diagnostics for TestFlight
When testing new features or API integrations with a TestFlight build, always place Diagnose button on the Settings screen:
// Settings画面に追加
const handleDiagnose = async () => {
const lines: string[] = [];
// 1. 認証状態
lines.push(`Clerk isSignedIn: ${clerkIsSignedIn}`);
lines.push(`Clerk userId: ${clerkUserId ?? 'null'}`);
// 2. トークン取得テスト
try {
const token = await getToken();
lines.push(`Token: ${token ? `OK (len=${token.length})` : 'NULL'}`);
} catch (err: any) {
lines.push(`Token ERROR: ${err.message}`);
}
// 3. API疎通テスト(直接 fetch で生レスポンスを確認)
try {
const resp = await fetch(`${BASE_URL}/api/target-endpoint`, { headers });
const raw = await resp.json();
// apiSuccess ラップの有無を確認
lines.push(`API: ${resp.status}`);
lines.push(`Has data wrapper: ${'data' in raw}`);
lines.push(`Response keys: ${Object.keys(raw).join(', ')}`);
} catch (err: any) {
lines.push(`API ERROR: ${err.message}`);
}
Alert.alert('Diagnose', lines.join('\n'));
};
Checkpoints:
- Token is
NULL→ Clerk session issue - Token OK but API returns 401 → server-side JWT verification issue
- API 200 but data is
undefined→ response wrapper (apiSuccess) missing unwrap Has data wrapper: true→apiClient.handleResponserequires unwrapping
Before release (submission for review), always:
- Remove the Diagnose button (or turn it into a tap-count easter egg)
apiClient.tssuch asconsole.log('[API] Token obtained...')Remove debug logs such as- Remove temporary debug UI components (banners, status displays, etc.)
- After checking these, build → submit for review.Do not submit for review with debug UI left in place
Phase 4: Pre-Build Verification
4.1 Do not return null in Provider
grep -r "return null" <APP_DIR>/src/providers/ 2>/dev/null
NG: if (!isLoaded) return null;
OK: return a loading UI
4.2 ATT Plugin Configuration (apps using ads only)
grep -A5 "expo-tracking-transparency" <APP_DIR>/app.json
A bare string "expo-tracking-transparency" is NG. Object format is required.
4.3 Checking gitignore
# modules/*/ios/ が除外されていないか確認
# ios/ ではなく /ios/ (ルート限定) であること
grep "^ios/" <APP_DIR>/.gitignore
grep "^/ios/" <APP_DIR>/.gitignore
ios/ → Excludes ios/ at all levels (Swift source for native modules also disappears)
/ios/ → Excludes only the root ios/ (correct)
Per-App Quick Reference
MUEDnote
cd muednote-mobile
npm install && npx expo prebuild --clean
xcodebuild -workspace ios/MUEDnote.xcworkspace -scheme MUEDnote \
-configuration Release -archivePath build/MUEDnote.xcarchive \
-destination "generic/platform=iOS" DEVELOPMENT_TEAM=F529L4WT3V \
CODE_SIGN_STYLE=Automatic -allowProvisioningUpdates archive
xcodebuild -exportArchive -archivePath build/MUEDnote.xcarchive \
-exportPath build -exportOptionsPlist ExportOptions.plist -allowProvisioningUpdates
- Bundle ID:
com.mued.note - Apple Sign In: Yes
MUEDear
cd muedear
npm install && npx expo prebuild --clean
xcodebuild -workspace ios/MUEDear.xcworkspace -scheme MUEDear \
-configuration Release -archivePath build/MUEDear.xcarchive \
-destination "generic/platform=iOS" DEVELOPMENT_TEAM=F529L4WT3V \
CODE_SIGN_STYLE=Automatic -allowProvisioningUpdates archive
xcodebuild -exportArchive -archivePath build/MUEDear.xcarchive \
-exportPath build -exportOptionsPlist ExportOptions.plist -allowProvisioningUpdates
- Bundle ID:
com.mued.muedear - AdMob + ATT: Yes
Gotchas
- EAS Build is based on git clone: Files not in git, even if present locally, are not included in the build.
.gitignoreのios/がmodules/*/ios/There was an incident where the Swift source of - version train is closed: If a version is closed on App Store Connect due to rejection, etc., you cannot resubmit with the same version. Bump the version
- buildNumber is disposable: Once used, a buildNumber cannot be reused. Check the latest number in the App Store Connect build list before incrementing
- Always restore .env after building: For TestFlight,
pk_testbuilding for production while left as is will cause an accident - Always add --clean to prebuild: It causes old native code to remain and lead to confusion
- Screenshots for a new version need to be saved once: Right after creating a new version the screenshots appear empty, but saving them carries them over from the previous version
Common Rejection Reasons & Fixes
1. App Completeness (black screen)
Provider が null is being returned. Fix it to return a loading UI.
2. ATT Not Shown
expo-tracking-transparency plugin configuration is a bare string. Change it to object format.
3. Accurate Metadata
The screenshots do not match the actual app. Update them to the latest version.
4. Sign In Issues
The demo account information does not match the app's form. If the placeholder is "email address", write it in email format.
Resolution Center comment template
We have addressed the issue identified in the previous review:
**[Issue] (fixed)**
- Root cause: [Technical explanation]
- Fix: [What was changed]
- File changed: [File path]
Build [N] contains this fix. Thank you for your review.