diff --git a/.github/workflows/ios-periphery.yml b/.github/workflows/ios-periphery.yml index 42c63be33ee..0f675557c1d 100644 --- a/.github/workflows/ios-periphery.yml +++ b/.github/workflows/ios-periphery.yml @@ -102,6 +102,7 @@ jobs: set -euo pipefail ./scripts/ios-configure-signing.sh ./scripts/ios-write-version-xcconfig.sh + node scripts/ios-write-swift-filelist.mjs cd apps/ios xcodegen generate diff --git a/.gitignore b/.gitignore index 443bad351cb..b1dc7f65f9d 100644 --- a/.gitignore +++ b/.gitignore @@ -81,6 +81,7 @@ apps/android/fastlane/README.md apps/ios/fastlane/report.xml apps/ios/fastlane/Preview.html apps/ios/fastlane/screenshots/ +apps/ios/fastlane/metadata/*/release_notes.txt apps/ios/fastlane/test_output/ apps/ios/fastlane/logs/ apps/ios/fastlane/.env diff --git a/apps/ios/Config/Signing.xcconfig b/apps/ios/Config/Signing.xcconfig index 37357aea32e..106b930513c 100644 --- a/apps/ios/Config/Signing.xcconfig +++ b/apps/ios/Config/Signing.xcconfig @@ -1,5 +1,5 @@ // Shared iOS signing defaults for local development + CI. -#include "Version.xcconfig" +#include "../build/Version.xcconfig" OPENCLAW_IOS_DEFAULT_TEAM = FWJYW4S8P8 OPENCLAW_IOS_SELECTED_TEAM = $(OPENCLAW_IOS_DEFAULT_TEAM) diff --git a/apps/ios/Config/Version.xcconfig b/apps/ios/Config/Version.xcconfig deleted file mode 100644 index 37bf698cd3d..00000000000 --- a/apps/ios/Config/Version.xcconfig +++ /dev/null @@ -1,9 +0,0 @@ -// Shared iOS version defaults. -// Source of truth: package.json or explicit release --version. -// Generated by scripts/ios-sync-versioning.ts. - -OPENCLAW_IOS_VERSION = 2026.6.11 -OPENCLAW_MARKETING_VERSION = 2026.6.11 -OPENCLAW_BUILD_VERSION = 1 - -#include? "../build/Version.xcconfig" diff --git a/apps/ios/README.md b/apps/ios/README.md index 02e1794b19b..cdf479a40f6 100644 --- a/apps/ios/README.md +++ b/apps/ios/README.md @@ -25,10 +25,7 @@ OpenClaw iOS is the officially released iPhone app. It connects to an OpenClaw G ```bash pnpm install -./scripts/ios-configure-signing.sh -cd apps/ios -xcodegen generate -open OpenClaw.xcodeproj +pnpm ios:open ``` 3. In Xcode: @@ -40,10 +37,10 @@ open OpenClaw.xcodeproj - Use unique local bundle IDs via `apps/ios/LocalSigning.xcconfig`. - Start from `apps/ios/LocalSigning.xcconfig.example`. -Shortcut command (same flow + open project): +Generate without opening Xcode: ```bash -pnpm ios:open +pnpm ios:gen ``` ## App Store Release Flow @@ -165,10 +162,10 @@ This should create `apps/ios/fastlane/.env` with non-secret App Store Connect va Use `pnpm ios:release:signing:setup` for the initial portal setup, then `MATCH_PASSWORD=... pnpm ios:release:signing:sync:push` to publish encrypted Fastlane match assets to the shared private repo. -4. If you are starting a brand-new production release train, add or update the matching iOS changelog section and sync generated metadata: +4. If you are starting a brand-new production release train, add or update the matching iOS changelog section and validate the release notes: ```bash -pnpm ios:version:sync -- --version 2026.6.11 +pnpm ios:version:check -- --version 2026.6.11 ``` 5. Upload the build with explicit release intent: @@ -183,7 +180,7 @@ pnpm ios:release:upload -- --version 2026.6.11 --build-number 3 7. Expected behavior: - Fastlane reads the explicit `--version` value - - verifies synced iOS versioning artifacts for that version + - validates iOS versioning inputs for that version - resolves the next App Store Connect build number for that short version - generates deterministic App Store screenshots - uploads release notes, screenshots, and the App Review PDF attachment to the editable App Store version @@ -205,16 +202,17 @@ pnpm ios:release:upload -- --version 2026.6.11 --build-number 3 - Release upload version: explicit `--version` - Local default version: root `package.json` - iOS-only changelog: `apps/ios/CHANGELOG.md` -- Generated checked-in artifacts: - - `apps/ios/Config/Version.xcconfig` - - `apps/ios/fastlane/metadata/en-US/release_notes.txt` +- Generated local artifacts: + - `apps/ios/build/Version.xcconfig` + - `apps/ios/SwiftSources.input.xcfilelist` + - temporary Fastlane metadata containing release notes rendered from `apps/ios/CHANGELOG.md` - Useful commands: ```bash pnpm ios:version pnpm ios:version:check pnpm ios:version -- --version 2026.6.11 -pnpm ios:version:sync -- --version 2026.6.11 +pnpm ios:filelist:gen ``` Recommended flow: @@ -223,7 +221,7 @@ Recommended flow: 1. Choose the App Store train explicitly, for example `2026.6.11`. 2. Update `apps/ios/CHANGELOG.md`, usually under `## Unreleased` while iterating. -3. Run `pnpm ios:version:sync -- --version 2026.6.11` after changelog changes. +3. Run `pnpm ios:version:check -- --version 2026.6.11` after changelog changes. 4. Upload additional App Store Connect builds with `pnpm ios:release:upload -- --version 2026.6.11`. 5. Let Fastlane bump only the numeric build number. @@ -231,7 +229,7 @@ Recommended flow: 1. Confirm the target gateway version in root `package.json`. 2. Update `apps/ios/CHANGELOG.md` for the new release as needed. -3. Run `pnpm ios:version:sync -- --version `. +3. Run `pnpm ios:version:check -- --version `. 4. Submit the first App Store Connect build with `pnpm ios:release:upload -- --version `. 5. Keep iterating on that same explicit version until the release candidate is ready. diff --git a/apps/ios/Signing.xcconfig b/apps/ios/Signing.xcconfig index f4fbd4bac91..47c62b70cbc 100644 --- a/apps/ios/Signing.xcconfig +++ b/apps/ios/Signing.xcconfig @@ -2,7 +2,7 @@ // Auto-selected local team overrides live in .local-signing.xcconfig (git-ignored). // Manual local overrides can go in LocalSigning.xcconfig (git-ignored). -#include "Config/Version.xcconfig" +#include "build/Version.xcconfig" OPENCLAW_CODE_SIGN_STYLE = Manual OPENCLAW_CODE_SIGN_IDENTITY = Apple Development diff --git a/apps/ios/SwiftSources.input.xcfilelist b/apps/ios/SwiftSources.input.xcfilelist deleted file mode 100644 index 5d746e61f1a..00000000000 --- a/apps/ios/SwiftSources.input.xcfilelist +++ /dev/null @@ -1,153 +0,0 @@ -Sources/Calendar/CalendarService.swift -Sources/Camera/CameraController.swift -Sources/Capabilities/NodeCapabilityRouter.swift -Sources/Chat/AppleReviewDemoChatTransport.swift -Sources/Chat/IOSGatewayChatTransport.swift -Sources/Contacts/ContactsService.swift -Sources/Device/DeviceInfoHelper.swift -Sources/Device/DeviceStatusService.swift -Sources/Device/NetworkStatusService.swift -Sources/Device/NodeDisplayName.swift -Sources/Design/OpenClawBrand.swift -Sources/Design/AgentProDreamingDestination.swift -Sources/Design/AgentProNodesDestination.swift -Sources/Design/AgentProTab.swift -Sources/Design/ChatProTab.swift -Sources/Design/CommandCenterTab.swift -Sources/Design/TalkProTab.swift -Sources/Design/OpenClawProComponents.swift -Sources/Design/SettingsProTab.swift -Sources/Design/SettingsProTabSupport.swift -Sources/Design/SettingsProTabSections.swift -Sources/Design/SettingsProTabActions.swift -Sources/Design/TalkRuntimeIssueBanner.swift -Sources/Design/CommandCenterSupport.swift -Sources/Design/AgentProTab+Overview.swift -Sources/Design/AgentProTab+Destinations.swift -Sources/Design/AgentProTab+Skills.swift -Sources/Design/AgentProTab+Cron.swift -Sources/Design/AgentProTab+Usage.swift -Sources/Design/AgentProTab+DetailComponents.swift -Sources/Design/AgentProTab+GatewayData.swift -Sources/Design/AgentProModels.swift -Sources/Design/IPadActivityScreen.swift -Sources/Design/IPadSidebarFeaturePreviews.swift -Sources/Design/IPadSidebarFeatureScreens.swift -Sources/Design/IPadSkillWorkshopScreen.swift -Sources/Design/IPadSidebarScreenChrome.swift -Sources/Design/IPadWorkboardScreen.swift -Sources/Design/OpenClawDocsScreen.swift -Sources/Design/RootTabsPhoneControlHub.swift -Sources/Design/SettingsChannelsDestination.swift -Sources/EventKit/EventKitAuthorization.swift -Sources/Gateway/DeepLinkAgentPromptAlert.swift -Sources/Gateway/ExecApprovalPromptDialog.swift -Sources/Gateway/GatewayConnectConfig.swift -Sources/Gateway/GatewayConnectionController.swift -Sources/Gateway/GatewayConnectionIssue.swift -Sources/Gateway/GatewayDiscoveryDebugLogView.swift -Sources/Gateway/GatewayDiscoveryModel.swift -Sources/Gateway/GatewayHealthMonitor.swift -Sources/Gateway/GatewayProblemPrimaryAction.swift -Sources/Gateway/GatewayProblemView.swift -Sources/Gateway/GatewayQuickSetupSheet.swift -Sources/Gateway/NotificationPermissionGuidanceDialog.swift -Sources/Gateway/GatewayServiceResolver.swift -Sources/Gateway/GatewaySettingsStore.swift -Sources/Gateway/GatewayTrustPromptAlert.swift -Sources/Gateway/KeychainStore.swift -Sources/Gateway/TCPProbe.swift -Sources/LiveActivity/LiveActivityManager.swift -Sources/LiveActivity/OpenClawActivityAttributes.swift -Sources/Location/LocationService.swift -Sources/Location/SignificantLocationMonitor.swift -Sources/Media/PhotoLibraryService.swift -Sources/Model/NodeAppModel+Canvas.swift -Sources/Model/NodeAppModel+WatchNotifyNormalization.swift -Sources/Model/NodeAppModel.swift -Sources/Model/WatchReplyCoordinator.swift -Sources/Motion/MotionService.swift -Sources/Onboarding/GatewayOnboardingReset.swift -Sources/Onboarding/OnboardingStateStore.swift -Sources/Onboarding/OnboardingWizardSteps.swift -Sources/Onboarding/OnboardingWizardView.swift -Sources/Onboarding/QRScannerView.swift -Sources/OpenClawApp.swift -Sources/Permissions/PermissionRequestBridge.swift -Sources/Push/ExecApprovalNotificationBridge.swift -Sources/Push/BackgroundAliveBeacon.swift -Sources/Push/PushBuildConfig.swift -Sources/Push/PushEnrollmentConsent.swift -Sources/Push/PushRegistrationManager.swift -Sources/Push/PushRelayClient.swift -Sources/Push/PushRelayKeychainStore.swift -Sources/Reminders/RemindersService.swift -Sources/RootTabs.swift -Sources/RootTabsNavigation.swift -Sources/Screen/ScreenController.swift -Sources/Screen/ScreenRecordService.swift -Sources/Screen/ScreenWebView.swift -Sources/Services/NodeServiceProtocols.swift -Sources/Services/NotificationService.swift -Sources/Services/WatchConnectivityTransport.swift -Sources/Services/WatchMessagingPayloadCodec.swift -Sources/Services/WatchMessagingService.swift -Sources/SessionKey.swift -Sources/Settings/PrivacyAccessSectionView.swift -Sources/Settings/VoiceWakeWordsSettingsView.swift -Sources/Status/GatewayStatusBuilder.swift -Sources/Status/VoiceWakeToast.swift -Sources/Voice/TalkGatewayPermissionState.swift -Sources/Voice/TalkDefaults.swift -Sources/Voice/RealtimeTalkRelaySession.swift -Sources/Voice/TalkGatewaySpeechClient.swift -Sources/Voice/TalkModeGatewayConfig.swift -Sources/Voice/TalkModeManager.swift -Sources/Voice/TalkModeManager+Permissions.swift -Sources/Voice/TalkPermissionPromptView.swift -Sources/Voice/TalkRealtimeClientSession.swift -Sources/Voice/TalkRealtimeWebRTCSession.swift -Sources/Voice/TalkSpeechLocale.swift -Sources/Voice/VoiceWakeManager.swift -Sources/Voice/VoiceWakePreferences.swift -ShareExtension/ShareViewController.swift -ActivityWidget/OpenClawActivityWidgetBundle.swift -ActivityWidget/OpenClawLiveActivity.swift -WatchApp/Sources/OpenClawWatchApp.swift -WatchApp/Sources/WatchConnectivityReceiver.swift -WatchApp/Sources/WatchInboxStore.swift -WatchApp/Sources/WatchInboxView.swift -../shared/OpenClawKit/Sources/OpenClawChatUI/ChatComposer.swift -../shared/OpenClawKit/Sources/OpenClawChatUI/ChatMarkdownRenderer.swift -../shared/OpenClawKit/Sources/OpenClawChatUI/ChatMarkdownPreprocessor.swift -../shared/OpenClawKit/Sources/OpenClawChatUI/ChatMessageViews.swift -../shared/OpenClawKit/Sources/OpenClawChatUI/ChatModels.swift -../shared/OpenClawKit/Sources/OpenClawChatUI/ChatPayloadDecoding.swift -../shared/OpenClawKit/Sources/OpenClawChatUI/ChatSessions.swift -../shared/OpenClawKit/Sources/OpenClawChatUI/ChatSheets.swift -../shared/OpenClawKit/Sources/OpenClawChatUI/ChatTheme.swift -../shared/OpenClawKit/Sources/OpenClawChatUI/ChatTransport.swift -../shared/OpenClawKit/Sources/OpenClawChatUI/ChatView.swift -../shared/OpenClawKit/Sources/OpenClawChatUI/ChatViewModel+Attachments.swift -../shared/OpenClawKit/Sources/OpenClawChatUI/ChatViewModel+SessionKeys.swift -../shared/OpenClawKit/Sources/OpenClawChatUI/ChatViewModel.swift -../shared/OpenClawKit/Sources/OpenClawKit/AnyCodable.swift -../shared/OpenClawKit/Sources/OpenClawKit/BonjourEscapes.swift -../shared/OpenClawKit/Sources/OpenClawKit/BonjourTypes.swift -../shared/OpenClawKit/Sources/OpenClawKit/BridgeFrames.swift -../shared/OpenClawKit/Sources/OpenClawKit/CameraCommands.swift -../shared/OpenClawKit/Sources/OpenClawKit/CanvasA2UIAction.swift -../shared/OpenClawKit/Sources/OpenClawKit/CanvasA2UICommands.swift -../shared/OpenClawKit/Sources/OpenClawKit/CanvasA2UIJSONL.swift -../shared/OpenClawKit/Sources/OpenClawKit/CanvasCommandParams.swift -../shared/OpenClawKit/Sources/OpenClawKit/CanvasCommands.swift -../shared/OpenClawKit/Sources/OpenClawKit/Capabilities.swift -../shared/OpenClawKit/Sources/OpenClawKit/OpenClawKitResources.swift -../shared/OpenClawKit/Sources/OpenClawKit/DeepLinks.swift -../shared/OpenClawKit/Sources/OpenClawKit/JPEGTranscoder.swift -../shared/OpenClawKit/Sources/OpenClawKit/NodeError.swift -../shared/OpenClawKit/Sources/OpenClawKit/ScreenCommands.swift -../shared/OpenClawKit/Sources/OpenClawKit/StoragePaths.swift -../shared/OpenClawKit/Sources/OpenClawKit/SystemCommands.swift -../shared/OpenClawKit/Sources/OpenClawKit/TalkDirective.swift -../swabble/Sources/SwabbleKit/WakeWordGate.swift diff --git a/apps/ios/VERSIONING.md b/apps/ios/VERSIONING.md index 2951ce02b94..e732448da0c 100644 --- a/apps/ios/VERSIONING.md +++ b/apps/ios/VERSIONING.md @@ -54,7 +54,7 @@ upload fully deterministic. ### Source files - `package.json` - - default iOS version source for local builds and checked-in defaults + - default iOS version source for local builds - explicit `--version` - release upload source of truth - `apps/ios/CHANGELOG.md` @@ -64,26 +64,28 @@ upload fully deterministic. ### Generated or derived files -- `apps/ios/Config/Version.xcconfig` - - checked-in defaults derived from `package.json` or `pnpm ios:version:sync -- --version ...` -- `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 release prep +- `apps/ios/SwiftSources.input.xcfilelist` + - local gitignored Swift lint input file generated before Xcode project generation +- temporary Fastlane metadata + - release notes generated from `apps/ios/CHANGELOG.md` during metadata upload ## Tooling surfaces - `scripts/lib/ios-version.ts` - validates iOS CalVer - normalizes gateway version -> iOS CalVer - - renders checked-in xcconfig and release notes + - renders release notes from the iOS changelog - `scripts/ios-version.ts` - CLI for JSON, shell, or single-field version reads - accepts `--version YYYY.M.D` for explicit release queries - `scripts/ios-sync-versioning.ts` - - syncs checked-in derived files from the default or explicit iOS version + - validates that release notes can be rendered from the default or explicit iOS version - `scripts/ios-write-version-xcconfig.sh` - writes the local numeric build override file in `apps/ios/build/Version.xcconfig` +- `scripts/ios-write-swift-filelist.mjs` + - writes the local Swift file list consumed by Xcode pre-build lint phases - `scripts/ios-release-prepare.sh` - requires `--version` and prepares App Store distribution signing and bundle settings - `apps/ios/fastlane/Fastfile` @@ -102,17 +104,17 @@ Store Connect mutation commands. ## Release-note resolution order -When generating `apps/ios/fastlane/metadata/en-US/release_notes.txt`, the -tooling reads the first available changelog section in this order: +When generating the temporary Fastlane release notes metadata, the tooling reads +the first available changelog section in this order: 1. exact release version, for example `## 2026.6.11` 2. `## Unreleased` -Before production upload, prefer a final `## ` section and run -sync with the same version: +Before production upload, prefer a final `## ` section and +validate with the same version: ```bash -pnpm ios:version:sync -- --version 2026.6.11 +pnpm ios:version:check -- --version 2026.6.11 ``` ## Common commands @@ -121,7 +123,7 @@ pnpm ios:version:sync -- --version 2026.6.11 pnpm ios:version pnpm ios:version -- --version 2026.6.11 pnpm ios:version:check -pnpm ios:version:sync -- --version 2026.6.11 +pnpm ios:filelist:gen pnpm ios:release:upload -- --version 2026.6.11 --build-number 3 ``` @@ -129,7 +131,7 @@ pnpm ios:release:upload -- --version 2026.6.11 --build-number 3 1. choose the App Store release train explicitly, for example `2026.6.11` 2. update `apps/ios/CHANGELOG.md` under `## ` or `## Unreleased` -3. run `pnpm ios:version:sync -- --version ` +3. run `pnpm ios:version:check -- --version ` 4. check App Store Connect for the latest build number when needed 5. upload another build with `pnpm ios:release:upload -- --version --build-number ` @@ -182,10 +184,10 @@ node -e "console.log(require('./package.json').version)" ``` 2. update `apps/ios/CHANGELOG.md` for that release -3. sync iOS generated files: +3. validate iOS release notes: ```bash -pnpm ios:version:sync -- --version 2026.6.11 +pnpm ios:version:check -- --version 2026.6.11 ``` 4. verify live App Store Connect state and choose the next build number @@ -201,7 +203,7 @@ pnpm ios:release:upload -- --version 2026.6.11 --build-number 3 ## Important invariant App Store uploads must carry explicit version intent. Do not infer a release -train from checked-in generated files. +train from generated local files. App Review submission remains manual. Automation may create/update the editable App Store version, upload screenshots, upload release notes, upload the App diff --git a/apps/ios/fastlane/Fastfile b/apps/ios/fastlane/Fastfile index 16a1194e9d1..769dfb286c6 100644 --- a/apps/ios/fastlane/Fastfile +++ b/apps/ios/fastlane/Fastfile @@ -734,18 +734,33 @@ def release_signing_check! sync_app_store_signing!(readonly: true) end -def release_notes_path - File.join(__dir__, "metadata", "en-US", "release_notes.txt") +def render_ios_release_notes(release_version:) + script_path = File.join(repo_root, "scripts", "ios-version.ts") + args = [ + "node", + "--import", + "tsx", + script_path, + "--field", + "releaseNotes" + ] + args.push("--version", release_version) if env_present?(release_version) + stdout, stderr, status = Open3.capture3( + *args, + chdir: repo_root + ) + return stdout if status.success? + + detail = stderr.to_s.strip + detail = stdout.to_s.strip if detail.empty? + UI.user_error!("Failed to render iOS release notes: #{detail}") end -def release_notes_metadata_path - source = release_notes_path - UI.user_error!("Missing release notes at #{source}. Run `pnpm ios:version:sync -- --version `.") unless File.exist?(source) - +def release_notes_metadata_path(release_version:) temp_root = Dir.mktmpdir("openclaw-release-notes") target_dir = File.join(temp_root, "en-US") FileUtils.mkdir_p(target_dir) - FileUtils.cp(source, File.join(target_dir, "release_notes.txt")) + File.write(File.join(target_dir, "release_notes.txt"), render_ios_release_notes(release_version: release_version)) temp_root end @@ -780,7 +795,7 @@ def assert_no_app_review_notes_field_metadata!(metadata_path) end end -def public_metadata_path +def public_metadata_path(release_version: nil) source = File.join(__dir__, "metadata") temp_root = Dir.mktmpdir("openclaw-app-store-metadata") Dir.children(source).each do |entry| @@ -790,6 +805,12 @@ def public_metadata_path FileUtils.cp_r(source_entry, File.join(temp_root, entry)) end + Dir[File.join(temp_root, "*", "release_notes.txt")].each { |path| FileUtils.rm_f(path) } + if release_notes_upload_requested? + target_dir = File.join(temp_root, "en-US") + FileUtils.mkdir_p(target_dir) + File.write(File.join(target_dir, "release_notes.txt"), render_ios_release_notes(release_version: release_version)) + end temp_root end @@ -1100,8 +1121,8 @@ def sync_ios_versioning!(release_version: nil) detail = stderr.to_s.strip detail = stdout.to_s.strip if detail.empty? - sync_command = env_present?(release_version) ? "pnpm ios:version:sync -- --version #{release_version}" : "pnpm ios:version:sync" - UI.user_error!("iOS versioning artifacts are stale. Run `#{sync_command}`.\n#{detail}") + check_command = env_present?(release_version) ? "pnpm ios:version:check -- --version #{release_version}" : "pnpm ios:version:check" + UI.user_error!("iOS versioning inputs are invalid. Run `#{check_command}`.\n#{detail}") end def shell_join(parts) @@ -1463,10 +1484,10 @@ platform :ios do end assert_no_app_review_notes_field_metadata!(File.join(__dir__, "metadata")) - metadata_path = public_metadata_path + metadata_path = public_metadata_path(release_version: release_version) skip_metadata = ENV["DELIVER_METADATA"] != "1" if release_notes_upload_requested? && skip_metadata - metadata_path = release_notes_metadata_path + metadata_path = release_notes_metadata_path(release_version: release_version) skip_metadata = false end assert_no_app_review_notes_field_metadata!(metadata_path) unless skip_metadata @@ -1517,6 +1538,7 @@ platform :ios do sh(shell_join(["bash", File.join(repo_root, "scripts", "ios-configure-signing.sh")])) sh(shell_join(["bash", File.join(repo_root, "scripts", "ios-write-version-xcconfig.sh"), *version_args])) + sh(shell_join(["node", File.join(repo_root, "scripts", "ios-write-swift-filelist.mjs")])) sh(shell_join(["xcodegen", "generate", "--spec", File.join(ios_root, "project.yml"), "--project", ios_root])) capture_ios_screenshots( @@ -1549,6 +1571,7 @@ platform :ios do sh(shell_join(["bash", File.join(repo_root, "scripts", "ios-configure-signing.sh")])) sh(shell_join(["bash", File.join(repo_root, "scripts", "ios-write-version-xcconfig.sh"), *version_args])) + sh(shell_join(["node", File.join(repo_root, "scripts", "ios-write-swift-filelist.mjs")])) sh(shell_join(["xcodegen", "generate", "--spec", File.join(ios_root, "project.yml"), "--project", ios_root])) capture_watch_screenshot end diff --git a/apps/ios/fastlane/SETUP.md b/apps/ios/fastlane/SETUP.md index 4bf910c7be6..3973e555aee 100644 --- a/apps/ios/fastlane/SETUP.md +++ b/apps/ios/fastlane/SETUP.md @@ -135,10 +135,10 @@ cd apps/ios fastlane ios auth_check ``` -4. If you are starting a brand-new production release train, sync iOS generated metadata for the release version: +4. If you are starting a brand-new production release train, validate iOS release notes for the release version: ```bash -pnpm ios:version:sync -- --version 2026.6.11 +pnpm ios:version:check -- --version 2026.6.11 ``` 5. Upload: @@ -163,8 +163,8 @@ Versioning rules: - Fastlane uses the explicit release version for App Store upload - Fastlane sets `CFBundleShortVersionString` to the release version, for example `2026.4.10` - Fastlane resolves `CFBundleVersion` as the next integer App Store Connect build number for that short version -- Run `pnpm ios:version:sync -- --version ` after changing `apps/ios/CHANGELOG.md` -- `pnpm ios:version:check` validates that checked-in iOS version artifacts are in sync +- Run `pnpm ios:version:check -- --version ` after changing `apps/ios/CHANGELOG.md` +- `pnpm ios:version:check` validates that release notes can be generated from the iOS changelog - The release flow regenerates `apps/ios/OpenClaw.xcodeproj` from `apps/ios/project.yml` before archiving - Local App Store signing uses a temporary generated xcconfig with profile names from `apps/ios/Config/AppStoreSigning.json` and leaves local development signing overrides untouched - App Store release uses `OpenClawPushMode=appStore`, which derives the canonical production hosted relay, production APNs, production relay profile, and `appleStrict` proof. The release lane rejects custom production relay URL overrides. diff --git a/apps/ios/fastlane/metadata/README.md b/apps/ios/fastlane/metadata/README.md index 4b469e7bf7b..40279fa208b 100644 --- a/apps/ios/fastlane/metadata/README.md +++ b/apps/ios/fastlane/metadata/README.md @@ -45,10 +45,11 @@ Or set `APP_STORE_CONNECT_API_KEY_PATH`. ## Notes - Locale files live under `metadata//`, for example `metadata/en-US/` and `metadata/sv-SE/`. Each locale directory should use the public metadata filenames consumed by the `ios metadata` lane. -- `release_notes.txt` is generated from `apps/ios/CHANGELOG.md`; after changelog updates, run `pnpm ios:version:sync -- --version `. +- Release notes are generated from `apps/ios/CHANGELOG.md` into temporary Fastlane metadata during upload; after changelog updates, run `pnpm ios:version:check -- --version `. +- Do not check in `release_notes.txt` under locale metadata directories; the lane strips copied release-note files and writes the current generated en-US release notes when requested. - `apps/ios/APP-REVIEW-NOTES.md` is rendered to `apps/ios/build/app-review/APP-REVIEW-NOTES.pdf` and uploaded as the App Review attachment when metadata is uploaded. - Release notes resolve from `## ` first, then fall back to `## Unreleased` while an App Store Connect build train is still in progress. -- When starting a new production release train, sync metadata with `pnpm ios:version:sync -- --version `. +- When starting a new production release train, validate metadata with `pnpm ios:version:check -- --version `. - The release upload flow uploads release notes, screenshots, and the App Review PDF attachment before the IPA, and never submits for App Review. - `privacy_url.txt` is set to `https://openclaw.ai/privacy`. - If app lookup fails in `deliver`, set one of: diff --git a/apps/ios/fastlane/metadata/en-US/release_notes.txt b/apps/ios/fastlane/metadata/en-US/release_notes.txt deleted file mode 100644 index b1e7bf5d45c..00000000000 --- a/apps/ios/fastlane/metadata/en-US/release_notes.txt +++ /dev/null @@ -1,3 +0,0 @@ -Maintenance update for the current OpenClaw release. - -- Refreshed iOS 26 visual styling, Talk controls, Gateway recovery, localization, and App Store screenshots. diff --git a/apps/ios/fastlane/metadata/sv-SE/release_notes.txt b/apps/ios/fastlane/metadata/sv-SE/release_notes.txt deleted file mode 100644 index 8feaac5c505..00000000000 --- a/apps/ios/fastlane/metadata/sv-SE/release_notes.txt +++ /dev/null @@ -1,3 +0,0 @@ -OpenClaw finns nu för iPhone. - -Anslut till din OpenClaw Gateway för att chatta med din assistent, använda Talk-läge i realtid, granska godkännanden, dela innehåll från iOS och använda enhetsfunktioner som kamera, plats, skärm och aviseringar i dina privata automatiseringar. diff --git a/package.json b/package.json index 80410daf462..f44e0b299c3 100644 --- a/package.json +++ b/package.json @@ -1623,9 +1623,10 @@ "gen:host-env-policy:swift": "node scripts/generate-host-env-security-policy-swift.mjs --write", "ghsa:patch": "node scripts/ghsa-patch.mjs", "ios:app-review-notes:pdf": "xcrun swift scripts/ios-app-review-notes-pdf.swift apps/ios/APP-REVIEW-NOTES.md apps/ios/build/app-review/APP-REVIEW-NOTES.pdf", - "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:build": "bash -lc './scripts/ios-configure-signing.sh && ./scripts/ios-write-version-xcconfig.sh && node scripts/ios-write-swift-filelist.mjs && cd apps/ios && xcodegen generate && xcodebuild -project OpenClaw.xcodeproj -scheme OpenClaw -destination \"${IOS_DEST:-platform=iOS Simulator,name=iPhone 17}\" -configuration Debug build'", + "ios:filelist:gen": "node scripts/ios-write-swift-filelist.mjs", + "ios:gen": "bash -lc './scripts/ios-configure-signing.sh && ./scripts/ios-write-version-xcconfig.sh && node scripts/ios-write-swift-filelist.mjs && cd apps/ios && xcodegen generate'", + "ios:open": "bash -lc './scripts/ios-configure-signing.sh && ./scripts/ios-write-version-xcconfig.sh && node scripts/ios-write-swift-filelist.mjs && cd apps/ios && xcodegen generate && open OpenClaw.xcodeproj'", "ios:release:archive": "bash scripts/ios-release-archive.sh", "ios:release:prepare": "bash scripts/ios-release-prepare.sh", "ios:release:signing:check": "bash -lc 'source ./scripts/lib/ios-fastlane.sh && cd apps/ios && run_ios_fastlane ios signing_check'", diff --git a/scripts/changed-lanes.mjs b/scripts/changed-lanes.mjs index e972eeea43a..3d978673865 100644 --- a/scripts/changed-lanes.mjs +++ b/scripts/changed-lanes.mjs @@ -35,8 +35,6 @@ export const RELEASE_METADATA_PATHS = new Set([ "apps/android/fastlane/metadata/android/en-US/release_notes.txt", "apps/android/version.json", "apps/ios/CHANGELOG.md", - "apps/ios/Config/Version.xcconfig", - "apps/ios/fastlane/metadata/en-US/release_notes.txt", "apps/macos/Sources/OpenClaw/Resources/Info.plist", "docs/.generated/config-baseline.sha256", "docs/install/updating.md", diff --git a/scripts/check-release-metadata-only.mjs b/scripts/check-release-metadata-only.mjs index 30d01d0ffef..a027caf79c8 100644 --- a/scripts/check-release-metadata-only.mjs +++ b/scripts/check-release-metadata-only.mjs @@ -8,7 +8,6 @@ import { RELEASE_METADATA_PATHS } from "./changed-lanes.mjs"; const VERSION_ONLY_TEXT_PATHS = new Set([ "apps/android/Config/Version.properties", "apps/android/version.json", - "apps/ios/Config/Version.xcconfig", "apps/macos/Sources/OpenClaw/Resources/Info.plist", ]); diff --git a/scripts/ios-release-prepare.sh b/scripts/ios-release-prepare.sh index 018bcad171c..f41afe8754f 100755 --- a/scripts/ios-release-prepare.sh +++ b/scripts/ios-release-prepare.sh @@ -150,6 +150,7 @@ fi ( bash "${VERSION_HELPER}" --version "${IOS_VERSION}" --build-number "${BUILD_NUMBER}" ) +node "${ROOT_DIR}/scripts/ios-write-swift-filelist.mjs" write_generated_file "${RELEASE_XCCONFIG}" < path.relative(process.cwd(), filePath)).join("\n- ")}\n`, diff --git a/scripts/ios-version.ts b/scripts/ios-version.ts index f1de18a3539..bbe0f5014b6 100644 --- a/scripts/ios-version.ts +++ b/scripts/ios-version.ts @@ -1,5 +1,5 @@ // Ios Version script supports OpenClaw repository automation. -import { resolveIosVersion } from "./lib/ios-version.ts"; +import { renderIosReleaseNotesForVersion, resolveIosVersion } from "./lib/ios-version.ts"; import { parseVersionQueryArgs } from "./lib/version-script-args.ts"; function printUsage(): void { @@ -18,6 +18,16 @@ function main(argv = process.argv.slice(2)): number { const version = resolveIosVersion(options.rootDir, { releaseVersion: options.releaseVersion }); if (options.field) { + if (options.field === "releaseNotes") { + process.stdout.write( + renderIosReleaseNotesForVersion({ + releaseVersion: options.releaseVersion, + rootDir: options.rootDir, + }), + ); + return 0; + } + const value = version[options.field as keyof typeof version]; if (value === undefined) { throw new Error(`Unknown iOS version field '${options.field}'.`); diff --git a/scripts/ios-write-swift-filelist.mjs b/scripts/ios-write-swift-filelist.mjs new file mode 100644 index 00000000000..f6e4ecd9e29 --- /dev/null +++ b/scripts/ios-write-swift-filelist.mjs @@ -0,0 +1,103 @@ +#!/usr/bin/env node +import { existsSync, lstatSync, mkdirSync, readdirSync, writeFileSync } from "node:fs"; +import path from "node:path"; + +const repoRoot = path.resolve(import.meta.dirname, ".."); +const iosRoot = path.join(repoRoot, "apps", "ios"); +const outputPath = path.join(iosRoot, "SwiftSources.input.xcfilelist"); + +const iosSourceRoots = [ + "Sources", + "ShareExtension", + "ActivityWidget", + path.join("WatchApp", "Sources"), +]; + +const sharedSwiftFiles = [ + "../shared/OpenClawKit/Sources/OpenClawChatUI/ChatComposer.swift", + "../shared/OpenClawKit/Sources/OpenClawChatUI/ChatMarkdownPreprocessor.swift", + "../shared/OpenClawKit/Sources/OpenClawChatUI/ChatMarkdownRenderer.swift", + "../shared/OpenClawKit/Sources/OpenClawChatUI/ChatMessageViews.swift", + "../shared/OpenClawKit/Sources/OpenClawChatUI/ChatModels.swift", + "../shared/OpenClawKit/Sources/OpenClawChatUI/ChatPayloadDecoding.swift", + "../shared/OpenClawKit/Sources/OpenClawChatUI/ChatSessions.swift", + "../shared/OpenClawKit/Sources/OpenClawChatUI/ChatSheets.swift", + "../shared/OpenClawKit/Sources/OpenClawChatUI/ChatTheme.swift", + "../shared/OpenClawKit/Sources/OpenClawChatUI/ChatTransport.swift", + "../shared/OpenClawKit/Sources/OpenClawChatUI/ChatView.swift", + "../shared/OpenClawKit/Sources/OpenClawChatUI/ChatViewModel+Attachments.swift", + "../shared/OpenClawKit/Sources/OpenClawChatUI/ChatViewModel+SessionKeys.swift", + "../shared/OpenClawKit/Sources/OpenClawChatUI/ChatViewModel.swift", + "../shared/OpenClawKit/Sources/OpenClawKit/AnyCodable.swift", + "../shared/OpenClawKit/Sources/OpenClawKit/BonjourEscapes.swift", + "../shared/OpenClawKit/Sources/OpenClawKit/BonjourTypes.swift", + "../shared/OpenClawKit/Sources/OpenClawKit/BridgeFrames.swift", + "../shared/OpenClawKit/Sources/OpenClawKit/CameraCommands.swift", + "../shared/OpenClawKit/Sources/OpenClawKit/CanvasA2UIAction.swift", + "../shared/OpenClawKit/Sources/OpenClawKit/CanvasA2UICommands.swift", + "../shared/OpenClawKit/Sources/OpenClawKit/CanvasA2UIJSONL.swift", + "../shared/OpenClawKit/Sources/OpenClawKit/CanvasCommandParams.swift", + "../shared/OpenClawKit/Sources/OpenClawKit/CanvasCommands.swift", + "../shared/OpenClawKit/Sources/OpenClawKit/Capabilities.swift", + "../shared/OpenClawKit/Sources/OpenClawKit/DeepLinks.swift", + "../shared/OpenClawKit/Sources/OpenClawKit/JPEGTranscoder.swift", + "../shared/OpenClawKit/Sources/OpenClawKit/NodeError.swift", + "../shared/OpenClawKit/Sources/OpenClawKit/OpenClawKitResources.swift", + "../shared/OpenClawKit/Sources/OpenClawKit/ScreenCommands.swift", + "../shared/OpenClawKit/Sources/OpenClawKit/StoragePaths.swift", + "../shared/OpenClawKit/Sources/OpenClawKit/SystemCommands.swift", + "../shared/OpenClawKit/Sources/OpenClawKit/TalkDirective.swift", + "../swabble/Sources/SwabbleKit/WakeWordGate.swift", +]; + +function normalizeFileListPath(filePath) { + return filePath.split(path.sep).join("/"); +} + +function collectSwiftFiles(rootRelativePath) { + const root = path.join(iosRoot, rootRelativePath); + if (!existsSync(root)) { + throw new Error(`Missing iOS Swift source root: ${rootRelativePath}`); + } + + const entries = []; + const visit = (dir) => { + for (const entry of readdirSync(dir, { withFileTypes: true })) { + const fullPath = path.join(dir, entry.name); + if (entry.isDirectory()) { + visit(fullPath); + } else if (entry.isFile() && entry.name.endsWith(".swift")) { + entries.push(normalizeFileListPath(path.relative(iosRoot, fullPath))); + } + } + }; + visit(root); + return entries; +} + +function assertSharedFilesExist(filePaths) { + for (const filePath of filePaths) { + const absolutePath = path.resolve(iosRoot, filePath); + if (!existsSync(absolutePath)) { + throw new Error(`Missing shared Swift file listed for iOS lint: ${filePath}`); + } + } +} + +function writeGeneratedFile(filePath, contents) { + if (existsSync(filePath) && lstatSync(filePath).isSymbolicLink()) { + throw new Error(`Refusing to overwrite symlinked file: ${filePath}`); + } + mkdirSync(path.dirname(filePath), { recursive: true }); + writeFileSync(filePath, contents, "utf8"); +} + +assertSharedFilesExist(sharedSwiftFiles); + +const iosFiles = iosSourceRoots.flatMap(collectSwiftFiles); +const fileList = [...new Set([...iosFiles, ...sharedSwiftFiles])].toSorted((left, right) => + left.localeCompare(right), +); + +writeGeneratedFile(outputPath, `${fileList.join("\n")}\n`); +process.stdout.write(`Prepared iOS Swift file list: ${path.relative(repoRoot, outputPath)}\n`); diff --git a/scripts/lib/ios-version.ts b/scripts/lib/ios-version.ts index 47b98c8b38c..3c704eeb500 100644 --- a/scripts/lib/ios-version.ts +++ b/scripts/lib/ios-version.ts @@ -1,29 +1,21 @@ // Ios Version script supports OpenClaw repository automation. -import { readFileSync, writeFileSync } from "node:fs"; +import { readFileSync } from "node:fs"; import path from "node:path"; import { parseReleaseVersion } from "./npm-publish-plan.mjs"; const IOS_CHANGELOG_FILE = "apps/ios/CHANGELOG.md"; -const IOS_VERSION_XCCONFIG_FILE = "apps/ios/Config/Version.xcconfig"; -const IOS_RELEASE_NOTES_FILE = "apps/ios/fastlane/metadata/en-US/release_notes.txt"; type ResolvedIosVersion = { canonicalVersion: string; marketingVersion: string; buildVersion: string; changelogPath: string; - versionXcconfigPath: string; - releaseNotesPath: string; versionSource: "explicit" | "package"; versionSourcePath: string | null; }; type SyncIosVersioningMode = "check" | "write"; -function normalizeTrailingNewline(value: string): string { - return value.endsWith("\n") ? value : `${value}\n`; -} - function parsePinnedReleaseVersion(rawVersion: string): string | null { const parsed = parseReleaseVersion(rawVersion.trim()); if (!parsed || parsed.version !== parsed.baseVersion) { @@ -92,8 +84,6 @@ export function resolveIosVersion( options?: { releaseVersion?: string | null }, ): ResolvedIosVersion { const changelogPath = path.join(rootDir, IOS_CHANGELOG_FILE); - const versionXcconfigPath = path.join(rootDir, IOS_VERSION_XCCONFIG_FILE); - const releaseNotesPath = path.join(rootDir, IOS_RELEASE_NOTES_FILE); const explicitReleaseVersion = options?.releaseVersion?.trim() ?? ""; const canonicalVersion = explicitReleaseVersion ? normalizePinnedIosVersion(explicitReleaseVersion) @@ -104,17 +94,11 @@ export function resolveIosVersion( marketingVersion: canonicalVersion, buildVersion: "1", changelogPath, - versionXcconfigPath, - releaseNotesPath, versionSource: explicitReleaseVersion ? "explicit" : "package", versionSourcePath: explicitReleaseVersion ? null : rootPackageJsonPath(rootDir), }; } -export function renderIosVersionXcconfig(version: ResolvedIosVersion): string { - return `// Shared iOS version defaults.\n// Source of truth: package.json or explicit release --version.\n// Generated by scripts/ios-sync-versioning.ts.\n\nOPENCLAW_IOS_VERSION = ${version.canonicalVersion}\nOPENCLAW_MARKETING_VERSION = ${version.marketingVersion}\nOPENCLAW_BUILD_VERSION = ${version.buildVersion}\n\n#include? "../build/Version.xcconfig"\n`; -} - function matchChangelogHeading(line: string, heading: string): boolean { const normalized = line.trim(); return normalized === `## ${heading}` || normalized.startsWith(`## ${heading} - `); @@ -160,26 +144,6 @@ export function renderIosReleaseNotes( ); } -function syncFile(params: { - mode: SyncIosVersioningMode; - path: string; - nextContent: string; - label: string; -}): boolean { - const nextContent = normalizeTrailingNewline(params.nextContent); - const currentContent = readFileSync(params.path, "utf8"); - if (currentContent === nextContent) { - return false; - } - - if (params.mode === "check") { - throw new Error(`${params.label} is stale: ${path.relative(process.cwd(), params.path)}`); - } - - writeFileSync(params.path, nextContent, "utf8"); - return true; -} - export function syncIosVersioning(params?: { mode?: SyncIosVersioningMode; releaseVersion?: string | null; @@ -187,36 +151,21 @@ export function syncIosVersioning(params?: { }): { updatedPaths: string[]; } { - const mode = params?.mode ?? "write"; const rootDir = path.resolve(params?.rootDir ?? "."); const releaseVersion = params?.releaseVersion; const version = resolveIosVersion(rootDir, { releaseVersion }); const changelogContent = readFileSync(version.changelogPath, "utf8"); - const nextVersionXcconfig = renderIosVersionXcconfig(version); - const nextReleaseNotes = renderIosReleaseNotes(version, changelogContent); - const updatedPaths: string[] = []; + renderIosReleaseNotes(version, changelogContent); - if ( - syncFile({ - mode, - path: version.versionXcconfigPath, - nextContent: nextVersionXcconfig, - label: "iOS version xcconfig", - }) - ) { - updatedPaths.push(version.versionXcconfigPath); - } - - if ( - syncFile({ - mode, - path: version.releaseNotesPath, - nextContent: nextReleaseNotes, - label: "iOS release notes", - }) - ) { - updatedPaths.push(version.releaseNotesPath); - } - - return { updatedPaths }; + return { updatedPaths: [] }; +} + +export function renderIosReleaseNotesForVersion(params?: { + releaseVersion?: string | null; + rootDir?: string; +}): string { + const rootDir = path.resolve(params?.rootDir ?? "."); + const version = resolveIosVersion(rootDir, { releaseVersion: params?.releaseVersion }); + const changelogContent = readFileSync(version.changelogPath, "utf8"); + return renderIosReleaseNotes(version, changelogContent); } diff --git a/test/scripts/changed-lanes.test.ts b/test/scripts/changed-lanes.test.ts index b57cc54c6bf..eff72647b2f 100644 --- a/test/scripts/changed-lanes.test.ts +++ b/test/scripts/changed-lanes.test.ts @@ -1328,8 +1328,6 @@ describe("scripts/changed-lanes", () => { "apps/android/fastlane/metadata/android/en-US/release_notes.txt", "apps/android/version.json", "apps/ios/CHANGELOG.md", - "apps/ios/Config/Version.xcconfig", - "apps/ios/fastlane/metadata/en-US/release_notes.txt", "apps/macos/Sources/OpenClaw/Resources/Info.plist", "docs/.generated/config-baseline.sha256", "package.json", diff --git a/test/scripts/check-release-metadata-only.test.ts b/test/scripts/check-release-metadata-only.test.ts index 6568d159953..fa738429d21 100644 --- a/test/scripts/check-release-metadata-only.test.ts +++ b/test/scripts/check-release-metadata-only.test.ts @@ -10,13 +10,13 @@ describe("check-release-metadata-only", () => { "--head", "HEAD", "./package.json", - "apps\\ios\\Config\\Version.xcconfig", + "apps\\ios\\CHANGELOG.md", ]), ).toEqual({ staged: false, base: "origin/release", head: "HEAD", - paths: ["package.json", "apps/ios/Config/Version.xcconfig"], + paths: ["package.json", "apps/ios/CHANGELOG.md"], }); }); diff --git a/test/scripts/ios-version.test-support.ts b/test/scripts/ios-version.test-support.ts index e3cfd38d7db..45a77738e62 100644 --- a/test/scripts/ios-version.test-support.ts +++ b/test/scripts/ios-version.test-support.ts @@ -16,30 +16,15 @@ export function writeIosFixture(params: { version?: string; changelog: string; packageVersion?: string; - releaseNotes?: string; - versionXcconfig?: string; prefix?: string; }): string { const rootDir = makeTempDir(tempDirs, params.prefix ?? "openclaw-ios-version-"); - fs.mkdirSync(path.join(rootDir, "apps", "ios", "Config"), { recursive: true }); - fs.mkdirSync(path.join(rootDir, "apps", "ios", "fastlane", "metadata", "en-US"), { - recursive: true, - }); + fs.mkdirSync(path.join(rootDir, "apps", "ios"), { recursive: true }); fs.writeFileSync( path.join(rootDir, "package.json"), `${JSON.stringify({ version: params.packageVersion ?? params.version ?? "2026.4.6" }, null, 2)}\n`, "utf8", ); fs.writeFileSync(path.join(rootDir, "apps", "ios", "CHANGELOG.md"), params.changelog, "utf8"); - fs.writeFileSync( - path.join(rootDir, "apps", "ios", "Config", "Version.xcconfig"), - params.versionXcconfig ?? "", - "utf8", - ); - fs.writeFileSync( - path.join(rootDir, "apps", "ios", "fastlane", "metadata", "en-US", "release_notes.txt"), - params.releaseNotes ?? "", - "utf8", - ); return rootDir; } diff --git a/test/scripts/ios-version.test.ts b/test/scripts/ios-version.test.ts index c633c946ae1..4633c977a40 100644 --- a/test/scripts/ios-version.test.ts +++ b/test/scripts/ios-version.test.ts @@ -8,7 +8,6 @@ import { normalizeGatewayVersionToPinnedIosVersion, normalizePinnedIosVersion, renderIosReleaseNotes, - renderIosVersionXcconfig, resolveGatewayVersionForIosRelease, resolveIosVersion, } from "../../scripts/lib/ios-version.ts"; @@ -99,6 +98,35 @@ describe("resolveIosVersion", () => { expect(result.stderr).toBe(""); }); + it("prints derived release notes from the CLI", () => { + const rootDir = writeIosFixture({ + packageVersion: "2026.4.6", + changelog: "# OpenClaw iOS Changelog\n\n## 2026.4.7\n\nGenerated notes.\n", + }); + const result = spawnSync( + process.execPath, + [ + "--import", + "tsx", + "scripts/ios-version.ts", + "--root", + rootDir, + "--version", + "2026.4.7", + "--field", + "releaseNotes", + ], + { + cwd: process.cwd(), + encoding: "utf8", + }, + ); + + expect(result.status).toBe(0); + expect(result.stdout).toBe("Generated notes.\n"); + expect(result.stderr).toBe(""); + }); + it("rejects missing iOS sync CLI root values before reading version files", () => { const result = spawnSync( process.execPath, @@ -136,10 +164,8 @@ describe("resolveIosVersion", () => { canonicalVersion: "2026.4.6", changelogPath: path.join(rootDir, "apps/ios/CHANGELOG.md"), marketingVersion: "2026.4.6", - releaseNotesPath: path.join(rootDir, "apps/ios/fastlane/metadata/en-US/release_notes.txt"), versionSource: "package", versionSourcePath: path.join(rootDir, "package.json"), - versionXcconfigPath: path.join(rootDir, "apps/ios/Config/Version.xcconfig"), }); }); @@ -212,20 +238,6 @@ describe("gateway version normalization", () => { }); }); -describe("renderIosVersionXcconfig", () => { - it("renders checked-in defaults from the package-derived iOS version", () => { - const rootDir = writeIosFixture({ - packageVersion: "2026.4.8", - changelog: "# OpenClaw iOS Changelog\n\n## 2026.4.8\n\nNotes.\n", - }); - const version = resolveIosVersion(rootDir); - - expect(renderIosVersionXcconfig(version)).toContain("OPENCLAW_IOS_VERSION = 2026.4.8"); - expect(renderIosVersionXcconfig(version)).toContain("OPENCLAW_MARKETING_VERSION = 2026.4.8"); - expect(renderIosVersionXcconfig(version)).toContain("OPENCLAW_BUILD_VERSION = 1"); - }); -}); - describe("release note extraction", () => { it("extracts exact pinned version sections first", () => { const rootDir = writeIosFixture({