SuperAPI AI Directory

ios-app-store-submission

General & other

View repository

Updated 10-15 20:04

About this skill

iOS Local Build & Submission

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_mcp to 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)

  1. App Store Connect → App → TestFlight tab
  2. Confirm that the build is "Finished" (processing complete)
  3. Internal Testers Check the group
  4. If it has been added automatically, testing is possible immediately
  5. If not, add the build from the "Builds" tab

Export compliance:

  • ITSAppUsesNonExemptEncryption: false If 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.ts Debug console.log such as → delete
  • Temporary debug UI (status banner, etc.) → delete
  • .env is the production value → confirm
  1. App Store Connect → App → Distribution tab
  2. Create a new version (+ button → enter version number)
  3. 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
  4. 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.handleResponse requires unwrapping

Before release (submission for review), always:

  1. Remove the Diagnose button (or turn it into a tap-count easter egg)
  2. apiClient.ts such as console.log('[API] Token obtained...') Remove debug logs such as
  3. Remove temporary debug UI components (banners, status displays, etc.)
  4. 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_test building 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.