mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-03 22:33:42 +00:00
chore(ios): generate release artifacts locally
This commit is contained in:
1
.github/workflows/ios-periphery.yml
vendored
1
.github/workflows/ios-periphery.yml
vendored
@@ -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
|
||||
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
@@ -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 <release-version>`.
|
||||
3. Run `pnpm ios:version:check -- --version <release-version>`.
|
||||
4. Submit the first App Store Connect build with `pnpm ios:release:upload -- --version <release-version>`.
|
||||
5. Keep iterating on that same explicit version until the release candidate is ready.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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 `## <release version>` section and run
|
||||
sync with the same version:
|
||||
Before production upload, prefer a final `## <release version>` 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 `## <release version>` or `## Unreleased`
|
||||
3. run `pnpm ios:version:sync -- --version <release version>`
|
||||
3. run `pnpm ios:version:check -- --version <release version>`
|
||||
4. check App Store Connect for the latest build number when needed
|
||||
5. upload another build with `pnpm ios:release:upload -- --version <release version> --build-number <next>`
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 <release-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
|
||||
|
||||
@@ -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 <release-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 <release-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.
|
||||
|
||||
@@ -45,10 +45,11 @@ Or set `APP_STORE_CONNECT_API_KEY_PATH`.
|
||||
## Notes
|
||||
|
||||
- Locale files live under `metadata/<locale>/`, 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-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 <release-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 `## <release version>` 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 <release-version>`.
|
||||
- When starting a new production release train, validate metadata with `pnpm ios:version:check -- --version <release-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:
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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'",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
]);
|
||||
|
||||
|
||||
@@ -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}" <<EOF
|
||||
// Auto-generated by scripts/ios-release-prepare.sh.
|
||||
|
||||
@@ -110,6 +110,7 @@ fi
|
||||
|
||||
"${ROOT_DIR}/scripts/ios-configure-signing.sh"
|
||||
"${ROOT_DIR}/scripts/ios-write-version-xcconfig.sh"
|
||||
node "${ROOT_DIR}/scripts/ios-write-swift-filelist.mjs"
|
||||
|
||||
cd "${IOS_DIR}"
|
||||
"${XCODEGEN_BIN}" generate
|
||||
|
||||
@@ -7,7 +7,7 @@ export { parseVersionSyncArgs as parseArgs } from "./lib/version-script-args.ts"
|
||||
|
||||
function printUsage(): void {
|
||||
process.stdout.write(
|
||||
"Usage: node --import tsx scripts/ios-sync-versioning.ts [--write|--check] [--version YYYY.M.D] [--root dir]\n",
|
||||
"Usage: node --import tsx scripts/ios-sync-versioning.ts [--write|--check] [--version YYYY.M.D] [--root dir]\n\nValidates that iOS versioning inputs can produce generated local artifacts.\n",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -25,9 +25,11 @@ function main(argv = process.argv.slice(2)): number {
|
||||
});
|
||||
|
||||
if (options.mode === "check") {
|
||||
process.stdout.write("iOS versioning artifacts are up to date.\n");
|
||||
process.stdout.write("iOS versioning inputs are valid.\n");
|
||||
} else if (result.updatedPaths.length === 0) {
|
||||
process.stdout.write("iOS versioning artifacts already up to date.\n");
|
||||
process.stdout.write(
|
||||
"iOS versioning inputs are valid; local artifacts are generated by iOS prep commands.\n",
|
||||
);
|
||||
} else {
|
||||
process.stdout.write(
|
||||
`Updated iOS versioning artifacts:\n- ${result.updatedPaths.map((filePath) => path.relative(process.cwd(), filePath)).join("\n- ")}\n`,
|
||||
|
||||
@@ -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}'.`);
|
||||
|
||||
103
scripts/ios-write-swift-filelist.mjs
Normal file
103
scripts/ios-write-swift-filelist.mjs
Normal file
@@ -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`);
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"],
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user