Files
edgeever/docs/ios-xcode-cloud.md
T
tianma-if fabfede7e0 fix(ios): stop xcodegen from archiving the previous App Store version
Cloud #50 exported 1.74.0 because project.yml hardcoded MARKETING_VERSION
and ci_post_clone regenerated the Xcode project from that YAML. Keep the
version in Version.xcconfig and stamp it into the pbxproj before archive.
2026-09-20 23:57:33 +08:00

7.5 KiB
Raw Blame History

iOS App Store builds via Xcode Cloud

Why

Local Macs on macOS beta stamp binaries with BuildMachineOSBuild from the beta OS. App Store Connect then rejects the upload as ITMS-90111 (often worded as “Unsupported SDK or Xcode version”), even when using a release Xcode.

Policy for this repo:

Work Where
Day-to-day coding, Simulator, device debug Local Mac (beta OS OK)
Archive / TestFlight / App Store binary Xcode Cloud only (started by store-delivery or a manual control workflow)

Xcode Cloud is billed as 25 compute hours per month with the Apple Developer Program. Keep the Cloud workflow start condition Manual so git pushes do not consume hours; formal Releases start it through the App Store Connect API.

Native store target: apps/ios (SwiftUI). Expo apps/mobile is not the iOS store path.

One-time setup

Xcode / App Store Connect

  1. Open the project:
    cd apps/ios
    xcodegen generate   # if needed
    open EdgeEver.xcodeproj
    
  2. Xcode → Product → Xcode Cloud → Create Workflow… (or Report navigator → Cloud).
  3. Product: EdgeEver (org.edgeever.mobile), repository tianma-if/edgeever.
  4. When asked for the project path in a monorepo, select apps/ios/EdgeEver.xcodeproj.
  5. Workflow settings (important):
    • Start condition: Manual only — do not start on every git push / PR (saves the 25h quota). Formal Releases start this workflow through the App Store Connect API.
    • Actions: Archive (scheme EdgeEver, platform iOS).
    • Deployment preparation: App Store Connect / App Store eligible (or TestFlight Internal Only).
    • Xcode / macOS: latest release — not a beta toolchain.
  6. Signing: project uses Automatic signing so Xcode Cloud can mint Development + Distribution profiles. First run may ask the Account Holder to confirm certificates in App Store Connect.
  7. Grant GitHub access for the primary repo only. Public SPM deps (GRDB, Pow) do not need Connect.

Shared secrets (auto-upload)

In App Store Connect → EdgeEver → Xcode Cloud → Settings → Shared Environment Variables, add (secret):

Variable Value
APP_STORE_CONNECT_API_KEY_ID Key ID (e.g. from Users and Access → Integrations → App Store Connect API)
APP_STORE_CONNECT_API_ISSUER_ID Issuer ID
APP_STORE_CONNECT_API_KEY_P8_BASE64 Entire .p8 file contents, base64-encoded

ci_post_xcodebuild.sh uses these to run altool after the archive so the IPA reaches ASC even if secondary Development/Ad Hoc exports fail.

Encode the key:

base64 -i AuthKey_XXXX.p8 | tr -d '\n'

Build number (source of truth)

Xcode Cloud overwrites CFBundleVersion with its managed counter:

App Store Connect → EdgeEver → Xcode Cloud → Settings → Build Number → Next build number

Rules:

  1. Next Cloud number must be greater than every build already on ASC for this app.

  2. Keep apps/ios/Config/Version.xcconfig as the only marketing-version source. Do not hardcode MARKETING_VERSION in project.yml; Cloud runs xcodegen in ci_post_clone, and a stale YAML value would archive the previous App Store version. ci_pre_xcodebuild.sh copies the xcconfig version into the generated pbxproj and stamps Cloud’s CI_BUILD_NUMBER into CURRENT_PROJECT_VERSION.

  3. Before a store run, from a machine with API credentials:

    cd apps/ios
    export APP_STORE_CONNECT_API_KEY_ID=...
    export APP_STORE_CONNECT_API_ISSUER_ID=...
    export APP_STORE_CONNECT_API_KEY_P8_BASE64=...   # or install AuthKey_*.p8 under ~/.appstoreconnect/private_keys/
    bash Scripts/ensure-xcode-cloud-build-number.sh
    

    Then set the ASC “next build number” UI to the printed recommended_next_cloud_build (or higher) if the UI is not already there.

What the repo provides

Path Role
scripts/xcode-cloud-control.py Start/monitor Cloud and wait until the App Store build is Valid
apps/ios/ci_scripts/ci_post_clone.sh bun + XcodeGen + EditorBundle + xcodegen generate
apps/ios/ci_scripts/ci_pre_xcodebuild.sh Stamp CI_BUILD_NUMBER; force Automatic signing
apps/ios/ci_scripts/ci_post_xcodebuild.sh Upload App Store IPA via altool when secrets are set
apps/ios/Scripts/upload-app-store-ipa.sh Manual upload of .ipa or Cloud app-store.zip
apps/ios/Scripts/ensure-xcode-cloud-build-number.sh Recommend / sync next build number from ASC history
apps/ios/EdgeEver.xcodeproj/xcshareddata/xcodecloud/manifest.json Cloud product linkage (commit this file)

Routine: ship a store binary

Formal Releases do this automatically when apps/ios or the shared editor runtime changed: bun run release bumps MARKETING_VERSION, starts the Manual Xcode Cloud Archive workflow, requires the Cloud source's marketing version to match the Release tag, waits until the build is Valid, and submits App Review. The Cloud workflow stays Manual, so Apple rejects starting it on a tag that is not in the start condition. project.yml must not hardcode MARKETING_VERSION; Cloud regenerates the Xcode project during ci_post_clone.

To retry or ship iOS without a new GitHub Release:

bun run publish:stores -- --release vX.Y.Z --platform ios

Pass --ios-build-number N only when the IPA is already on App Store Connect.

Manual Cloud controls remain available from .github/workflows/ios-xcode-cloud.yml (inspect / start / monitor). Keep Cloud next build number above the latest ASC build (ensure-xcode-cloud-build-number.sh) if a Cloud run stamps a too-low CFBundleVersion.

Local archive (release macOS only)

cd apps/ios
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer bash Scripts/archive-app-store.sh
bash Scripts/upload-app-store-ipa.sh build/export/EdgeEver.ipa

On macOS beta, do not upload that IPA; use Xcode Cloud instead. archive-app-store.sh refuses beta hosts unless EDGE_EVER_IOS_ALLOW_BETA_HOST=1.

Troubleshooting

Symptom Likely cause Fix
ITMS-90111 after local upload Host macOS is beta (BuildMachineOSBuild) Use Xcode Cloud only for store binaries
Cloud run FAILED but ARCHIVE_EXPORT / App Store IPA exists Development/Ad Hoc export lacks profiles Automatic signing + Cloud managed certs; ci_post_xcodebuild still uploads App Store IPA
IPA CFBundleVersion is 1 or too low Cloud “next build number” not advanced ASC → Xcode Cloud → Build Number; run ensure-xcode-cloud-build-number.sh
Build never appears on ASC Auto-upload secrets missing; or only Dev/Ad Hoc failed without App Store IPA Set shared env secrets; or manual upload-app-store-ipa.sh
Cloud fails in post-clone on bun/xcodegen Network / Homebrew on Cloud Re-run; inspect ci_post_clone log
Editor blank in app EditorBundle not built Confirm ci_post_clone log shows bundle build
Hours running out Workflow not Manual Disable push/PR start conditions
PrepareBuildForAppStoreConnect fails with the previous live version (for example 1.74.0) project.yml hardcoded MARKETING_VERSION and xcodegen overwrote Version.xcconfig Keep marketing version only in Config/Version.xcconfig; ci_pre_xcodebuild.sh must stamp it into the pbxproj
  • apps/ios/README.md — generate / test / Fastlane submit
  • apps/ios/Scripts/archive-app-store.sh — local archive
  • apps/ios/ci_scripts/* — Cloud lifecycle hooks