ios-app-store-submission
iOS Local Build & Submission
ローカルビルド → App Store Connect アップロード → TestFlight / 審査提出の完全ワークフロー。
基本方針
- ローカルビルドをデフォルトにする(EAS Buildは使わない)
- EAS Free tier は月30ビルド。デバッグ反復で一瞬で溶ける
- EASはgit cloneベース → gitに入っていないファイルはビルドに含まれない → 事故の元
- ローカルビルドはファイルシステムを直接使う → 安全・高速・無制限
Phase 0: ビルド前チェック
0.1 アプリ特定
# どのアプリをビルドするか確認
ls -d */app.json
# → muednote-mobile/app.json, muedear/app.json
0.2 ブランチ確認
| 目的 | ブランチ |
|---|---|
| App Store 審査提出 | main |
| TestFlight テスト | feature ブランチ OK |
git branch --show-current
git status
0.3 バージョン・ビルド番号の確認
# app.json から現在の version と buildNumber を確認
cat <APP_DIR>/app.json | grep -E '"version"|"buildNumber"'
ルール:
version: App Store に表示されるバージョン(例: 0.8.4)buildNumber: 同一 version 内で一意。常に既存の最大値 + 1 にする- App Store Connect で使用済みの buildNumber は再利用不可
- version train が閉じたら(リジェクト等)、version を上げる必要あり
0.4 .env 管理
重要: .env の値は prebuild 時ではなく、xcodebuild archive 時に Metro bundler が注入する。
# 現在の .env を確認
cat <APP_DIR>/.env
# バックアップ(初回のみ)
cp <APP_DIR>/.env <APP_DIR>/.env.backup
| 用途 | CLERK_KEY | API_URL |
|---|---|---|
| 本番 (App Store) | pk_live_... |
https://mued.jp |
| テスト (TestFlight) | pk_test_... |
preview deploy URL |
TestFlight ビルドの場合:
# .env を TestFlight 用に変更
# EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
# EXPO_PUBLIC_API_URL=https://<preview-url>.vercel.app
本番ビルドの場合:
# .env が本番値であることを確認
# EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_live_...
# EXPO_PUBLIC_API_URL=https://mued.jp
ビルド後は必ず本番値に戻す:
cp <APP_DIR>/.env.backup <APP_DIR>/.env
Phase 1: ローカルビルド
1.1 依存関係インストール & Prebuild
cd <APP_DIR>
npm install
npx expo prebuild --clean
--clean は ios/ を削除して再生成する。常につける。
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
アプリ別の値:
| アプリ | 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 がないと "No profiles found" エラーになる。
1.4 ExportOptions.plist
各アプリディレクトリに配置済み。なければ作成:
<?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 ビルド後の .env 復元
cp <APP_DIR>/.env.backup <APP_DIR>/.env
Phase 2: App Store Connect 操作
アップロード完了後、5〜15分で処理完了。
2.1 Chrome 拡張を使う場合
Claude in Chrome 拡張で App Store Connect を操作できる。
1. tabs_context_mcp で接続確認
2. appstoreconnect.apple.com に navigate
3. ユーザーにログインしてもらう(パスワード入力は禁止)
4. ログイン後、アプリ一覧からスクリーンショットで状態確認
注意:
- Chrome 拡張の接続は不安定。切れたら
tabs_context_mcpで再接続 - Apple ID / パスワードは絶対に入力しない。ユーザーに任せる
- 「審査へ提出」等の不可逆操作は実行前にユーザー確認を取る
2.2 TestFlight 配信(テスト用)
- App Store Connect → アプリ → TestFlight タブ
- ビルドが「終了」(処理完了)であることを確認
- Internal Testers グループを確認
- 自動追加されていれば即テスト可能
- されていなければ「ビルド」タブからビルドを追加
輸出コンプライアンス:
ITSAppUsesNonExemptEncryption: falseが app.json に設定済みなら自動回答- 未設定の場合、手動で「いいえ」を選択
2.3 App Store 審査提出
⚠ ビルド前にデバッグ残骸を除去:
- Settings画面の Diagnose ボタン → 削除
apiClient.ts等のデバッグconsole.log→ 削除- 一時的なデバッグUI(ステータスバナー等) → 削除
.envが本番値であること → 確認
- App Store Connect → アプリ → 配信 タブ
- 新バージョンを作成(+ボタン → バージョン番号入力)
- 必須項目を確認:
- スクリーンショット: 前バージョンから引き継がれるが、保存が必要な場合あり
- ビルド: 「ビルドを追加」から該当ビルドを選択
- このバージョンの最新情報: 更新内容を英語で記載
- 保存 → 審査用に追加 → 審査へ提出
よくあるエラー:
| エラー | 対処 |
|---|---|
| 「ビルドを選択してください」 | ビルドが保存されていない。再度追加→保存 |
| 「このバージョンの最新情報は必須」 | What's New を記入 |
| 「スクリーンショットが必要」 | 新バージョンは初回保存後にスクショが引き継がれる |
| 「バージョンXのトレインはクローズ」 | version を上げる(0.8.3 → 0.8.4) |
Phase 3: TestFlight 検証(UIデバッグ)
TestFlight では console.log は見えない。 問題の切り分けにはUI上のデバッグ表示が必須。
3.0 TestFlight 向けデバッグ診断の実装
TestFlight ビルドで新機能やAPI連携をテストする場合、Settings画面に Diagnose ボタン を必ず設置する:
// 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'));
};
確認ポイント:
- トークンが
NULL→ Clerk セッションの問題 - トークン OK だが API が 401 → サーバー側のJWT検証の問題
- API 200 だがデータが
undefined→ レスポンスラッパー(apiSuccess)の unwrap 漏れ Has data wrapper: true→apiClient.handleResponseでアンラップが必要
リリース(審査提出)前に必ず:
- Diagnose ボタンを削除(またはタップ回数イースターエッグ化)
apiClient.ts等のconsole.log('[API] Token obtained...')等のデバッグログを削除- デバッグ用の一時UIコンポーネント(バナー、ステータス表示等)を削除
- これらを確認してからビルド → 審査提出する。デバッグUIが残ったまま審査提出しない
Phase 4: ビルド前検証
4.1 Provider で null を返さない
grep -r "return null" <APP_DIR>/src/providers/ 2>/dev/null
NG: if (!isLoaded) return null;
OK: ローディング UI を返す
4.2 ATT プラグイン設定(広告使用アプリのみ)
grep -A5 "expo-tracking-transparency" <APP_DIR>/app.json
ベア文字列 "expo-tracking-transparency" はNG。オブジェクト形式が必須。
4.3 gitignore の確認
# modules/*/ios/ が除外されていないか確認
# ios/ ではなく /ios/ (ルート限定) であること
grep "^ios/" <APP_DIR>/.gitignore
grep "^/ios/" <APP_DIR>/.gitignore
ios/ → 全階層の ios/ を除外(ネイティブモジュールのSwiftソースも消える)
/ios/ → ルートの ios/ のみ除外(正しい)
アプリ別クイックリファレンス
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: あり
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: あり
Gotchas
- EAS Build は git clone ベース: ローカルにあっても git に入っていないファイルはビルドに含まれない。
.gitignoreのios/がmodules/*/ios/の Swift ソースまで除外していた事故例あり - version train がクローズ: App Store Connect でバージョンがリジェクト等でクローズされたら、同じバージョンでは再提出不可。version を上げる
- buildNumber は使い捨て: 一度使った buildNumber は再利用不可。App Store Connect のビルド一覧で最新番号を確認してからインクリメント
- .env はビルド後に必ず戻す: TestFlight用の
pk_testのまま本番ビルドすると事故る - prebuild の --clean は常につける: 古いネイティブコードが残って混乱する原因になる
- 新バージョンのスクショは一度保存が必要: 新バージョン作成直後はスクショが空に見えるが、保存すると前バージョンから引き継がれる
Common Rejection Reasons & Fixes
1. App Completeness(黒い画面)
Provider が null を返している。ローディング UI を返すように修正。
2. ATT Not Shown
expo-tracking-transparency のプラグイン設定がベア文字列。オブジェクト形式に変更。
3. Accurate Metadata
スクリーンショットが実際のアプリと一致しない。最新版に更新。
4. Sign In Issues
デモアカウント情報がアプリのフォームと一致しない。プレースホルダーが「メールアドレス」ならメール形式で記載。
Resolution Center コメントテンプレート
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.