mirror of https://github.com/openclaw/openclaw.git
feat(ios): add local beta release flow (#42991)
Merged via squash.
Prepared head SHA: 82b38fe93b
Co-authored-by: ngutman <1540134+ngutman@users.noreply.github.com>
Co-authored-by: ngutman <1540134+ngutman@users.noreply.github.com>
Reviewed-by: @ngutman
This commit is contained in:
parent
665f677265
commit
2d91284fdb
|
|
@ -15,6 +15,7 @@ Docs: https://docs.openclaw.ai
|
||||||
- Discord/auto threads: add `autoArchiveDuration` channel config for auto-created threads so Discord thread archiving can stay at 1 hour, 1 day, 3 days, or 1 week instead of always using the 1-hour default. (#35065) Thanks @davidguttman.
|
- Discord/auto threads: add `autoArchiveDuration` channel config for auto-created threads so Discord thread archiving can stay at 1 hour, 1 day, 3 days, or 1 week instead of always using the 1-hour default. (#35065) Thanks @davidguttman.
|
||||||
- OpenCode/onboarding: add new OpenCode Go provider, treat Zen and Go as one OpenCode setup in the wizard/docs while keeping the runtime providers split, store one shared OpenCode key for both profiles, and stop overriding the built-in `opencode-go` catalog routing. (#42313) Thanks @ImLukeF and @vincentkoc.
|
- OpenCode/onboarding: add new OpenCode Go provider, treat Zen and Go as one OpenCode setup in the wizard/docs while keeping the runtime providers split, store one shared OpenCode key for both profiles, and stop overriding the built-in `opencode-go` catalog routing. (#42313) Thanks @ImLukeF and @vincentkoc.
|
||||||
- macOS/chat UI: add a chat model picker, persist explicit thinking-level selections across relaunch, and harden provider-aware session model sync for the shared chat composer. (#42314) Thanks @ImLukeF.
|
- macOS/chat UI: add a chat model picker, persist explicit thinking-level selections across relaunch, and harden provider-aware session model sync for the shared chat composer. (#42314) Thanks @ImLukeF.
|
||||||
|
- iOS/TestFlight: add a local beta release flow with Fastlane prepare/archive/upload support, canonical beta bundle IDs, and watch-app archive fixes. (#42991) Thanks @ngutman.
|
||||||
|
|
||||||
### Breaking
|
### Breaking
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,9 @@
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>XPC!</string>
|
<string>XPC!</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2026.3.9</string>
|
<string>$(OPENCLAW_MARKETING_VERSION)</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>20260308</string>
|
<string>$(OPENCLAW_BUILD_VERSION)</string>
|
||||||
<key>NSExtension</key>
|
<key>NSExtension</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSExtensionPointIdentifier</key>
|
<key>NSExtensionPointIdentifier</key>
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
// Shared iOS signing defaults for local development + CI.
|
// Shared iOS signing defaults for local development + CI.
|
||||||
|
#include "Version.xcconfig"
|
||||||
|
|
||||||
OPENCLAW_IOS_DEFAULT_TEAM = Y5PE65HELJ
|
OPENCLAW_IOS_DEFAULT_TEAM = Y5PE65HELJ
|
||||||
OPENCLAW_IOS_SELECTED_TEAM = $(OPENCLAW_IOS_DEFAULT_TEAM)
|
OPENCLAW_IOS_SELECTED_TEAM = $(OPENCLAW_IOS_DEFAULT_TEAM)
|
||||||
OPENCLAW_APP_BUNDLE_ID = ai.openclaw.ios
|
OPENCLAW_APP_BUNDLE_ID = ai.openclaw.client
|
||||||
OPENCLAW_WATCH_APP_BUNDLE_ID = ai.openclaw.ios.watchkitapp
|
OPENCLAW_WATCH_APP_BUNDLE_ID = ai.openclaw.client.watchkitapp
|
||||||
OPENCLAW_WATCH_EXTENSION_BUNDLE_ID = ai.openclaw.ios.watchkitapp.extension
|
OPENCLAW_WATCH_EXTENSION_BUNDLE_ID = ai.openclaw.client.watchkitapp.extension
|
||||||
OPENCLAW_ACTIVITY_WIDGET_BUNDLE_ID = ai.openclaw.ios.activitywidget
|
OPENCLAW_ACTIVITY_WIDGET_BUNDLE_ID = ai.openclaw.client.activitywidget
|
||||||
|
|
||||||
// Local contributors can override this by running scripts/ios-configure-signing.sh.
|
// Local contributors can override this by running scripts/ios-configure-signing.sh.
|
||||||
// Keep include after defaults: xcconfig is evaluated top-to-bottom.
|
// Keep include after defaults: xcconfig is evaluated top-to-bottom.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
// Shared iOS version defaults.
|
||||||
|
// Generated overrides live in build/Version.xcconfig (git-ignored).
|
||||||
|
|
||||||
|
OPENCLAW_GATEWAY_VERSION = 0.0.0
|
||||||
|
OPENCLAW_MARKETING_VERSION = 0.0.0
|
||||||
|
OPENCLAW_BUILD_VERSION = 0
|
||||||
|
|
||||||
|
#include? "../build/Version.xcconfig"
|
||||||
|
|
@ -1,15 +1,12 @@
|
||||||
# OpenClaw iOS (Super Alpha)
|
# OpenClaw iOS (Super Alpha)
|
||||||
|
|
||||||
NO TEST FLIGHT AVAILABLE AT THIS POINT
|
|
||||||
|
|
||||||
This iPhone app is super-alpha and internal-use only. It connects to an OpenClaw Gateway as a `role: node`.
|
This iPhone app is super-alpha and internal-use only. It connects to an OpenClaw Gateway as a `role: node`.
|
||||||
|
|
||||||
## Distribution Status
|
## Distribution Status
|
||||||
|
|
||||||
NO TEST FLIGHT AVAILABLE AT THIS POINT
|
- Public distribution: not available.
|
||||||
|
- Internal beta distribution: local archive + TestFlight upload via Fastlane.
|
||||||
- Current distribution: local/manual deploy from source via Xcode.
|
- Local/manual deploy from source via Xcode remains the default development path.
|
||||||
- App Store flow is not part of the current internal development path.
|
|
||||||
|
|
||||||
## Super-Alpha Disclaimer
|
## Super-Alpha Disclaimer
|
||||||
|
|
||||||
|
|
@ -50,6 +47,45 @@ Shortcut command (same flow + open project):
|
||||||
pnpm ios:open
|
pnpm ios:open
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Local Beta Release Flow
|
||||||
|
|
||||||
|
Prereqs:
|
||||||
|
|
||||||
|
- Xcode 16+
|
||||||
|
- `pnpm`
|
||||||
|
- `xcodegen`
|
||||||
|
- `fastlane`
|
||||||
|
- Apple account signed into Xcode for automatic signing/provisioning
|
||||||
|
- App Store Connect API key set up in Keychain via `scripts/ios-asc-keychain-setup.sh` when auto-resolving a beta build number or uploading to TestFlight
|
||||||
|
|
||||||
|
Release behavior:
|
||||||
|
|
||||||
|
- Local development keeps using unique per-developer bundle IDs from `scripts/ios-configure-signing.sh`.
|
||||||
|
- Beta release uses canonical `ai.openclaw.client*` bundle IDs through a temporary generated xcconfig in `apps/ios/build/BetaRelease.xcconfig`.
|
||||||
|
- The beta flow does not modify `apps/ios/.local-signing.xcconfig` or `apps/ios/LocalSigning.xcconfig`.
|
||||||
|
- Root `package.json.version` is the only version source for iOS.
|
||||||
|
- A root version like `2026.3.9-beta.1` becomes:
|
||||||
|
- `CFBundleShortVersionString = 2026.3.9`
|
||||||
|
- `CFBundleVersion = next TestFlight build number for 2026.3.9`
|
||||||
|
|
||||||
|
Archive without upload:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm ios:beta:archive
|
||||||
|
```
|
||||||
|
|
||||||
|
Archive and upload to TestFlight:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm ios:beta
|
||||||
|
```
|
||||||
|
|
||||||
|
If you need to force a specific build number:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm ios:beta -- --build-number 7
|
||||||
|
```
|
||||||
|
|
||||||
## APNs Expectations For Local/Manual Builds
|
## APNs Expectations For Local/Manual Builds
|
||||||
|
|
||||||
- The app calls `registerForRemoteNotifications()` at launch.
|
- The app calls `registerForRemoteNotifications()` at launch.
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,9 @@
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>XPC!</string>
|
<string>XPC!</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2026.3.9</string>
|
<string>$(OPENCLAW_MARKETING_VERSION)</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>20260308</string>
|
<string>$(OPENCLAW_BUILD_VERSION)</string>
|
||||||
<key>NSExtension</key>
|
<key>NSExtension</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSExtensionAttributes</key>
|
<key>NSExtensionAttributes</key>
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
// Auto-selected local team overrides live in .local-signing.xcconfig (git-ignored).
|
// Auto-selected local team overrides live in .local-signing.xcconfig (git-ignored).
|
||||||
// Manual local overrides can go in LocalSigning.xcconfig (git-ignored).
|
// Manual local overrides can go in LocalSigning.xcconfig (git-ignored).
|
||||||
|
|
||||||
|
#include "Config/Version.xcconfig"
|
||||||
|
|
||||||
OPENCLAW_CODE_SIGN_STYLE = Manual
|
OPENCLAW_CODE_SIGN_STYLE = Manual
|
||||||
OPENCLAW_DEVELOPMENT_TEAM = Y5PE65HELJ
|
OPENCLAW_DEVELOPMENT_TEAM = Y5PE65HELJ
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2026.3.9</string>
|
<string>$(OPENCLAW_MARKETING_VERSION)</string>
|
||||||
<key>CFBundleURLTypes</key>
|
<key>CFBundleURLTypes</key>
|
||||||
<array>
|
<array>
|
||||||
<dict>
|
<dict>
|
||||||
|
|
@ -36,7 +36,7 @@
|
||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>20260308</string>
|
<string>$(OPENCLAW_BUILD_VERSION)</string>
|
||||||
<key>ITSAppUsesNonExemptEncryption</key>
|
<key>ITSAppUsesNonExemptEncryption</key>
|
||||||
<false/>
|
<false/>
|
||||||
<key>NSAppTransportSecurity</key>
|
<key>NSAppTransportSecurity</key>
|
||||||
|
|
|
||||||
|
|
@ -2255,8 +2255,7 @@ extension NodeAppModel {
|
||||||
from: payload)
|
from: payload)
|
||||||
guard !decoded.actions.isEmpty else { return }
|
guard !decoded.actions.isEmpty else { return }
|
||||||
self.pendingActionLogger.info(
|
self.pendingActionLogger.info(
|
||||||
"Pending actions pulled trigger=\(trigger, privacy: .public) "
|
"Pending actions pulled trigger=\(trigger, privacy: .public) count=\(decoded.actions.count, privacy: .public)")
|
||||||
+ "count=\(decoded.actions.count, privacy: .public)")
|
|
||||||
await self.applyPendingForegroundNodeActions(decoded.actions, trigger: trigger)
|
await self.applyPendingForegroundNodeActions(decoded.actions, trigger: trigger)
|
||||||
} catch {
|
} catch {
|
||||||
// Best-effort only.
|
// Best-effort only.
|
||||||
|
|
@ -2279,9 +2278,7 @@ extension NodeAppModel {
|
||||||
paramsJSON: action.paramsJSON)
|
paramsJSON: action.paramsJSON)
|
||||||
let result = await self.handleInvoke(req)
|
let result = await self.handleInvoke(req)
|
||||||
self.pendingActionLogger.info(
|
self.pendingActionLogger.info(
|
||||||
"Pending action replay trigger=\(trigger, privacy: .public) "
|
"Pending action replay trigger=\(trigger, privacy: .public) id=\(action.id, privacy: .public) command=\(action.command, privacy: .public) ok=\(result.ok, privacy: .public)")
|
||||||
+ "id=\(action.id, privacy: .public) command=\(action.command, privacy: .public) "
|
|
||||||
+ "ok=\(result.ok, privacy: .public)")
|
|
||||||
guard result.ok else { return }
|
guard result.ok else { return }
|
||||||
let acked = await self.ackPendingForegroundNodeAction(
|
let acked = await self.ackPendingForegroundNodeAction(
|
||||||
id: action.id,
|
id: action.id,
|
||||||
|
|
@ -2306,9 +2303,7 @@ extension NodeAppModel {
|
||||||
return true
|
return true
|
||||||
} catch {
|
} catch {
|
||||||
self.pendingActionLogger.error(
|
self.pendingActionLogger.error(
|
||||||
"Pending action ack failed trigger=\(trigger, privacy: .public) "
|
"Pending action ack failed trigger=\(trigger, privacy: .public) id=\(id, privacy: .public) command=\(command, privacy: .public) error=\(String(describing: error), privacy: .public)")
|
||||||
+ "id=\(id, privacy: .public) command=\(command, privacy: .public) "
|
|
||||||
+ "error=\(String(describing: error), privacy: .public)")
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,8 @@
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>BNDL</string>
|
<string>BNDL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2026.3.9</string>
|
<string>$(OPENCLAW_MARKETING_VERSION)</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>20260308</string>
|
<string>$(OPENCLAW_BUILD_VERSION)</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,9 @@
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2026.3.9</string>
|
<string>$(OPENCLAW_MARKETING_VERSION)</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>20260308</string>
|
<string>$(OPENCLAW_BUILD_VERSION)</string>
|
||||||
<key>WKCompanionAppBundleIdentifier</key>
|
<key>WKCompanionAppBundleIdentifier</key>
|
||||||
<string>$(OPENCLAW_APP_BUNDLE_ID)</string>
|
<string>$(OPENCLAW_APP_BUNDLE_ID)</string>
|
||||||
<key>WKWatchKitApp</key>
|
<key>WKWatchKitApp</key>
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,9 @@
|
||||||
<key>CFBundleName</key>
|
<key>CFBundleName</key>
|
||||||
<string>$(PRODUCT_NAME)</string>
|
<string>$(PRODUCT_NAME)</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2026.3.9</string>
|
<string>$(OPENCLAW_MARKETING_VERSION)</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>20260308</string>
|
<string>$(OPENCLAW_BUILD_VERSION)</string>
|
||||||
<key>NSExtension</key>
|
<key>NSExtension</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSExtensionAttributes</key>
|
<key>NSExtensionAttributes</key>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
require "shellwords"
|
require "shellwords"
|
||||||
require "open3"
|
require "open3"
|
||||||
|
require "json"
|
||||||
|
|
||||||
default_platform(:ios)
|
default_platform(:ios)
|
||||||
|
|
||||||
|
BETA_APP_IDENTIFIER = "ai.openclaw.client"
|
||||||
|
|
||||||
def load_env_file(path)
|
def load_env_file(path)
|
||||||
return unless File.exist?(path)
|
return unless File.exist?(path)
|
||||||
|
|
||||||
|
|
@ -84,6 +87,111 @@ def read_asc_key_content_from_keychain
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def repo_root
|
||||||
|
File.expand_path("../../..", __dir__)
|
||||||
|
end
|
||||||
|
|
||||||
|
def ios_root
|
||||||
|
File.expand_path("..", __dir__)
|
||||||
|
end
|
||||||
|
|
||||||
|
def normalize_release_version(raw_value)
|
||||||
|
version = raw_value.to_s.strip.sub(/\Av/, "")
|
||||||
|
UI.user_error!("Missing root package.json version.") unless env_present?(version)
|
||||||
|
unless version.match?(/\A\d+\.\d+\.\d+(?:[.-]?beta[.-]\d+)?\z/i)
|
||||||
|
UI.user_error!("Invalid package.json version '#{raw_value}'. Expected 2026.3.9 or 2026.3.9-beta.1.")
|
||||||
|
end
|
||||||
|
|
||||||
|
version
|
||||||
|
end
|
||||||
|
|
||||||
|
def read_root_package_version
|
||||||
|
package_json_path = File.join(repo_root, "package.json")
|
||||||
|
UI.user_error!("Missing package.json at #{package_json_path}.") unless File.exist?(package_json_path)
|
||||||
|
|
||||||
|
parsed = JSON.parse(File.read(package_json_path))
|
||||||
|
normalize_release_version(parsed["version"])
|
||||||
|
rescue JSON::ParserError => e
|
||||||
|
UI.user_error!("Invalid package.json at #{package_json_path}: #{e.message}")
|
||||||
|
end
|
||||||
|
|
||||||
|
def short_release_version(version)
|
||||||
|
normalize_release_version(version).sub(/([.-]?beta[.-]\d+)\z/i, "")
|
||||||
|
end
|
||||||
|
|
||||||
|
def shell_join(parts)
|
||||||
|
Shellwords.join(parts.compact)
|
||||||
|
end
|
||||||
|
|
||||||
|
def resolve_beta_build_number(api_key:, version:)
|
||||||
|
explicit = ENV["IOS_BETA_BUILD_NUMBER"]
|
||||||
|
if env_present?(explicit)
|
||||||
|
UI.user_error!("Invalid IOS_BETA_BUILD_NUMBER '#{explicit}'. Expected digits only.") unless explicit.match?(/\A\d+\z/)
|
||||||
|
UI.message("Using explicit iOS beta build number #{explicit}.")
|
||||||
|
return explicit
|
||||||
|
end
|
||||||
|
|
||||||
|
short_version = short_release_version(version)
|
||||||
|
latest_build = latest_testflight_build_number(
|
||||||
|
api_key: api_key,
|
||||||
|
app_identifier: BETA_APP_IDENTIFIER,
|
||||||
|
version: short_version,
|
||||||
|
initial_build_number: 0
|
||||||
|
)
|
||||||
|
next_build = latest_build.to_i + 1
|
||||||
|
UI.message("Resolved iOS beta build number #{next_build} for #{short_version} (latest TestFlight build: #{latest_build}).")
|
||||||
|
next_build.to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
def beta_build_number_needs_asc_auth?
|
||||||
|
explicit = ENV["IOS_BETA_BUILD_NUMBER"]
|
||||||
|
!env_present?(explicit)
|
||||||
|
end
|
||||||
|
|
||||||
|
def prepare_beta_release!(version:, build_number:)
|
||||||
|
script_path = File.join(repo_root, "scripts", "ios-beta-prepare.sh")
|
||||||
|
UI.message("Preparing iOS beta release #{version} (build #{build_number}).")
|
||||||
|
sh(shell_join(["bash", script_path, "--build-number", build_number]))
|
||||||
|
|
||||||
|
beta_xcconfig = File.join(ios_root, "build", "BetaRelease.xcconfig")
|
||||||
|
UI.user_error!("Missing beta xcconfig at #{beta_xcconfig}.") unless File.exist?(beta_xcconfig)
|
||||||
|
|
||||||
|
ENV["XCODE_XCCONFIG_FILE"] = beta_xcconfig
|
||||||
|
beta_xcconfig
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_beta_release(context)
|
||||||
|
version = context[:version]
|
||||||
|
output_directory = File.join("build", "beta")
|
||||||
|
archive_path = File.join(output_directory, "OpenClaw-#{version}.xcarchive")
|
||||||
|
|
||||||
|
build_app(
|
||||||
|
project: "OpenClaw.xcodeproj",
|
||||||
|
scheme: "OpenClaw",
|
||||||
|
configuration: "Release",
|
||||||
|
export_method: "app-store",
|
||||||
|
clean: true,
|
||||||
|
skip_profile_detection: true,
|
||||||
|
build_path: "build",
|
||||||
|
archive_path: archive_path,
|
||||||
|
output_directory: output_directory,
|
||||||
|
output_name: "OpenClaw-#{version}.ipa",
|
||||||
|
xcargs: "-allowProvisioningUpdates",
|
||||||
|
export_xcargs: "-allowProvisioningUpdates",
|
||||||
|
export_options: {
|
||||||
|
signingStyle: "automatic"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
{
|
||||||
|
archive_path: archive_path,
|
||||||
|
build_number: context[:build_number],
|
||||||
|
ipa_path: lane_context[SharedValues::IPA_OUTPUT_PATH],
|
||||||
|
short_version: context[:short_version],
|
||||||
|
version: version
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
platform :ios do
|
platform :ios do
|
||||||
private_lane :asc_api_key do
|
private_lane :asc_api_key do
|
||||||
load_env_file(File.join(__dir__, ".env"))
|
load_env_file(File.join(__dir__, ".env"))
|
||||||
|
|
@ -132,38 +240,48 @@ platform :ios do
|
||||||
api_key
|
api_key
|
||||||
end
|
end
|
||||||
|
|
||||||
desc "Build + upload to TestFlight"
|
private_lane :prepare_beta_context do |options|
|
||||||
|
require_api_key = options[:require_api_key] == true
|
||||||
|
needs_api_key = require_api_key || beta_build_number_needs_asc_auth?
|
||||||
|
api_key = needs_api_key ? asc_api_key : nil
|
||||||
|
version = read_root_package_version
|
||||||
|
build_number = resolve_beta_build_number(api_key: api_key, version: version)
|
||||||
|
beta_xcconfig = prepare_beta_release!(version: version, build_number: build_number)
|
||||||
|
|
||||||
|
{
|
||||||
|
api_key: api_key,
|
||||||
|
beta_xcconfig: beta_xcconfig,
|
||||||
|
build_number: build_number,
|
||||||
|
short_version: short_release_version(version),
|
||||||
|
version: version
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
desc "Build a beta archive locally without uploading"
|
||||||
|
lane :beta_archive do
|
||||||
|
context = prepare_beta_context(require_api_key: false)
|
||||||
|
build = build_beta_release(context)
|
||||||
|
UI.success("Built iOS beta archive: version=#{build[:version]} short=#{build[:short_version]} build=#{build[:build_number]}")
|
||||||
|
build
|
||||||
|
ensure
|
||||||
|
ENV.delete("XCODE_XCCONFIG_FILE")
|
||||||
|
end
|
||||||
|
|
||||||
|
desc "Build + upload a beta to TestFlight"
|
||||||
lane :beta do
|
lane :beta do
|
||||||
api_key = asc_api_key
|
context = prepare_beta_context(require_api_key: true)
|
||||||
|
build = build_beta_release(context)
|
||||||
team_id = ENV["IOS_DEVELOPMENT_TEAM"]
|
|
||||||
if team_id.nil? || team_id.strip.empty?
|
|
||||||
helper_path = File.expand_path("../../../scripts/ios-team-id.sh", __dir__)
|
|
||||||
if File.exist?(helper_path)
|
|
||||||
# Keep CI/local compatibility where teams are present in keychain but not Xcode account metadata.
|
|
||||||
team_id = sh("IOS_ALLOW_KEYCHAIN_TEAM_FALLBACK=1 bash #{helper_path.shellescape}").strip
|
|
||||||
end
|
|
||||||
end
|
|
||||||
UI.user_error!("Missing IOS_DEVELOPMENT_TEAM (Apple Team ID). Add it to fastlane/.env or export it in your shell.") if team_id.nil? || team_id.strip.empty?
|
|
||||||
|
|
||||||
build_app(
|
|
||||||
project: "OpenClaw.xcodeproj",
|
|
||||||
scheme: "OpenClaw",
|
|
||||||
export_method: "app-store",
|
|
||||||
clean: true,
|
|
||||||
skip_profile_detection: true,
|
|
||||||
xcargs: "DEVELOPMENT_TEAM=#{team_id} -allowProvisioningUpdates",
|
|
||||||
export_xcargs: "-allowProvisioningUpdates",
|
|
||||||
export_options: {
|
|
||||||
signingStyle: "automatic"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
upload_to_testflight(
|
upload_to_testflight(
|
||||||
api_key: api_key,
|
api_key: context[:api_key],
|
||||||
|
ipa: build[:ipa_path],
|
||||||
skip_waiting_for_build_processing: true,
|
skip_waiting_for_build_processing: true,
|
||||||
uses_non_exempt_encryption: false
|
uses_non_exempt_encryption: false
|
||||||
)
|
)
|
||||||
|
|
||||||
|
UI.success("Uploaded iOS beta: version=#{build[:version]} short=#{build[:short_version]} build=#{build[:build_number]}")
|
||||||
|
ensure
|
||||||
|
ENV.delete("XCODE_XCCONFIG_FILE")
|
||||||
end
|
end
|
||||||
|
|
||||||
desc "Upload App Store metadata (and optionally screenshots)"
|
desc "Upload App Store metadata (and optionally screenshots)"
|
||||||
|
|
|
||||||
|
|
@ -32,9 +32,9 @@ ASC_KEYCHAIN_ACCOUNT=YOUR_MAC_USERNAME
|
||||||
Optional app targeting variables (helpful if Fastlane cannot auto-resolve app by bundle):
|
Optional app targeting variables (helpful if Fastlane cannot auto-resolve app by bundle):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ASC_APP_IDENTIFIER=ai.openclaw.ios
|
ASC_APP_IDENTIFIER=ai.openclaw.client
|
||||||
# or
|
# or
|
||||||
ASC_APP_ID=6760218713
|
ASC_APP_ID=YOUR_APP_STORE_CONNECT_APP_ID
|
||||||
```
|
```
|
||||||
|
|
||||||
File-based fallback (CI/non-macOS):
|
File-based fallback (CI/non-macOS):
|
||||||
|
|
@ -60,9 +60,37 @@ cd apps/ios
|
||||||
fastlane ios auth_check
|
fastlane ios auth_check
|
||||||
```
|
```
|
||||||
|
|
||||||
Run:
|
ASC auth is only required when:
|
||||||
|
|
||||||
|
- uploading to TestFlight
|
||||||
|
- auto-resolving the next build number from App Store Connect
|
||||||
|
|
||||||
|
If you pass `--build-number` to `pnpm ios:beta:archive`, the local archive path does not need ASC auth.
|
||||||
|
|
||||||
|
Archive locally without upload:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm ios:beta:archive
|
||||||
|
```
|
||||||
|
|
||||||
|
Upload to TestFlight:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm ios:beta
|
||||||
|
```
|
||||||
|
|
||||||
|
Direct Fastlane entry point:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd apps/ios
|
cd apps/ios
|
||||||
fastlane beta
|
fastlane ios beta
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Versioning rules:
|
||||||
|
|
||||||
|
- Root `package.json.version` is the single source of truth for iOS
|
||||||
|
- Use `YYYY.M.D` for stable versions and `YYYY.M.D-beta.N` for beta versions
|
||||||
|
- Fastlane stamps `CFBundleShortVersionString` to `YYYY.M.D`
|
||||||
|
- Fastlane resolves `CFBundleVersion` as the next integer TestFlight build number for that short version
|
||||||
|
- The beta flow regenerates `apps/ios/OpenClaw.xcodeproj` from `apps/ios/project.yml` before archiving
|
||||||
|
- Local beta signing uses a temporary generated xcconfig and leaves local development signing overrides untouched
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ This directory is used by `fastlane deliver` for App Store Connect text metadata
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd apps/ios
|
cd apps/ios
|
||||||
ASC_APP_ID=6760218713 \
|
ASC_APP_ID=YOUR_APP_STORE_CONNECT_APP_ID \
|
||||||
DELIVER_METADATA=1 fastlane ios metadata
|
DELIVER_METADATA=1 fastlane ios metadata
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -107,8 +107,8 @@ targets:
|
||||||
- CFBundleURLName: ai.openclaw.ios
|
- CFBundleURLName: ai.openclaw.ios
|
||||||
CFBundleURLSchemes:
|
CFBundleURLSchemes:
|
||||||
- openclaw
|
- openclaw
|
||||||
CFBundleShortVersionString: "2026.3.9"
|
CFBundleShortVersionString: "$(OPENCLAW_MARKETING_VERSION)"
|
||||||
CFBundleVersion: "20260308"
|
CFBundleVersion: "$(OPENCLAW_BUILD_VERSION)"
|
||||||
UILaunchScreen: {}
|
UILaunchScreen: {}
|
||||||
UIApplicationSceneManifest:
|
UIApplicationSceneManifest:
|
||||||
UIApplicationSupportsMultipleScenes: false
|
UIApplicationSupportsMultipleScenes: false
|
||||||
|
|
@ -168,8 +168,8 @@ targets:
|
||||||
path: ShareExtension/Info.plist
|
path: ShareExtension/Info.plist
|
||||||
properties:
|
properties:
|
||||||
CFBundleDisplayName: OpenClaw Share
|
CFBundleDisplayName: OpenClaw Share
|
||||||
CFBundleShortVersionString: "2026.3.9"
|
CFBundleShortVersionString: "$(OPENCLAW_MARKETING_VERSION)"
|
||||||
CFBundleVersion: "20260308"
|
CFBundleVersion: "$(OPENCLAW_BUILD_VERSION)"
|
||||||
NSExtension:
|
NSExtension:
|
||||||
NSExtensionPointIdentifier: com.apple.share-services
|
NSExtensionPointIdentifier: com.apple.share-services
|
||||||
NSExtensionPrincipalClass: "$(PRODUCT_MODULE_NAME).ShareViewController"
|
NSExtensionPrincipalClass: "$(PRODUCT_MODULE_NAME).ShareViewController"
|
||||||
|
|
@ -205,8 +205,8 @@ targets:
|
||||||
path: ActivityWidget/Info.plist
|
path: ActivityWidget/Info.plist
|
||||||
properties:
|
properties:
|
||||||
CFBundleDisplayName: OpenClaw Activity
|
CFBundleDisplayName: OpenClaw Activity
|
||||||
CFBundleShortVersionString: "2026.3.9"
|
CFBundleShortVersionString: "$(OPENCLAW_MARKETING_VERSION)"
|
||||||
CFBundleVersion: "20260308"
|
CFBundleVersion: "$(OPENCLAW_BUILD_VERSION)"
|
||||||
NSSupportsLiveActivities: true
|
NSSupportsLiveActivities: true
|
||||||
NSExtension:
|
NSExtension:
|
||||||
NSExtensionPointIdentifier: com.apple.widgetkit-extension
|
NSExtensionPointIdentifier: com.apple.widgetkit-extension
|
||||||
|
|
@ -224,6 +224,7 @@ targets:
|
||||||
Release: Config/Signing.xcconfig
|
Release: Config/Signing.xcconfig
|
||||||
settings:
|
settings:
|
||||||
base:
|
base:
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon
|
||||||
ENABLE_APPINTENTS_METADATA: NO
|
ENABLE_APPINTENTS_METADATA: NO
|
||||||
ENABLE_APP_INTENTS_METADATA_GENERATION: NO
|
ENABLE_APP_INTENTS_METADATA_GENERATION: NO
|
||||||
PRODUCT_BUNDLE_IDENTIFIER: "$(OPENCLAW_WATCH_APP_BUNDLE_ID)"
|
PRODUCT_BUNDLE_IDENTIFIER: "$(OPENCLAW_WATCH_APP_BUNDLE_ID)"
|
||||||
|
|
@ -231,8 +232,8 @@ targets:
|
||||||
path: WatchApp/Info.plist
|
path: WatchApp/Info.plist
|
||||||
properties:
|
properties:
|
||||||
CFBundleDisplayName: OpenClaw
|
CFBundleDisplayName: OpenClaw
|
||||||
CFBundleShortVersionString: "2026.3.9"
|
CFBundleShortVersionString: "$(OPENCLAW_MARKETING_VERSION)"
|
||||||
CFBundleVersion: "20260308"
|
CFBundleVersion: "$(OPENCLAW_BUILD_VERSION)"
|
||||||
WKCompanionAppBundleIdentifier: "$(OPENCLAW_APP_BUNDLE_ID)"
|
WKCompanionAppBundleIdentifier: "$(OPENCLAW_APP_BUNDLE_ID)"
|
||||||
WKWatchKitApp: true
|
WKWatchKitApp: true
|
||||||
|
|
||||||
|
|
@ -256,8 +257,8 @@ targets:
|
||||||
path: WatchExtension/Info.plist
|
path: WatchExtension/Info.plist
|
||||||
properties:
|
properties:
|
||||||
CFBundleDisplayName: OpenClaw
|
CFBundleDisplayName: OpenClaw
|
||||||
CFBundleShortVersionString: "2026.3.9"
|
CFBundleShortVersionString: "$(OPENCLAW_MARKETING_VERSION)"
|
||||||
CFBundleVersion: "20260308"
|
CFBundleVersion: "$(OPENCLAW_BUILD_VERSION)"
|
||||||
NSExtension:
|
NSExtension:
|
||||||
NSExtensionAttributes:
|
NSExtensionAttributes:
|
||||||
WKAppBundleIdentifier: "$(OPENCLAW_WATCH_APP_BUNDLE_ID)"
|
WKAppBundleIdentifier: "$(OPENCLAW_WATCH_APP_BUNDLE_ID)"
|
||||||
|
|
@ -293,8 +294,8 @@ targets:
|
||||||
path: Tests/Info.plist
|
path: Tests/Info.plist
|
||||||
properties:
|
properties:
|
||||||
CFBundleDisplayName: OpenClawTests
|
CFBundleDisplayName: OpenClawTests
|
||||||
CFBundleShortVersionString: "2026.3.9"
|
CFBundleShortVersionString: "$(OPENCLAW_MARKETING_VERSION)"
|
||||||
CFBundleVersion: "20260308"
|
CFBundleVersion: "$(OPENCLAW_BUILD_VERSION)"
|
||||||
|
|
||||||
OpenClawLogicTests:
|
OpenClawLogicTests:
|
||||||
type: bundle.unit-test
|
type: bundle.unit-test
|
||||||
|
|
@ -319,5 +320,5 @@ targets:
|
||||||
path: Tests/Info.plist
|
path: Tests/Info.plist
|
||||||
properties:
|
properties:
|
||||||
CFBundleDisplayName: OpenClawLogicTests
|
CFBundleDisplayName: OpenClawLogicTests
|
||||||
CFBundleShortVersionString: "2026.3.9"
|
CFBundleShortVersionString: "$(OPENCLAW_MARKETING_VERSION)"
|
||||||
CFBundleVersion: "20260308"
|
CFBundleVersion: "$(OPENCLAW_BUILD_VERSION)"
|
||||||
|
|
|
||||||
11
package.json
11
package.json
|
|
@ -262,10 +262,13 @@
|
||||||
"gateway:watch": "node scripts/watch-node.mjs gateway --force",
|
"gateway:watch": "node scripts/watch-node.mjs gateway --force",
|
||||||
"gen:host-env-policy:swift": "node scripts/generate-host-env-security-policy-swift.mjs --write",
|
"gen:host-env-policy:swift": "node scripts/generate-host-env-security-policy-swift.mjs --write",
|
||||||
"ghsa:patch": "node scripts/ghsa-patch.mjs",
|
"ghsa:patch": "node scripts/ghsa-patch.mjs",
|
||||||
"ios:build": "bash -lc './scripts/ios-configure-signing.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:beta": "bash scripts/ios-beta-release.sh",
|
||||||
"ios:gen": "bash -lc './scripts/ios-configure-signing.sh && cd apps/ios && xcodegen generate'",
|
"ios:beta:archive": "bash scripts/ios-beta-archive.sh",
|
||||||
"ios:open": "bash -lc './scripts/ios-configure-signing.sh && cd apps/ios && xcodegen generate && open OpenClaw.xcodeproj'",
|
"ios:beta:prepare": "bash scripts/ios-beta-prepare.sh",
|
||||||
"ios:run": "bash -lc './scripts/ios-configure-signing.sh && cd apps/ios && xcodegen generate && xcodebuild -project OpenClaw.xcodeproj -scheme OpenClaw -destination \"${IOS_DEST:-platform=iOS Simulator,name=iPhone 17}\" -configuration Debug build && xcrun simctl boot \"${IOS_SIM:-iPhone 17}\" || true && xcrun simctl launch booted ai.openclaw.ios'",
|
"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:run": "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 && xcrun simctl boot \"${IOS_SIM:-iPhone 17}\" || true && xcrun simctl launch booted ai.openclaw.ios'",
|
||||||
"lint": "oxlint --type-aware",
|
"lint": "oxlint --type-aware",
|
||||||
"lint:agent:ingress-owner": "node scripts/check-ingress-agent-owner-context.mjs",
|
"lint:agent:ingress-owner": "node scripts/check-ingress-agent-owner-context.mjs",
|
||||||
"lint:all": "pnpm lint && pnpm lint:swift",
|
"lint:all": "pnpm lint && pnpm lint:swift",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage:
|
||||||
|
scripts/ios-beta-archive.sh [--build-number 7]
|
||||||
|
|
||||||
|
Archives and exports a beta-release IPA locally without uploading.
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
BUILD_NUMBER="${IOS_BETA_BUILD_NUMBER:-}"
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--)
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--build-number)
|
||||||
|
BUILD_NUMBER="${2:-}"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown argument: $1" >&2
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
(
|
||||||
|
cd "${ROOT_DIR}/apps/ios"
|
||||||
|
IOS_BETA_BUILD_NUMBER="${BUILD_NUMBER}" fastlane ios beta_archive
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,117 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage:
|
||||||
|
scripts/ios-beta-prepare.sh --build-number 7 [--team-id TEAMID]
|
||||||
|
|
||||||
|
Prepares local beta-release inputs without touching local signing overrides:
|
||||||
|
- reads package.json.version and writes apps/ios/build/Version.xcconfig
|
||||||
|
- writes apps/ios/build/BetaRelease.xcconfig with canonical bundle IDs
|
||||||
|
- regenerates apps/ios/OpenClaw.xcodeproj via xcodegen
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
IOS_DIR="${ROOT_DIR}/apps/ios"
|
||||||
|
BUILD_DIR="${IOS_DIR}/build"
|
||||||
|
BETA_XCCONFIG="${IOS_DIR}/build/BetaRelease.xcconfig"
|
||||||
|
TEAM_HELPER="${ROOT_DIR}/scripts/ios-team-id.sh"
|
||||||
|
VERSION_HELPER="${ROOT_DIR}/scripts/ios-write-version-xcconfig.sh"
|
||||||
|
|
||||||
|
BUILD_NUMBER=""
|
||||||
|
TEAM_ID="${IOS_DEVELOPMENT_TEAM:-}"
|
||||||
|
PACKAGE_VERSION="$(cd "${ROOT_DIR}" && node -p "require('./package.json').version" 2>/dev/null || true)"
|
||||||
|
|
||||||
|
prepare_build_dir() {
|
||||||
|
if [[ -L "${BUILD_DIR}" ]]; then
|
||||||
|
echo "Refusing to use symlinked build directory: ${BUILD_DIR}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "${BUILD_DIR}"
|
||||||
|
}
|
||||||
|
|
||||||
|
write_generated_file() {
|
||||||
|
local output_path="$1"
|
||||||
|
local tmp_file=""
|
||||||
|
|
||||||
|
if [[ -e "${output_path}" && -L "${output_path}" ]]; then
|
||||||
|
echo "Refusing to overwrite symlinked file: ${output_path}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
tmp_file="$(mktemp "${output_path}.XXXXXX")"
|
||||||
|
cat >"${tmp_file}"
|
||||||
|
mv -f "${tmp_file}" "${output_path}"
|
||||||
|
}
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--)
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--build-number)
|
||||||
|
BUILD_NUMBER="${2:-}"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--team-id)
|
||||||
|
TEAM_ID="${2:-}"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown argument: $1" >&2
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -z "${BUILD_NUMBER}" ]]; then
|
||||||
|
echo "Missing required --build-number." >&2
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "${TEAM_ID}" ]]; then
|
||||||
|
TEAM_ID="$(IOS_ALLOW_KEYCHAIN_TEAM_FALLBACK=1 bash "${TEAM_HELPER}")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "${TEAM_ID}" ]]; then
|
||||||
|
echo "Could not resolve Apple Team ID. Set IOS_DEVELOPMENT_TEAM or sign into Xcode." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
prepare_build_dir
|
||||||
|
|
||||||
|
(
|
||||||
|
bash "${VERSION_HELPER}" --build-number "${BUILD_NUMBER}"
|
||||||
|
)
|
||||||
|
|
||||||
|
write_generated_file "${BETA_XCCONFIG}" <<EOF
|
||||||
|
// Auto-generated by scripts/ios-beta-prepare.sh.
|
||||||
|
// Local beta-release override; do not commit.
|
||||||
|
OPENCLAW_CODE_SIGN_STYLE = Automatic
|
||||||
|
OPENCLAW_DEVELOPMENT_TEAM = ${TEAM_ID}
|
||||||
|
OPENCLAW_IOS_SELECTED_TEAM = ${TEAM_ID}
|
||||||
|
OPENCLAW_APP_BUNDLE_ID = ai.openclaw.client
|
||||||
|
OPENCLAW_SHARE_BUNDLE_ID = ai.openclaw.client.share
|
||||||
|
OPENCLAW_ACTIVITY_WIDGET_BUNDLE_ID = ai.openclaw.client.activitywidget
|
||||||
|
OPENCLAW_WATCH_APP_BUNDLE_ID = ai.openclaw.client.watchkitapp
|
||||||
|
OPENCLAW_WATCH_EXTENSION_BUNDLE_ID = ai.openclaw.client.watchkitapp.extension
|
||||||
|
OPENCLAW_APP_PROFILE =
|
||||||
|
OPENCLAW_SHARE_PROFILE =
|
||||||
|
EOF
|
||||||
|
|
||||||
|
(
|
||||||
|
cd "${IOS_DIR}"
|
||||||
|
xcodegen generate
|
||||||
|
)
|
||||||
|
|
||||||
|
echo "Prepared iOS beta release: version=${PACKAGE_VERSION} build=${BUILD_NUMBER} team=${TEAM_ID}"
|
||||||
|
echo "XCODE_XCCONFIG_FILE=${BETA_XCCONFIG}"
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage:
|
||||||
|
scripts/ios-beta-release.sh [--build-number 7]
|
||||||
|
|
||||||
|
Archives and uploads a beta-release IPA to TestFlight locally.
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
BUILD_NUMBER="${IOS_BETA_BUILD_NUMBER:-}"
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--)
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--build-number)
|
||||||
|
BUILD_NUMBER="${2:-}"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown argument: $1" >&2
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
(
|
||||||
|
cd "${ROOT_DIR}/apps/ios"
|
||||||
|
IOS_BETA_BUILD_NUMBER="${BUILD_NUMBER}" fastlane ios beta
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,99 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage:
|
||||||
|
scripts/ios-write-version-xcconfig.sh [--build-number 7]
|
||||||
|
|
||||||
|
Writes apps/ios/build/Version.xcconfig from root package.json.version:
|
||||||
|
- OPENCLAW_GATEWAY_VERSION = exact package.json version
|
||||||
|
- OPENCLAW_MARKETING_VERSION = short iOS/App Store version
|
||||||
|
- OPENCLAW_BUILD_VERSION = explicit build number or local numeric fallback
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
IOS_DIR="${ROOT_DIR}/apps/ios"
|
||||||
|
BUILD_DIR="${IOS_DIR}/build"
|
||||||
|
VERSION_XCCONFIG="${IOS_DIR}/build/Version.xcconfig"
|
||||||
|
PACKAGE_VERSION="$(cd "${ROOT_DIR}" && node -p "require('./package.json').version" 2>/dev/null || true)"
|
||||||
|
BUILD_NUMBER=""
|
||||||
|
|
||||||
|
prepare_build_dir() {
|
||||||
|
if [[ -L "${BUILD_DIR}" ]]; then
|
||||||
|
echo "Refusing to use symlinked build directory: ${BUILD_DIR}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "${BUILD_DIR}"
|
||||||
|
}
|
||||||
|
|
||||||
|
write_generated_file() {
|
||||||
|
local output_path="$1"
|
||||||
|
local tmp_file=""
|
||||||
|
|
||||||
|
if [[ -e "${output_path}" && -L "${output_path}" ]]; then
|
||||||
|
echo "Refusing to overwrite symlinked file: ${output_path}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
tmp_file="$(mktemp "${output_path}.XXXXXX")"
|
||||||
|
cat >"${tmp_file}"
|
||||||
|
mv -f "${tmp_file}" "${output_path}"
|
||||||
|
}
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--)
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--build-number)
|
||||||
|
BUILD_NUMBER="${2:-}"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown argument: $1" >&2
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
PACKAGE_VERSION="$(printf '%s' "${PACKAGE_VERSION}" | tr -d '\n' | xargs)"
|
||||||
|
if [[ -z "${PACKAGE_VERSION}" ]]; then
|
||||||
|
echo "Unable to read package.json.version from ${ROOT_DIR}/package.json." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${PACKAGE_VERSION}" =~ ^([0-9]{4}\.[0-9]{1,2}\.[0-9]{1,2})([.-]?beta[.-][0-9]+)?$ ]]; then
|
||||||
|
MARKETING_VERSION="${BASH_REMATCH[1]}"
|
||||||
|
else
|
||||||
|
echo "Unsupported package.json.version '${PACKAGE_VERSION}'. Expected 2026.3.9 or 2026.3.9-beta.1." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "${BUILD_NUMBER}" ]]; then
|
||||||
|
BUILD_NUMBER="$(cd "${ROOT_DIR}" && git rev-list --count HEAD 2>/dev/null || printf '0')"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! "${BUILD_NUMBER}" =~ ^[0-9]+$ ]]; then
|
||||||
|
echo "Invalid build number '${BUILD_NUMBER}'. Expected digits only." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
prepare_build_dir
|
||||||
|
|
||||||
|
write_generated_file "${VERSION_XCCONFIG}" <<EOF
|
||||||
|
// Auto-generated by scripts/ios-write-version-xcconfig.sh.
|
||||||
|
// Local version override; do not commit.
|
||||||
|
OPENCLAW_GATEWAY_VERSION = ${PACKAGE_VERSION}
|
||||||
|
OPENCLAW_MARKETING_VERSION = ${MARKETING_VERSION}
|
||||||
|
OPENCLAW_BUILD_VERSION = ${BUILD_NUMBER}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "Prepared iOS version settings: gateway=${PACKAGE_VERSION} marketing=${MARKETING_VERSION} build=${BUILD_NUMBER}"
|
||||||
Loading…
Reference in New Issue