feat(ios): route App Store archives through Xcode Cloud on beta macOS
Add Cloud post-clone setup and docs so store binaries build on Apple's release hosts; block local archive-app-store on seed OS to avoid ITMS-90111.
This commit is contained in:
1 parent
5124ef3539
commit
7ceed4ea16
4 files changed
+197
-2
No files matched your search
+16
-2
@@ -4,6 +4,8 @@ Native SwiftUI client for EdgeEver. This replaces the React Native / Expo iOS ta
|
||||
|
||||
**Design document:** [`docs/ios-swift-rewrite.md`](../../docs/ios-swift-rewrite.md)
|
||||
|
||||
**App Store binaries on macOS beta:** use **Xcode Cloud (manual)** — [`docs/ios-xcode-cloud.md`](../../docs/ios-xcode-cloud.md).
|
||||
|
||||
## Status
|
||||
|
||||
UI/interaction is aligned with the Android RN app (`apps/mobile`):
|
||||
@@ -85,14 +87,26 @@ xcrun simctl launch booted org.edgeever.mobile \
|
||||
|
||||
## App Store archive
|
||||
|
||||
Use the stable Xcode app (not beta) so App Store Connect accepts the SDK:
|
||||
### Preferred: Xcode Cloud (manual only)
|
||||
|
||||
If the Mac runs **macOS beta**, local archives are rejected by App Store Connect (**ITMS-90111**) even with release Xcode. Day-to-day development stays local; **only store / TestFlight binaries** should use **Xcode Cloud** (25 compute hours/month with Apple Developer Program).
|
||||
|
||||
- Setup and workflow: **[`docs/ios-xcode-cloud.md`](../../docs/ios-xcode-cloud.md)**
|
||||
- Cloud prepare script: `ci_scripts/ci_post_clone.sh` (EditorBundle + `xcodegen`)
|
||||
- Workflow start condition must stay **Manual** so hours are not burned on every push
|
||||
|
||||
### Local archive (release macOS only)
|
||||
|
||||
Use the stable Xcode app (not beta) on a **non-beta** host OS:
|
||||
|
||||
```sh
|
||||
cd apps/ios
|
||||
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer bash Scripts/archive-app-store.sh
|
||||
```
|
||||
|
||||
The script builds the TipTap EditorBundle, regenerates the Xcode project, archives with the App Store distribution profiles, and exports `build/export/EdgeEver.ipa`. Upload with `xcrun altool` / Transporter, then submit the exact build with:
|
||||
On a beta host the script **exits** unless you set `EDGE_EVER_IOS_ALLOW_BETA_HOST=1` (upload will still likely fail ASC).
|
||||
|
||||
The script builds the TipTap EditorBundle, regenerates the Xcode project, archives with the App Store distribution profiles, and exports `build/export/EdgeEver.ipa`. Upload with `xcrun altool` / Transporter (or use Cloud upload), then submit the exact build with:
|
||||
|
||||
```sh
|
||||
cd apps/ios
|
||||
|
||||
@@ -16,6 +16,22 @@ if [[ -z "${DEVELOPER_DIR:-}" ]]; then
|
||||
fi
|
||||
echo "Using $(xcodebuild -version | tr '\n' ' ')"
|
||||
|
||||
# Beta host macOS stamps BuildMachineOSBuild; ASC often rejects as ITMS-90111.
|
||||
# Prefer Xcode Cloud for store binaries (docs/ios-xcode-cloud.md).
|
||||
# Heuristic: Apple seed builds usually end with a lowercase letter (e.g. 26A5368g);
|
||||
# GM/release builds typically end with digits (e.g. 24G90).
|
||||
HOST_OS_BUILD="$(sw_vers -buildVersion 2>/dev/null || true)"
|
||||
HOST_OS_VER="$(sw_vers -productVersion 2>/dev/null || true)"
|
||||
if [[ "$HOST_OS_BUILD" =~ [a-z]$ ]]; then
|
||||
echo "WARNING: Host macOS ${HOST_OS_VER} (${HOST_OS_BUILD}) looks like a seed/beta build." >&2
|
||||
echo "WARNING: App Store Connect may reject this IPA with ITMS-90111 even if Xcode is a release build." >&2
|
||||
echo "WARNING: Use Xcode Cloud (manual Archive workflow) for store uploads — see docs/ios-xcode-cloud.md" >&2
|
||||
if [[ "${EDGE_EVER_IOS_ALLOW_BETA_HOST:-0}" != "1" ]]; then
|
||||
echo "Refusing local App Store archive on beta host. Set EDGE_EVER_IOS_ALLOW_BETA_HOST=1 to override." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
cd "$ROOT"
|
||||
|
||||
if [[ "${EDGE_EVER_IOS_SKIP_EDITOR_BUILD:-0}" != "1" ]]; then
|
||||
|
||||
Executable
+65
@@ -0,0 +1,65 @@
|
||||
#!/bin/sh
|
||||
# Xcode Cloud — runs after the repository is cloned, before xcodebuild.
|
||||
# Keep this lightweight: local day-to-day builds stay on the developer Mac;
|
||||
# Cloud is only for App Store / TestFlight archives (manual workflow).
|
||||
#
|
||||
# Docs: docs/ios-xcode-cloud.md
|
||||
set -euo pipefail
|
||||
|
||||
echo "==> EdgeEver Xcode Cloud post-clone"
|
||||
|
||||
# Resolve monorepo paths. CI_PRIMARY_REPOSITORY_PATH is the Git clone root.
|
||||
REPO_ROOT="${CI_PRIMARY_REPOSITORY_PATH:-}"
|
||||
if [ -z "$REPO_ROOT" ]; then
|
||||
# Local dry-run: this file lives at apps/ios/ci_scripts/
|
||||
REPO_ROOT="$(CDPATH= cd -- "$(dirname "$0")/../../.." && pwd)"
|
||||
fi
|
||||
IOS_ROOT="$REPO_ROOT/apps/ios"
|
||||
|
||||
if [ ! -d "$IOS_ROOT" ]; then
|
||||
echo "error: expected apps/ios at $IOS_ROOT" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cd "$IOS_ROOT"
|
||||
echo "IOS_ROOT=$IOS_ROOT"
|
||||
echo "Xcode: $(xcodebuild -version | tr '\n' ' ')"
|
||||
|
||||
# --- Toolchain: bun (EditorSource) + XcodeGen ---
|
||||
export PATH="${HOME}/.bun/bin:/opt/homebrew/bin:/usr/local/bin:${PATH}"
|
||||
|
||||
if ! command -v bun >/dev/null 2>&1; then
|
||||
echo "==> Installing bun"
|
||||
curl -fsSL https://bun.sh/install | bash
|
||||
export PATH="${HOME}/.bun/bin:${PATH}"
|
||||
fi
|
||||
command -v bun >/dev/null 2>&1 || {
|
||||
echo "error: bun install failed; EditorSource build needs bun or npm" >&2
|
||||
exit 1
|
||||
}
|
||||
echo "bun: $(bun --version)"
|
||||
|
||||
if ! command -v xcodegen >/dev/null 2>&1; then
|
||||
echo "==> Installing XcodeGen"
|
||||
if command -v brew >/dev/null 2>&1; then
|
||||
brew install xcodegen
|
||||
else
|
||||
echo "error: brew not found; cannot install xcodegen" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
echo "xcodegen: $(xcodegen --version 2>/dev/null || true)"
|
||||
|
||||
# --- TipTap EditorBundle + regenerate Xcode project ---
|
||||
echo "==> Building EditorBundle"
|
||||
bash "$IOS_ROOT/Scripts/build-editor-bundle.sh"
|
||||
|
||||
echo "==> Running xcodegen generate"
|
||||
xcodegen generate
|
||||
|
||||
if [ ! -f "$IOS_ROOT/EdgeEver.xcodeproj/project.pbxproj" ]; then
|
||||
echo "error: EdgeEver.xcodeproj missing after xcodegen" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "==> post-clone complete"
|
||||
@@ -0,0 +1,100 @@
|
||||
# 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** (manual start) |
|
||||
|
||||
Xcode Cloud is billed as **25 compute hours per month** with the Apple Developer Program. Start workflows **manually** so hours go only to real store submissions.
|
||||
|
||||
Native store target: **`apps/ios`** (SwiftUI). Expo `apps/mobile` is not the iOS store path.
|
||||
|
||||
## One-time setup (Xcode UI)
|
||||
|
||||
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).
|
||||
|
||||
1. Open the project:
|
||||
```sh
|
||||
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`** (or the `apps/ios` workspace root Xcode shows).
|
||||
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`).
|
||||
|
||||
### What the repo already provides
|
||||
|
||||
After clone, Xcode Cloud runs:
|
||||
|
||||
`apps/ios/ci_scripts/ci_post_clone.sh`
|
||||
|
||||
That script:
|
||||
|
||||
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`**
|
||||
|
||||
You should not need to commit a machine-local `DerivedData` tree. SPM packages (GRDB, Pow) resolve during the Cloud build.
|
||||
|
||||
## Routine: submit a build (uses Cloud hours)
|
||||
|
||||
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):
|
||||
|
||||
```sh
|
||||
cd apps/ios
|
||||
APP_STORE_VERSION=X.Y.Z APP_STORE_BUILD_NUMBER=N \
|
||||
APP_STORE_CONNECT_API_KEY_ID=... \
|
||||
APP_STORE_CONNECT_API_ISSUER_ID=... \
|
||||
APP_STORE_CONNECT_API_KEY_P8_BASE64=... \
|
||||
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
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
## 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 |
|
||||
|
||||
## 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
|
||||
Reference in new issue
Block a user