fix(ios): harden Xcode Cloud store builds and auto-upload
Use Automatic signing and Cloud lifecycle scripts so App Store IPAs get correct build numbers and reach ASC even when secondary exports fail.
This commit is contained in:
1 parent
7ceed4ea16
commit
73d3bad9a3
13 files changed
+521
-76
No files matched your search
+73
-38
@@ -15,9 +15,9 @@ Xcode Cloud is billed as **25 compute hours per month** with the Apple Developer
|
||||
|
||||
Native store target: **`apps/ios`** (SwiftUI). Expo `apps/mobile` is not the iOS store path.
|
||||
|
||||
## One-time setup (Xcode UI)
|
||||
## One-time setup
|
||||
|
||||
Do this once on any Mac that can open the project (beta Mac is fine for *configuring* Cloud; Cloud still builds on Apple’s release OS images).
|
||||
### Xcode / App Store Connect
|
||||
|
||||
1. Open the project:
|
||||
```sh
|
||||
@@ -27,39 +27,75 @@ Do this once on any Mac that can open the project (beta Mac is fine for *configu
|
||||
```
|
||||
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`** (or the `apps/ios` workspace root Xcode shows).
|
||||
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).
|
||||
- **Actions:** **Archive** (App Store / TestFlight distribution).
|
||||
- **Deployment:** App Store Connect (upload the archive).
|
||||
- **Xcode version:** latest **release** (or RC when Apple requires it) — not a beta toolchain.
|
||||
6. Signing: prefer **Xcode Cloud managed signing** for Distribution, or ensure the App Store profiles already used by `ExportOptions.plist` / `project.yml` are available to the team. First Cloud run may ask the Account Holder to confirm certificates in App Store Connect.
|
||||
7. Grant GitHub access if Xcode prompts (read the monorepo so `ci_post_clone` can see `apps/ios` + `EditorSource`).
|
||||
- **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.
|
||||
|
||||
### What the repo already provides
|
||||
### Shared secrets (auto-upload)
|
||||
|
||||
After clone, Xcode Cloud runs:
|
||||
In **App Store Connect → EdgeEver → Xcode Cloud → Settings → Shared Environment Variables**, add (secret):
|
||||
|
||||
`apps/ios/ci_scripts/ci_post_clone.sh`
|
||||
| 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 |
|
||||
|
||||
That script:
|
||||
`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.
|
||||
|
||||
1. Installs **bun** (if missing) and **XcodeGen** (via Homebrew if needed)
|
||||
2. Builds the TipTap **EditorBundle** (`Scripts/build-editor-bundle.sh`)
|
||||
3. Runs **`xcodegen generate`**
|
||||
Encode the key:
|
||||
|
||||
You should not need to commit a machine-local `DerivedData` tree. SPM packages (GRDB, Pow) resolve during the Cloud build.
|
||||
```sh
|
||||
base64 -i AuthKey_XXXX.p8 | tr -d '\n'
|
||||
```
|
||||
|
||||
## Routine: submit a build (uses Cloud hours)
|
||||
### Build number (source of truth)
|
||||
|
||||
1. Land the code you want on the branch Xcode Cloud tracks (usually `main`).
|
||||
2. Bump versions if this is a store upload:
|
||||
- `apps/ios/Config/Version.xcconfig` → `MARKETING_VERSION` / `CURRENT_PROJECT_VERSION`
|
||||
- Keep monorepo release rules in `AGENTS.md` when cutting a formal GitHub Release.
|
||||
3. In Xcode (or App Store Connect → Xcode Cloud): **Start Build** on the **manual** Archive workflow only.
|
||||
4. Wait for Archive + upload to succeed.
|
||||
5. In App Store Connect → TestFlight / version page: select the new build, complete compliance, **Submit for Review**.
|
||||
6. Optional: submit metadata-only steps with Fastlane from any machine (does not re-archive):
|
||||
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` → `CURRENT_PROJECT_VERSION` in the same ballpark (the repo stamps Cloud’s `CI_BUILD_NUMBER` into the archive via `ci_pre_xcodebuild.sh`).
|
||||
3. Before a store run, from a machine with API credentials:
|
||||
|
||||
```sh
|
||||
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 |
|
||||
| --- | --- |
|
||||
| `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
|
||||
|
||||
1. Land the code on the branch Cloud tracks (usually `main`).
|
||||
2. Bump `MARKETING_VERSION` when the user-facing version changes; ensure Cloud **next build number** > latest ASC build (`ensure-xcode-cloud-build-number.sh`).
|
||||
3. Start the **manual** Archive workflow (Xcode or ASC → Xcode Cloud → Start Build).
|
||||
4. Wait for Archive. Prefer a green run; if the run is red only because Development/Ad Hoc export failed, check that `ci_post_xcodebuild` still uploaded the App Store IPA (or download `ARCHIVE_EXPORT` and run `upload-app-store-ipa.sh`).
|
||||
5. In ASC: wait until the build is **Valid**, attach it to the version, **Submit for Review**.
|
||||
|
||||
Optional (metadata already on ASC, binary already uploaded):
|
||||
|
||||
```sh
|
||||
cd apps/ios
|
||||
@@ -70,31 +106,30 @@ You should not need to commit a machine-local `DerivedData` tree. SPM packages (
|
||||
fastlane ios submit_review
|
||||
```
|
||||
|
||||
(`skip_binary_upload: true` in the lane — binary must already be on ASC from Cloud.)
|
||||
|
||||
## Local archive (release macOS only)
|
||||
|
||||
`Scripts/archive-app-store.sh` remains for machines on a **non-beta** macOS:
|
||||
|
||||
```sh
|
||||
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. The script prints a warning when it detects a beta host OS build.
|
||||
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 |
|
||||
| --- | --- |
|
||||
| ITMS-90111 after **local** upload | Host macOS is beta (`BuildMachineOSBuild`) |
|
||||
| Cloud fails in post-clone on bun/xcodegen | Network / Homebrew on Cloud; re-run; check script logs |
|
||||
| Editor blank in app | EditorBundle not built — confirm `ci_post_clone` log shows bundle build |
|
||||
| Signing errors on Cloud | Confirm Distribution cert/profile for `org.edgeever.mobile` + share extension; or enable Cloud-managed signing |
|
||||
| Hours running out | Workflow not Manual — disable push/PR start conditions |
|
||||
| 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 |
|
||||
|
||||
## Related
|
||||
|
||||
- `apps/ios/README.md` — generate / test / Fastlane submit
|
||||
- `apps/ios/Scripts/archive-app-store.sh` — local archive
|
||||
- `apps/ios/ci_scripts/ci_post_clone.sh` — Cloud prepare step
|
||||
- `apps/ios/ci_scripts/*` — Cloud lifecycle hooks
|
||||
+10
-8
@@ -88,11 +88,13 @@ explicitly requested.
|
||||
|
||||
### App Store Connect
|
||||
|
||||
EAS Build creates the signed iOS archive from the selected tag and increments
|
||||
the remote iOS build number. EAS Submit uploads that exact build to App Store
|
||||
Connect, where it becomes available to TestFlight after Apple finishes
|
||||
processing it. Fastlane then selects the exact app version and build number,
|
||||
submits it to App Review with an App Store Connect API key, and configures
|
||||
automatic release after approval. Missing metadata, agreements, review
|
||||
information, or credentials cause the workflow to fail without submitting a
|
||||
different build.
|
||||
Native iOS store binaries come from **`apps/ios`** (SwiftUI), not Expo EAS.
|
||||
On macOS beta hosts, archives must go through **Xcode Cloud** (manual Archive
|
||||
workflow) so `BuildMachineOSBuild` is a release OS image — see
|
||||
[iOS Xcode Cloud](ios-xcode-cloud.md). Cloud stamps `CFBundleVersion` from the
|
||||
product’s next build number; `ci_post_xcodebuild.sh` uploads the App Store IPA
|
||||
with an App Store Connect API key when shared environment secrets are set.
|
||||
Fastlane (`apps/ios` `submit_review`) then selects the exact app version and
|
||||
build number, submits App Review, and configures automatic release after
|
||||
approval. Missing metadata, agreements, review information, or credentials
|
||||
cause the workflow to fail without submitting a different build.
|
||||
@@ -82,8 +82,12 @@ Release。默认命令直接使用 Production;只有明确要求测试交付
|
||||
|
||||
### App Store Connect
|
||||
|
||||
EAS Build 从指定 tag 创建签名 iOS Archive,并递增远端 iOS Build Number。
|
||||
EAS Submit 将同一个构建上传到 App Store Connect;Apple 处理完成后,它会出现在
|
||||
TestFlight 中。随后 Fastlane 使用 App Store Connect API Key 精确选择相同的
|
||||
App Version 和 Build Number,提交 App Review,并设置为审核通过后自动发布。
|
||||
如果元数据、协议、审核信息或凭据不完整,工作流会失败,不会改为提交其他构建。
|
||||
原生 iOS 商店二进制来自 **`apps/ios`**(SwiftUI),不再走 Expo EAS。
|
||||
在 macOS beta 本机上,Archive 必须通过 **Xcode Cloud**(仅手动触发的 Archive
|
||||
工作流),保证 `BuildMachineOSBuild` 来自正式系统镜像——见
|
||||
[iOS Xcode Cloud](ios-xcode-cloud.md)。Cloud 用产品「下一个构建版本编号」写入
|
||||
`CFBundleVersion`;配置共享环境变量后,`ci_post_xcodebuild.sh` 会用
|
||||
App Store Connect API Key 上传 App Store IPA。随后 Fastlane(`apps/ios` 的
|
||||
`submit_review`)精确选择相同的 App Version 与 Build Number,提交 App Review,
|
||||
并设置为审核通过后自动发布。元数据、协议、审核信息或凭据不完整时工作流会失败,
|
||||
不会改为提交其他构建。
|
||||
Reference in new issue
Block a user