@@ -3,6 +3,8 @@ require "open3"
33
44default_platform ( :ios )
55
6+ BETA_APP_IDENTIFIER = "ai.openclaw.client"
7+
68def load_env_file ( path )
79 return unless File . exist? ( path )
810
@@ -84,6 +86,96 @@ def read_asc_key_content_from_keychain
8486 end
8587end
8688
89+ def repo_root
90+ File . expand_path ( "../../.." , __dir__ )
91+ end
92+
93+ def ios_root
94+ File . expand_path ( ".." , __dir__ )
95+ end
96+
97+ def normalize_beta_version ( raw_value )
98+ version = raw_value . to_s . strip . sub ( /\A v/ , "" )
99+ UI . user_error! ( "Missing IOS_BETA_VERSION. Example: IOS_BETA_VERSION=2026.3.9-beta.1 fastlane ios beta" ) unless env_present? ( version )
100+ unless version . match? ( /\A \d +\. \d +\. \d +(?:[.-]?beta[.-]\d +)?\z /i )
101+ UI . user_error! ( "Invalid IOS_BETA_VERSION '#{ raw_value } '. Expected 2026.3.9 or 2026.3.9-beta.1." )
102+ end
103+
104+ version
105+ end
106+
107+ def short_beta_version ( version )
108+ normalize_beta_version ( version ) . sub ( /([.-]?beta[.-]\d +)\z /i , "" )
109+ end
110+
111+ def shell_join ( parts )
112+ Shellwords . join ( parts . compact )
113+ end
114+
115+ def resolve_beta_build_number ( api_key :, version :)
116+ explicit = ENV [ "IOS_BETA_BUILD_NUMBER" ]
117+ if env_present? ( explicit )
118+ UI . user_error! ( "Invalid IOS_BETA_BUILD_NUMBER '#{ explicit } '. Expected digits only." ) unless explicit . match? ( /\A \d +\z / )
119+ UI . message ( "Using explicit iOS beta build number #{ explicit } ." )
120+ return explicit
121+ end
122+
123+ short_version = short_beta_version ( version )
124+ latest_build = latest_testflight_build_number (
125+ api_key : api_key ,
126+ app_identifier : BETA_APP_IDENTIFIER ,
127+ version : short_version ,
128+ initial_build_number : 0
129+ )
130+ next_build = latest_build . to_i + 1
131+ UI . message ( "Resolved iOS beta build number #{ next_build } for #{ short_version } (latest TestFlight build: #{ latest_build } )." )
132+ next_build . to_s
133+ end
134+
135+ def prepare_beta_release! ( version :, build_number :)
136+ script_path = File . join ( repo_root , "scripts" , "ios-beta-prepare.sh" )
137+ UI . message ( "Preparing iOS beta release #{ version } (build #{ build_number } )." )
138+ sh ( shell_join ( [ "bash" , script_path , "--version" , version , "--build-number" , build_number ] ) )
139+
140+ beta_xcconfig = File . join ( ios_root , "build" , "BetaRelease.xcconfig" )
141+ UI . user_error! ( "Missing beta xcconfig at #{ beta_xcconfig } ." ) unless File . exist? ( beta_xcconfig )
142+
143+ ENV [ "XCODE_XCCONFIG_FILE" ] = beta_xcconfig
144+ beta_xcconfig
145+ end
146+
147+ def build_beta_release ( context )
148+ version = context [ :version ]
149+ output_directory = File . join ( "build" , "beta" )
150+ archive_path = File . join ( output_directory , "OpenClaw-#{ version } .xcarchive" )
151+
152+ build_app (
153+ project : "OpenClaw.xcodeproj" ,
154+ scheme : "OpenClaw" ,
155+ configuration : "Release" ,
156+ export_method : "app-store" ,
157+ clean : true ,
158+ skip_profile_detection : true ,
159+ build_path : "build" ,
160+ archive_path : archive_path ,
161+ output_directory : output_directory ,
162+ output_name : "OpenClaw-#{ version } .ipa" ,
163+ xcargs : "-allowProvisioningUpdates" ,
164+ export_xcargs : "-allowProvisioningUpdates" ,
165+ export_options : {
166+ signingStyle : "automatic"
167+ }
168+ )
169+
170+ {
171+ archive_path : archive_path ,
172+ build_number : context [ :build_number ] ,
173+ ipa_path : lane_context [ SharedValues ::IPA_OUTPUT_PATH ] ,
174+ short_version : context [ :short_version ] ,
175+ version : version
176+ }
177+ end
178+
87179platform :ios do
88180 private_lane :asc_api_key do
89181 load_env_file ( File . join ( __dir__ , ".env" ) )
@@ -132,38 +224,46 @@ platform :ios do
132224 api_key
133225 end
134226
135- desc "Build + upload to TestFlight"
136- lane :beta do
227+ private_lane :prepare_beta_context do
137228 api_key = asc_api_key
229+ version = normalize_beta_version ( ENV [ "IOS_BETA_VERSION" ] )
230+ build_number = resolve_beta_build_number ( api_key : api_key , version : version )
231+ beta_xcconfig = prepare_beta_release! ( version : version , build_number : build_number )
138232
139- team_id = ENV [ "IOS_DEVELOPMENT_TEAM" ]
140- if team_id . nil? || team_id . strip . empty?
141- helper_path = File . expand_path ( "../../../scripts/ios-team-id.sh" , __dir__ )
142- if File . exist? ( helper_path )
143- # Keep CI/local compatibility where teams are present in keychain but not Xcode account metadata.
144- team_id = sh ( "IOS_ALLOW_KEYCHAIN_TEAM_FALLBACK=1 bash #{ helper_path . shellescape } " ) . strip
145- end
146- end
147- 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?
148-
149- build_app (
150- project : "OpenClaw.xcodeproj" ,
151- scheme : "OpenClaw" ,
152- export_method : "app-store" ,
153- clean : true ,
154- skip_profile_detection : true ,
155- xcargs : "DEVELOPMENT_TEAM=#{ team_id } -allowProvisioningUpdates" ,
156- export_xcargs : "-allowProvisioningUpdates" ,
157- export_options : {
158- signingStyle : "automatic"
159- }
160- )
233+ {
234+ api_key : api_key ,
235+ beta_xcconfig : beta_xcconfig ,
236+ build_number : build_number ,
237+ short_version : short_beta_version ( version ) ,
238+ version : version
239+ }
240+ end
241+
242+ desc "Build a beta archive locally without uploading"
243+ lane :beta_archive do
244+ context = prepare_beta_context
245+ build = build_beta_release ( context )
246+ UI . success ( "Built iOS beta archive: version=#{ build [ :version ] } short=#{ build [ :short_version ] } build=#{ build [ :build_number ] } " )
247+ build
248+ ensure
249+ ENV . delete ( "XCODE_XCCONFIG_FILE" )
250+ end
251+
252+ desc "Build + upload a beta to TestFlight"
253+ lane :beta do
254+ context = prepare_beta_context
255+ build = build_beta_release ( context )
161256
162257 upload_to_testflight (
163- api_key : api_key ,
258+ api_key : context [ :api_key ] ,
259+ ipa : build [ :ipa_path ] ,
164260 skip_waiting_for_build_processing : true ,
165261 uses_non_exempt_encryption : false
166262 )
263+
264+ UI . success ( "Uploaded iOS beta: version=#{ build [ :version ] } short=#{ build [ :short_version ] } build=#{ build [ :build_number ] } " )
265+ ensure
266+ ENV . delete ( "XCODE_XCCONFIG_FILE" )
167267 end
168268
169269 desc "Upload App Store metadata (and optionally screenshots)"
0 commit comments