diff --git a/apps/ios/CHANGELOG.md b/apps/ios/CHANGELOG.md index be4184c92c32..a33413f82b61 100644 --- a/apps/ios/CHANGELOG.md +++ b/apps/ios/CHANGELOG.md @@ -58,11 +58,11 @@ Maintenance update for the current OpenClaw release. ## 2026.5.12 - 2026-05-12 -Maintenance update for the current OpenClaw beta release. +Maintenance update for the current OpenClaw release. ## 2026.5.10 - 2026-05-10 -Maintenance update for the current OpenClaw beta release. +Maintenance update for the current OpenClaw release. - Gateway connections now recover after a trusted Gateway certificate changes by refreshing the stored certificate pin during reconnect. @@ -128,7 +128,7 @@ Maintenance update for the current OpenClaw release. ## 2026.4.19 - 2026-04-19 -Maintenance update for the current OpenClaw beta release. +Maintenance update for the current OpenClaw release. ## 2026.4.18 - 2026-04-18 @@ -136,11 +136,11 @@ Maintenance update for the current OpenClaw release. ## 2026.4.15 - 2026-04-15 -Maintenance update for the current OpenClaw beta release. +Maintenance update for the current OpenClaw release. ## 2026.4.14 - 2026-04-14 -Maintenance update for the current OpenClaw beta release. +Maintenance update for the current OpenClaw release. ## 2026.4.12 - 2026-04-12 diff --git a/apps/ios/Config/Signing.xcconfig b/apps/ios/Config/Signing.xcconfig index 646bba255fc9..d2f53c8a52cc 100644 --- a/apps/ios/Config/Signing.xcconfig +++ b/apps/ios/Config/Signing.xcconfig @@ -5,6 +5,7 @@ OPENCLAW_IOS_DEFAULT_TEAM = FWJYW4S8P8 OPENCLAW_IOS_SELECTED_TEAM = $(OPENCLAW_IOS_DEFAULT_TEAM) OPENCLAW_DEVELOPMENT_TEAM = $(OPENCLAW_IOS_SELECTED_TEAM) OPENCLAW_CODE_SIGN_STYLE = Automatic +OPENCLAW_CODE_SIGN_IDENTITY = Apple Development OPENCLAW_APP_BUNDLE_ID = ai.openclawfoundation.app OPENCLAW_WATCH_APP_BUNDLE_ID = ai.openclawfoundation.app.watchkitapp OPENCLAW_WATCH_EXTENSION_BUNDLE_ID = ai.openclawfoundation.app.watchkitapp.extension @@ -18,7 +19,7 @@ OPENCLAW_WATCH_EXTENSION_PROFILE = #include? "../LocalSigning.xcconfig" CODE_SIGN_STYLE = $(OPENCLAW_CODE_SIGN_STYLE) -CODE_SIGN_IDENTITY = Apple Development +CODE_SIGN_IDENTITY = $(OPENCLAW_CODE_SIGN_IDENTITY) DEVELOPMENT_TEAM = $(OPENCLAW_DEVELOPMENT_TEAM) // Let Xcode manage provisioning for the selected local team unless a local override pins one. diff --git a/apps/ios/LocalSigning.xcconfig.example b/apps/ios/LocalSigning.xcconfig.example index 3c990f6af603..ba05becf94e6 100644 --- a/apps/ios/LocalSigning.xcconfig.example +++ b/apps/ios/LocalSigning.xcconfig.example @@ -2,6 +2,7 @@ // This file is only an example and should stay committed. OPENCLAW_CODE_SIGN_STYLE = Automatic +OPENCLAW_CODE_SIGN_IDENTITY = Apple Development OPENCLAW_DEVELOPMENT_TEAM = YOUR_TEAM_ID OPENCLAW_APP_BUNDLE_ID = ai.openclawfoundation.app diff --git a/apps/ios/README.md b/apps/ios/README.md index cca91ecf4d07..d21df0b1617a 100644 --- a/apps/ios/README.md +++ b/apps/ios/README.md @@ -4,8 +4,8 @@ This iOS app is super-alpha and internal-use only. The first public App Store re ## Distribution Status -- Public distribution: not available. -- Internal beta distribution: local archive + TestFlight upload via Fastlane. +- Public distribution: App Store Connect app created; production signing is configured through the App Store release Fastlane path. +- Internal TestFlight distribution: uses the same App Store distribution archive uploaded to App Store Connect. - Local/manual deploy from source via Xcode remains the default development path. ## Super-Alpha Disclaimer @@ -47,7 +47,7 @@ Shortcut command (same flow + open project): pnpm ios:open ``` -## Local Beta Release Flow +## App Store Release Flow Prereqs: @@ -55,52 +55,61 @@ Prereqs: - `pnpm` - `xcodegen` - `fastlane` -- Apple account signed into Xcode for automatic signing/provisioning -- App Store Connect API key set up in Keychain via `scripts/ios-asc-keychain-setup.sh` when auto-resolving a beta build number or uploading to TestFlight +- Apple account signed into Xcode for the canonical OpenClaw team (`FWJYW4S8P8`) +- Xcode account permissions to create Apple Distribution certificates, App IDs, and App Store provisioning profiles +- App Store Connect app already created for `ai.openclawfoundation.app` +- App Store Connect API key set up in Keychain via `scripts/ios-asc-keychain-setup.sh` when auto-resolving a build number or uploading to App Store Connect Release behavior: - Local development uses the canonical `ai.openclawfoundation.app*` bundle IDs when the OpenClaw team is available, and unique `ai.openclawfoundation.app.test.*` bundle IDs only for non-canonical fallback teams. -- Beta release uses canonical `ai.openclawfoundation.app*` bundle IDs through a temporary generated xcconfig in `apps/ios/build/BetaRelease.xcconfig`. -- Beta release also switches the app to `OpenClawPushTransport=relay`, `OpenClawPushDistribution=official`, and `OpenClawPushAPNsEnvironment=production`. +- App Store release uses canonical `ai.openclawfoundation.app*` bundle IDs through a temporary generated xcconfig in `apps/ios/build/AppStoreRelease.xcconfig`. +- App Store release uses automatic signing with `Apple Distribution` and `-allowProvisioningUpdates`, so Xcode can create missing distribution certificates/profiles for the canonical team when the signed-in account has permission. +- App Store release also switches the app to `OpenClawPushTransport=relay`, `OpenClawPushDistribution=official`, `OpenClawPushAPNsEnvironment=production`, and a production `aps-environment` entitlement. - App Store screenshots use `pnpm ios:screenshots`, which drives Fastlane Snapshot through a deterministic connected screenshot fixture instead of a live gateway. -- The beta flow does not modify `apps/ios/.local-signing.xcconfig` or `apps/ios/LocalSigning.xcconfig`. +- The release flow does not modify `apps/ios/.local-signing.xcconfig` or `apps/ios/LocalSigning.xcconfig`. - `apps/ios/version.json` is the pinned iOS release version source. - `apps/ios/CHANGELOG.md` is the iOS-only changelog and release-note source. - The pinned iOS version must use CalVer like `2026.4.10`. - That pinned value becomes: - `CFBundleShortVersionString = 2026.4.10` - - `CFBundleVersion = next TestFlight build number for 2026.4.10` + - `CFBundleVersion = next App Store Connect build number for 2026.4.10` - Changing the root gateway version does not change the iOS app version until you explicitly pin from the gateway. - See `apps/ios/VERSIONING.md` for the full workflow. -Relay behavior for beta builds: +Relay behavior for App Store builds: - Beta builds default to `https://ios-push-relay.openclaw.ai`. - Optional custom relay override: `OPENCLAW_PUSH_RELAY_BASE_URL=https://relay.example.com` This must be a plain `https://host[:port][/path]` base URL without whitespace, query params, fragments, or xcconfig metacharacters. +Prepare the generated release xcconfig/project without archiving: + +```bash +pnpm ios:release:prepare -- --build-number 7 +``` + Archive without upload: ```bash -pnpm ios:beta:archive +pnpm ios:release:archive ``` -Archive and upload to TestFlight: +Archive and upload to App Store Connect: ```bash -pnpm ios:beta +pnpm ios:release ``` If you need to force a specific build number: ```bash -pnpm ios:beta -- --build-number 7 +pnpm ios:release -- --build-number 7 ``` ### Maintainer Quick Release Checklist -Use this when a clone is missing local iOS release setup and you want the shortest path to a TestFlight upload. +Use this when a clone is missing local iOS release setup and you want the shortest path to an App Store Connect upload. 1. Confirm Fastlane auth is set up: @@ -120,38 +129,45 @@ scripts/ios-asc-keychain-setup.sh \ This should create `apps/ios/fastlane/.env` with the non-secret ASC variables while the private key stays in Keychain. -3. Optional: set a custom official/TestFlight relay URL for the build. If unset, the beta flow uses `https://ios-push-relay.openclaw.ai`. +3. Confirm the App Store Connect app and Apple Developer identifiers/capabilities exist for: + - `ai.openclawfoundation.app` + - `ai.openclawfoundation.app.share` + - `ai.openclawfoundation.app.activitywidget` + - `ai.openclawfoundation.app.watchkitapp` + - `ai.openclawfoundation.app.watchkitapp.extension` + +4. Optional: set a custom official relay URL for the build. If unset, the release flow uses `https://ios-push-relay.openclaw.ai`. ```bash export OPENCLAW_PUSH_RELAY_BASE_URL=https://relay.example.com ``` -4. If you are starting a brand-new production release train, pin iOS to the current gateway version first: +5. If you are starting a brand-new production release train, pin iOS to the current gateway version first: ```bash pnpm ios:version:pin -- --from-gateway ``` -5. Upload the beta: +6. Upload the build: ```bash -pnpm ios:beta +pnpm ios:release ``` -6. Expected behavior: +7. Expected behavior: - Fastlane reads `apps/ios/version.json` - verifies synced iOS versioning artifacts - - resolves the next TestFlight build number for that short version - - generates `apps/ios/build/BetaRelease.xcconfig` + - resolves the next App Store Connect build number for that short version + - generates `apps/ios/build/AppStoreRelease.xcconfig` - archives `OpenClaw` - - uploads the IPA to TestFlight + - uploads the IPA to App Store Connect for TestFlight/App Review use -7. Expected outputs after a successful run: - - `apps/ios/build/beta/OpenClaw-.ipa` - - `apps/ios/build/beta/OpenClaw-.app.dSYM.zip` - - Fastlane log line like `Uploaded iOS beta: version= short= build=` +8. Expected outputs after a successful run: + - `apps/ios/build/app-store/OpenClaw-.ipa` + - `apps/ios/build/app-store/OpenClaw-.app.dSYM.zip` + - Fastlane log line like `Uploaded iOS App Store build: version= short= build=` -8. If this is a fresh clone on a maintainer machine that already works elsewhere, it is OK to copy the non-secret `apps/ios/fastlane/.env` from another trusted local clone on the same Mac. The Keychain-backed private key remains machine-local and is not stored in the repo. +9. If this is a fresh clone on a maintainer machine that already works elsewhere, it is OK to copy the non-secret `apps/ios/fastlane/.env` from another trusted local clone on the same Mac. The Keychain-backed private key remains machine-local and is not stored in the repo. ## iOS Versioning Workflow @@ -177,7 +193,7 @@ Recommended flow: 1. Keep `apps/ios/version.json` pinned to the current train version. 2. Update `apps/ios/CHANGELOG.md`, usually under `## Unreleased` while iterating. 3. Run `pnpm ios:version:sync` after changelog changes. -4. Upload more TestFlight builds with `pnpm ios:beta`. +4. Upload more TestFlight builds with `pnpm ios:release`. 5. Let Fastlane bump only the numeric build number. ### Starting the next production release train @@ -190,7 +206,7 @@ pnpm ios:version:pin -- --from-gateway 2. Update `apps/ios/CHANGELOG.md` for the new release as needed. 3. Run `pnpm ios:version:sync`. -4. Submit the first TestFlight build for that newly pinned version. +4. Submit the first App Store Connect build for that newly pinned version. 5. Keep iterating on that same version until the release candidate is ready. See `apps/ios/VERSIONING.md` for the detailed spec. @@ -198,9 +214,9 @@ See `apps/ios/VERSIONING.md` for the detailed spec. ## APNs Expectations For Local/Manual Builds - The app calls `registerForRemoteNotifications()` at launch. -- `apps/ios/Sources/OpenClaw.entitlements` sets `aps-environment` to `development`. +- `apps/ios/Sources/OpenClaw.entitlements` derives `aps-environment` from the active build configuration/signing override. - APNs token registration to gateway happens only after gateway connection (`push.apns.register`). -- Local/manual builds default to `OpenClawPushTransport=direct` and `OpenClawPushDistribution=local`. +- Local/manual builds default to `OpenClawPushTransport=direct`, `OpenClawPushDistribution=local`, and a development `aps-environment` entitlement. - Your selected team/profile must support Push Notifications for the app bundle ID you are signing. - If push capability or provisioning is wrong, APNs registration fails at runtime (check Xcode logs for `APNs registration failed`). - The gateway host also needs direct APNs auth configured separately with `OPENCLAW_APNS_TEAM_ID`, `OPENCLAW_APNS_KEY_ID`, and either `OPENCLAW_APNS_PRIVATE_KEY_P8` or `OPENCLAW_APNS_PRIVATE_KEY_PATH`. diff --git a/apps/ios/Signing.xcconfig b/apps/ios/Signing.xcconfig index 9c49f55e62dc..b57da1f0cead 100644 --- a/apps/ios/Signing.xcconfig +++ b/apps/ios/Signing.xcconfig @@ -5,6 +5,7 @@ #include "Config/Version.xcconfig" OPENCLAW_CODE_SIGN_STYLE = Manual +OPENCLAW_CODE_SIGN_IDENTITY = Apple Development OPENCLAW_DEVELOPMENT_TEAM = FWJYW4S8P8 OPENCLAW_APP_BUNDLE_ID = ai.openclawfoundation.app @@ -12,6 +13,7 @@ OPENCLAW_SHARE_BUNDLE_ID = ai.openclawfoundation.app.share OPENCLAW_WATCH_APP_BUNDLE_ID = ai.openclawfoundation.app.watchkitapp OPENCLAW_WATCH_EXTENSION_BUNDLE_ID = ai.openclawfoundation.app.watchkitapp.extension OPENCLAW_ACTIVITY_WIDGET_BUNDLE_ID = ai.openclawfoundation.app.activitywidget +OPENCLAW_APNS_ENTITLEMENT_ENVIRONMENT = development OPENCLAW_APP_PROFILE = ai.openclawfoundation.app Development OPENCLAW_SHARE_PROFILE = ai.openclawfoundation.app.share Development diff --git a/apps/ios/Sources/OpenClaw.entitlements b/apps/ios/Sources/OpenClaw.entitlements index a2663ce930be..ff60a879b1a2 100644 --- a/apps/ios/Sources/OpenClaw.entitlements +++ b/apps/ios/Sources/OpenClaw.entitlements @@ -3,7 +3,6 @@ aps-environment - development + $(OPENCLAW_APNS_ENTITLEMENT_ENVIRONMENT) - diff --git a/apps/ios/VERSIONING.md b/apps/ios/VERSIONING.md index 6d7b7f49c80e..040c960309c6 100644 --- a/apps/ios/VERSIONING.md +++ b/apps/ios/VERSIONING.md @@ -64,7 +64,7 @@ Pinned iOS version `2026.4.10` maps to: - `apps/ios/fastlane/metadata/en-US/release_notes.txt` - generated from `apps/ios/CHANGELOG.md` - `apps/ios/build/Version.xcconfig` - - local gitignored build override generated per build or beta prep + - local gitignored build override generated per build or release prep ## Tooling surfaces @@ -81,16 +81,16 @@ Pinned iOS version `2026.4.10` maps to: - `scripts/ios-pin-version.ts` - explicitly pins iOS to a chosen release version or the current gateway version -### Build and beta flow +### Build and App Store release flow - `scripts/ios-write-version-xcconfig.sh` - reads the pinned iOS version - writes the local numeric build override file in `apps/ios/build/Version.xcconfig` -- `scripts/ios-beta-prepare.sh` - - prepares beta signing and bundle settings against the pinned iOS version +- `scripts/ios-release-prepare.sh` + - prepares App Store distribution signing and bundle settings against the pinned iOS version - `apps/ios/fastlane/Fastfile` - resolves version metadata from the pinned iOS helper - - increments TestFlight build numbers for the pinned short version + - increments App Store Connect build numbers for the pinned short version ## Release-note resolution order @@ -118,7 +118,7 @@ pnpm ios:version:pin -- --version 2026.4.10 1. keep `apps/ios/version.json` pinned to the current TestFlight train version 2. update `apps/ios/CHANGELOG.md` under `## Unreleased` while iterating -3. upload more betas with the usual flow +3. upload more App Store Connect builds with the usual flow 4. let Fastlane increment only `CFBundleVersion` This keeps the TestFlight version stable while review is in flight. @@ -139,7 +139,7 @@ pnpm ios:version:pin -- --from-gateway - `apps/ios/fastlane/metadata/en-US/release_notes.txt` 3. update `apps/ios/CHANGELOG.md` for the new release if needed 4. run `pnpm ios:version:sync` again if the changelog changed -5. submit the first TestFlight build for that newly pinned version +5. submit the first App Store Connect build for that newly pinned version 6. keep iterating only by build number until the release candidate is ready 7. release that reviewed TestFlight build to production diff --git a/apps/ios/fastlane/Fastfile b/apps/ios/fastlane/Fastfile index 93c37fb71b00..83dcc2caf155 100644 --- a/apps/ios/fastlane/Fastfile +++ b/apps/ios/fastlane/Fastfile @@ -4,7 +4,7 @@ require "json" default_platform(:ios) -BETA_APP_IDENTIFIER = "ai.openclawfoundation.app" +APP_STORE_APP_IDENTIFIER = "ai.openclawfoundation.app" DEFAULT_SNAPSHOT_DEVICES = ["iPhone 16 Pro Max", "iPad Pro 13-inch (M4)"].freeze SNAPSHOT_STATUS_BAR_ARGUMENTS = "--time 09:41 --dataNetwork wifi --wifiMode active --wifiBars 3 --cellularMode active --cellularBars 4 --batteryState charged --batteryLevel 100".freeze REQUIRED_SCREENSHOT_FAMILIES = { @@ -178,45 +178,45 @@ def shell_join(parts) Shellwords.join(parts.compact) end -def resolve_beta_build_number(api_key:, short_version:) - explicit = ENV["IOS_BETA_BUILD_NUMBER"] +def resolve_release_build_number(api_key:, short_version:) + explicit = ENV["IOS_RELEASE_BUILD_NUMBER"] if env_present?(explicit) - UI.user_error!("Invalid IOS_BETA_BUILD_NUMBER '#{explicit}'. Expected digits only.") unless explicit.match?(/\A\d+\z/) - UI.message("Using explicit iOS beta build number #{explicit}.") + UI.user_error!("Invalid iOS release build number '#{explicit}'. Expected digits only.") unless explicit.match?(/\A\d+\z/) + UI.message("Using explicit iOS release build number #{explicit}.") return explicit end latest_build = latest_testflight_build_number( api_key: api_key, - app_identifier: BETA_APP_IDENTIFIER, + app_identifier: APP_STORE_APP_IDENTIFIER, version: short_version, initial_build_number: 0 ) next_build = latest_build.to_i + 1 - UI.message("Resolved iOS beta build number #{next_build} for #{short_version} (latest TestFlight build: #{latest_build}).") + UI.message("Resolved iOS release build number #{next_build} for #{short_version} (latest App Store Connect build: #{latest_build}).") next_build.to_s end -def beta_build_number_needs_asc_auth? - explicit = ENV["IOS_BETA_BUILD_NUMBER"] +def release_build_number_needs_asc_auth? + explicit = ENV["IOS_RELEASE_BUILD_NUMBER"] !env_present?(explicit) end -def prepare_beta_release!(version:, build_number:) - script_path = File.join(repo_root, "scripts", "ios-beta-prepare.sh") - UI.message("Preparing iOS beta release #{version} (build #{build_number}).") +def prepare_app_store_release!(version:, build_number:) + script_path = File.join(repo_root, "scripts", "ios-release-prepare.sh") + UI.message("Preparing iOS App Store release #{version} (build #{build_number}).") sh(shell_join(["bash", script_path, "--build-number", build_number])) - beta_xcconfig = File.join(ios_root, "build", "BetaRelease.xcconfig") - UI.user_error!("Missing beta xcconfig at #{beta_xcconfig}.") unless File.exist?(beta_xcconfig) + release_xcconfig = File.join(ios_root, "build", "AppStoreRelease.xcconfig") + UI.user_error!("Missing App Store release xcconfig at #{release_xcconfig}.") unless File.exist?(release_xcconfig) - ENV["XCODE_XCCONFIG_FILE"] = beta_xcconfig - beta_xcconfig + ENV["XCODE_XCCONFIG_FILE"] = release_xcconfig + release_xcconfig end -def build_beta_release(context) +def build_app_store_release(context) version = context[:version] - output_directory = File.join("build", "beta") + output_directory = File.join("build", "app-store") archive_path = File.join(output_directory, "OpenClaw-#{version}.xcarchive") build_app( @@ -294,40 +294,40 @@ platform :ios do api_key end - private_lane :prepare_beta_context do |options| + private_lane :prepare_app_store_context do |options| require_api_key = options[:require_api_key] == true - needs_api_key = require_api_key || beta_build_number_needs_asc_auth? + needs_api_key = require_api_key || release_build_number_needs_asc_auth? api_key = needs_api_key ? asc_api_key : nil sync_ios_versioning! version_metadata = read_ios_version_metadata version = version_metadata[:version] short_version = version_metadata[:short_version] - build_number = resolve_beta_build_number(api_key: api_key, short_version: short_version) - beta_xcconfig = prepare_beta_release!(version: version, build_number: build_number) + build_number = resolve_release_build_number(api_key: api_key, short_version: short_version) + release_xcconfig = prepare_app_store_release!(version: version, build_number: build_number) { api_key: api_key, - beta_xcconfig: beta_xcconfig, build_number: build_number, + release_xcconfig: release_xcconfig, short_version: short_version, version: version } end - desc "Build a beta archive locally without uploading" - lane :beta_archive do - context = prepare_beta_context(require_api_key: false) - build = build_beta_release(context) - UI.success("Built iOS beta archive: version=#{build[:version]} short=#{build[:short_version]} build=#{build[:build_number]}") + desc "Build an App Store distribution archive locally without uploading" + lane :app_store_archive do + context = prepare_app_store_context(require_api_key: false) + build = build_app_store_release(context) + UI.success("Built iOS App Store archive: version=#{build[:version]} short=#{build[:short_version]} build=#{build[:build_number]}") build ensure ENV.delete("XCODE_XCCONFIG_FILE") end - desc "Build + upload a beta to TestFlight" - lane :beta do - context = prepare_beta_context(require_api_key: true) - build = build_beta_release(context) + desc "Build + upload an App Store distribution build to App Store Connect" + lane :app_store do + context = prepare_app_store_context(require_api_key: true) + build = build_app_store_release(context) upload_to_testflight( api_key: context[:api_key], @@ -336,7 +336,7 @@ platform :ios do uses_non_exempt_encryption: false ) - UI.success("Uploaded iOS beta: version=#{build[:version]} short=#{build[:short_version]} build=#{build[:build_number]}") + UI.success("Uploaded iOS App Store build: version=#{build[:version]} short=#{build[:short_version]} build=#{build[:build_number]}") ensure ENV.delete("XCODE_XCCONFIG_FILE") end diff --git a/apps/ios/fastlane/SETUP.md b/apps/ios/fastlane/SETUP.md index fb836a0c307d..aedf60f2e635 100644 --- a/apps/ios/fastlane/SETUP.md +++ b/apps/ios/fastlane/SETUP.md @@ -29,7 +29,7 @@ ASC_KEYCHAIN_SERVICE=openclaw-asc-key ASC_KEYCHAIN_ACCOUNT=YOUR_MAC_USERNAME ``` -Important: `apps/ios/fastlane/.env` is only for Fastlane/App Store Connect auth and optional beta-archive settings. It does **not** configure gateway-side direct APNs push delivery for local iOS builds. +Important: `apps/ios/fastlane/.env` is only for Fastlane/App Store Connect auth and optional release-archive settings. It does **not** configure gateway-side direct APNs push delivery for local iOS builds. Optional app targeting variables (helpful if Fastlane cannot auto-resolve app by bundle): @@ -53,7 +53,7 @@ Code signing variable (optional in `.env`): IOS_DEVELOPMENT_TEAM=YOUR_TEAM_ID ``` -Tip: run `scripts/ios-team-id.sh --require-canonical` from repo root to verify the canonical OpenClaw iOS team (`FWJYW4S8P8`) is available locally. Fastlane uses the same canonical-only path when `IOS_DEVELOPMENT_TEAM` is missing, and rejects non-canonical teams for beta archives. +Tip: run `scripts/ios-team-id.sh --require-canonical` from repo root to verify the canonical OpenClaw iOS team (`FWJYW4S8P8`) is available locally. Fastlane uses the same canonical-only path when `IOS_DEVELOPMENT_TEAM` is missing, and rejects non-canonical teams for release archives. For local/manual iOS builds that stay on direct APNs, configure the gateway host separately with `OPENCLAW_APNS_TEAM_ID`, `OPENCLAW_APNS_KEY_ID`, and either `OPENCLAW_APNS_PRIVATE_KEY_P8` or `OPENCLAW_APNS_PRIVATE_KEY_PATH`. Those gateway runtime env vars are separate from Fastlane's `.env`. @@ -66,15 +66,15 @@ fastlane ios auth_check ASC auth is only required when: -- uploading to TestFlight +- uploading to App Store Connect - auto-resolving the next build number from App Store Connect -If you pass `--build-number` to `pnpm ios:beta:archive`, the local archive path does not need ASC auth. +If you pass `--build-number` to `pnpm ios:release:archive`, the local archive path does not need ASC auth. Archive locally without upload: ```bash -pnpm ios:beta:archive +pnpm ios:release:archive ``` Generate deterministic App Store screenshots: @@ -85,17 +85,17 @@ pnpm ios:screenshots The screenshot lane runs the app with `--openclaw-screenshot-mode`, which enters the built-in connected screenshot fixture instead of pairing with a live gateway. By default it captures the tab set on `iPhone 16 Pro Max` and `iPad Pro 13-inch (M4)`; override devices with a comma-separated `OPENCLAW_SNAPSHOT_DEVICES` value when the requested simulators exist locally. -Upload to TestFlight: +Upload to App Store Connect: ```bash -pnpm ios:beta +pnpm ios:release ``` Direct Fastlane entry point: ```bash cd apps/ios -fastlane ios beta +fastlane ios app_store ``` Maintainer recovery path for a fresh clone on the same Mac: @@ -123,7 +123,7 @@ fastlane ios auth_check pnpm ios:version:pin -- --from-gateway ``` -5. Set the official/TestFlight relay URL before release: +5. Set the official relay URL before release: ```bash export OPENCLAW_PUSH_RELAY_BASE_URL=https://relay.example.com @@ -132,14 +132,14 @@ export OPENCLAW_PUSH_RELAY_BASE_URL=https://relay.example.com 6. Upload: ```bash -pnpm ios:beta +pnpm ios:release ``` Quick verification after upload: -- confirm `apps/ios/build/beta/OpenClaw-.ipa` exists -- confirm Fastlane prints `Uploaded iOS beta: version= short= build=` -- remember that TestFlight processing can take a few minutes after the upload succeeds +- confirm `apps/ios/build/app-store/OpenClaw-.ipa` exists +- confirm Fastlane prints `Uploaded iOS App Store build: version= short= build=` +- remember that App Store Connect/TestFlight processing can take a few minutes after the upload succeeds Versioning rules: @@ -149,9 +149,9 @@ Versioning rules: - `pnpm ios:version:pin -- --from-gateway` promotes the current root gateway version into the pinned iOS release version - Fastlane uses the pinned iOS version only; changing `package.json.version` alone does not change the iOS app version - Fastlane sets `CFBundleShortVersionString` to the pinned iOS version, for example `2026.4.10` -- Fastlane resolves `CFBundleVersion` as the next integer TestFlight build number for that short version +- Fastlane resolves `CFBundleVersion` as the next integer App Store Connect build number for that short version - Run `pnpm ios:version:sync` after changing `apps/ios/version.json` or `apps/ios/CHANGELOG.md` - `pnpm ios:version:check` validates that checked-in iOS version artifacts are in sync -- The beta flow regenerates `apps/ios/OpenClaw.xcodeproj` from `apps/ios/project.yml` before archiving -- Local beta signing uses a temporary generated xcconfig and leaves local development signing overrides untouched +- The release flow regenerates `apps/ios/OpenClaw.xcodeproj` from `apps/ios/project.yml` before archiving +- Local App Store signing uses a temporary generated xcconfig and leaves local development signing overrides untouched - See `apps/ios/VERSIONING.md` for the detailed workflow diff --git a/apps/ios/project.yml b/apps/ios/project.yml index 0d6a67514259..d9e4b31ce3f2 100644 --- a/apps/ios/project.yml +++ b/apps/ios/project.yml @@ -99,7 +99,7 @@ targets: swiftlint lint --config "$SRCROOT/.swiftlint.yml" --use-script-input-file-lists settings: base: - CODE_SIGN_IDENTITY: "Apple Development" + CODE_SIGN_IDENTITY: "$(OPENCLAW_CODE_SIGN_IDENTITY)" CODE_SIGN_ENTITLEMENTS: Sources/OpenClaw.entitlements CODE_SIGN_STYLE: "$(OPENCLAW_CODE_SIGN_STYLE)" DEVELOPMENT_TEAM: "$(OPENCLAW_DEVELOPMENT_TEAM)" @@ -113,11 +113,13 @@ targets: ENABLE_APP_INTENTS_METADATA_GENERATION: NO configs: Debug: + OPENCLAW_APNS_ENTITLEMENT_ENVIRONMENT: development OPENCLAW_PUSH_TRANSPORT: direct OPENCLAW_PUSH_DISTRIBUTION: local OPENCLAW_PUSH_RELAY_BASE_URL: "" OPENCLAW_PUSH_APNS_ENVIRONMENT: sandbox Release: + OPENCLAW_APNS_ENTITLEMENT_ENVIRONMENT: production OPENCLAW_PUSH_TRANSPORT: direct OPENCLAW_PUSH_DISTRIBUTION: local OPENCLAW_PUSH_RELAY_BASE_URL: "" @@ -184,7 +186,7 @@ targets: - sdk: AppIntents.framework settings: base: - CODE_SIGN_IDENTITY: "Apple Development" + CODE_SIGN_IDENTITY: "$(OPENCLAW_CODE_SIGN_IDENTITY)" CODE_SIGN_STYLE: "$(OPENCLAW_CODE_SIGN_STYLE)" DEVELOPMENT_TEAM: "$(OPENCLAW_DEVELOPMENT_TEAM)" ENABLE_APPINTENTS_METADATA: NO @@ -224,7 +226,7 @@ targets: - sdk: ActivityKit.framework settings: base: - CODE_SIGN_IDENTITY: "Apple Development" + CODE_SIGN_IDENTITY: "$(OPENCLAW_CODE_SIGN_IDENTITY)" CODE_SIGN_STYLE: "$(OPENCLAW_CODE_SIGN_STYLE)" DEVELOPMENT_TEAM: "$(OPENCLAW_DEVELOPMENT_TEAM)" PRODUCT_BUNDLE_IDENTIFIER: "$(OPENCLAW_ACTIVITY_WIDGET_BUNDLE_ID)" @@ -259,7 +261,7 @@ targets: settings: base: ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon - CODE_SIGN_IDENTITY: "Apple Development" + CODE_SIGN_IDENTITY: "$(OPENCLAW_CODE_SIGN_IDENTITY)" CODE_SIGN_STYLE: "$(OPENCLAW_CODE_SIGN_STYLE)" DEVELOPMENT_TEAM: "$(OPENCLAW_DEVELOPMENT_TEAM)" ENABLE_APPINTENTS_METADATA: NO @@ -293,7 +295,7 @@ targets: ProvisioningStyle: "$(OPENCLAW_CODE_SIGN_STYLE)" settings: base: - CODE_SIGN_IDENTITY: "Apple Development" + CODE_SIGN_IDENTITY: "$(OPENCLAW_CODE_SIGN_IDENTITY)" CODE_SIGN_STYLE: "$(OPENCLAW_CODE_SIGN_STYLE)" DEVELOPMENT_TEAM: "$(OPENCLAW_DEVELOPMENT_TEAM)" PRODUCT_BUNDLE_IDENTIFIER: "$(OPENCLAW_WATCH_EXTENSION_BUNDLE_ID)" @@ -326,7 +328,7 @@ targets: - sdk: AppIntents.framework settings: base: - CODE_SIGN_IDENTITY: "Apple Development" + CODE_SIGN_IDENTITY: "$(OPENCLAW_CODE_SIGN_IDENTITY)" CODE_SIGN_STYLE: "$(OPENCLAW_CODE_SIGN_STYLE)" DEVELOPMENT_TEAM: "$(OPENCLAW_DEVELOPMENT_TEAM)" PRODUCT_BUNDLE_IDENTIFIER: "$(OPENCLAW_APP_BUNDLE_ID).tests" @@ -354,7 +356,7 @@ targets: - package: OpenClawKit settings: base: - CODE_SIGN_IDENTITY: "Apple Development" + CODE_SIGN_IDENTITY: "$(OPENCLAW_CODE_SIGN_IDENTITY)" CODE_SIGN_STYLE: "$(OPENCLAW_CODE_SIGN_STYLE)" DEVELOPMENT_TEAM: "$(OPENCLAW_DEVELOPMENT_TEAM)" PRODUCT_BUNDLE_IDENTIFIER: "$(OPENCLAW_APP_BUNDLE_ID).logic-tests" @@ -381,7 +383,7 @@ targets: - target: OpenClaw settings: base: - CODE_SIGN_IDENTITY: "Apple Development" + CODE_SIGN_IDENTITY: "$(OPENCLAW_CODE_SIGN_IDENTITY)" CODE_SIGN_STYLE: "$(OPENCLAW_CODE_SIGN_STYLE)" DEVELOPMENT_TEAM: "$(OPENCLAW_DEVELOPMENT_TEAM)" PRODUCT_BUNDLE_IDENTIFIER: "$(OPENCLAW_APP_BUNDLE_ID).ui-tests" diff --git a/package.json b/package.json index 9f68da33c5d2..afc21c821cac 100644 --- a/package.json +++ b/package.json @@ -1550,12 +1550,12 @@ "gateway:watch:raw": "node scripts/watch-node.mjs gateway --force", "gen:host-env-policy:swift": "node scripts/generate-host-env-security-policy-swift.mjs --write", "ghsa:patch": "node scripts/ghsa-patch.mjs", - "ios:beta": "bash scripts/ios-beta-release.sh", - "ios:beta:archive": "bash scripts/ios-beta-archive.sh", - "ios:beta:prepare": "bash scripts/ios-beta-prepare.sh", "ios:build": "bash -lc './scripts/ios-configure-signing.sh && ./scripts/ios-write-version-xcconfig.sh && cd apps/ios && xcodegen generate && xcodebuild -project OpenClaw.xcodeproj -scheme OpenClaw -destination \"${IOS_DEST:-platform=iOS Simulator,name=iPhone 17}\" -configuration Debug build'", "ios:gen": "bash -lc './scripts/ios-configure-signing.sh && ./scripts/ios-write-version-xcconfig.sh && cd apps/ios && xcodegen generate'", "ios:open": "bash -lc './scripts/ios-configure-signing.sh && ./scripts/ios-write-version-xcconfig.sh && cd apps/ios && xcodegen generate && open OpenClaw.xcodeproj'", + "ios:release": "bash scripts/ios-release.sh", + "ios:release:archive": "bash scripts/ios-release-archive.sh", + "ios:release:prepare": "bash scripts/ios-release-prepare.sh", "ios:run": "bash scripts/ios-run.sh", "ios:screenshots": "bash scripts/ios-screenshots.sh", "ios:version": "node --import tsx scripts/ios-version.ts --json", diff --git a/scripts/ios-configure-signing.sh b/scripts/ios-configure-signing.sh index d4e1f7f247e2..adfe4e988c46 100755 --- a/scripts/ios-configure-signing.sh +++ b/scripts/ios-configure-signing.sh @@ -73,6 +73,8 @@ watch_app_bundle_id="${OPENCLAW_IOS_WATCH_APP_BUNDLE_ID:-${bundle_base}.watchkit watch_extension_bundle_id="${OPENCLAW_IOS_WATCH_EXTENSION_BUNDLE_ID:-${watch_app_bundle_id}.extension}" code_sign_style="${OPENCLAW_IOS_CODE_SIGN_STYLE:-Automatic}" +code_sign_identity="${OPENCLAW_IOS_CODE_SIGN_IDENTITY:-Apple Development}" +apns_entitlement_environment="${OPENCLAW_IOS_APNS_ENTITLEMENT_ENVIRONMENT:-development}" app_profile="${OPENCLAW_IOS_APP_PROFILE:-}" share_profile="${OPENCLAW_IOS_SHARE_PROFILE:-}" watch_app_profile="${OPENCLAW_IOS_WATCH_APP_PROFILE:-}" @@ -87,9 +89,12 @@ cat >"${tmp_file}" <&2 + echo "iOS App Store release must use canonical OpenClaw Team ID ${CANONICAL_TEAM_ID}; got ${TEAM_ID}." >&2 exit 1 fi @@ -153,10 +154,11 @@ fi bash "${VERSION_HELPER}" --build-number "${BUILD_NUMBER}" ) -write_generated_file "${BETA_XCCONFIG}" < { - it("rejects non-canonical beta signing teams before generating release inputs", () => { +describe("scripts/ios-release-prepare.sh", () => { + it("rejects non-canonical signing teams before generating release inputs", () => { const result = runPrepare(["--build-number", "7"]); expect(result.ok).toBe(false); expect(result.stderr).toContain( - "iOS beta release must use canonical OpenClaw Team ID FWJYW4S8P8", + "iOS App Store release must use canonical OpenClaw Team ID FWJYW4S8P8", ); expect(result.stderr).toContain("got Y3YUZP442G"); });