@@ -9,6 +9,7 @@ require "tempfile"
99require "tmpdir"
1010require "yaml"
1111require "open3"
12+ require "spaceship"
1213require_relative "../patches/developer_id_application_certificate_type_patch"
1314
1415def shell_escape ( value )
@@ -19,13 +20,63 @@ def workspace_root
1920 File . expand_path ( "../.." , __dir__ )
2021end
2122
23+ MACOS_RELEASE_FLAVOR_CONFIGS = {
24+ "mainnet" => {
25+ app_name : "Vizor" ,
26+ artifact_name : "Vizor" ,
27+ bundle_id : "com.keplr.vizor" ,
28+ bundle_id_name : "Vizor" ,
29+ zcash_default_network : "main" ,
30+ volume_name : "Install Vizor Wallet"
31+ } ,
32+ "testnet" => {
33+ app_name : "Vizor Testnet" ,
34+ artifact_name : "Vizor-Testnet" ,
35+ bundle_id : "com.keplr.vizor.testnet" ,
36+ bundle_id_name : "Vizor Testnet" ,
37+ zcash_default_network : "test" ,
38+ volume_name : "Install Vizor Testnet Wallet"
39+ }
40+ } . freeze
41+
42+ DEFAULT_MACOS_RELEASE_FLAVORS = [ "mainnet" , "testnet" ] . freeze
43+
44+ def normalize_macos_release_flavor ( flavor )
45+ normalized = flavor . to_s . strip . downcase
46+ return normalized if MACOS_RELEASE_FLAVOR_CONFIGS . key? ( normalized )
47+
48+ UI . user_error! ( "Unsupported macOS release flavor: #{ flavor } . Expected one of: #{ MACOS_RELEASE_FLAVOR_CONFIGS . keys . join ( ", " ) } " )
49+ end
50+
51+ def macos_release_flavors
52+ raw = ENV [ "VIZOR_MACOS_FLAVORS" ] . to_s . strip
53+ if raw . empty?
54+ single_flavor = ENV [ "VIZOR_MACOS_FLAVOR" ] . to_s . strip
55+ return [ normalize_macos_release_flavor ( single_flavor ) ] unless single_flavor . empty?
56+
57+ return DEFAULT_MACOS_RELEASE_FLAVORS
58+ end
59+
60+ flavors = raw . split ( "," ) . map ( &:strip ) . reject ( &:empty? )
61+ UI . user_error! ( "VIZOR_MACOS_FLAVORS must include at least one flavor" ) if flavors . empty?
62+ flavors . map { |flavor | normalize_macos_release_flavor ( flavor ) } . uniq
63+ end
64+
65+ def single_macos_release_flavor ( options = { } )
66+ raw = options [ :flavor ] || ENV [ "VIZOR_MACOS_FLAVOR" ]
67+ raw = raw . to_s . strip
68+ raw . empty? ? "mainnet" : normalize_macos_release_flavor ( raw )
69+ end
70+
2271def release_version_from_tag ( tag )
2372 match = /\A release\/ v(\d +\. \d +\. \d +)\z / . match ( tag . to_s )
2473 UI . user_error! ( "Unsupported release tag format: #{ tag } " ) unless match
2574 match [ 1 ]
2675end
2776
28- def release_metadata
77+ def release_metadata ( flavor : "mainnet" )
78+ flavor = normalize_macos_release_flavor ( flavor )
79+ flavor_config = MACOS_RELEASE_FLAVOR_CONFIGS . fetch ( flavor )
2980 pubspec = YAML . safe_load ( File . read ( File . join ( workspace_root , "pubspec.yaml" ) ) )
3081 version_string = pubspec . fetch ( "version" )
3182 version , build_number = version_string . split ( "+" , 2 )
@@ -38,21 +89,27 @@ def release_metadata
3889 end
3990
4091 asset_version = release_version_from_tag ( tag )
41- app_name = "Vizor"
92+ app_name = flavor_config . fetch ( :app_name )
93+ artifact_name = flavor_config . fetch ( :artifact_name )
4294 dist_dir = File . join ( workspace_root , "dist" , "macos" )
4395 app_path = File . join ( workspace_root , "build" , "macos" , "Build" , "Products" , "Release" , "#{ app_name } .app" )
4496
4597 {
98+ flavor : flavor ,
4699 app_name : app_name ,
47- bundle_id : "com.keplr.vizor" ,
100+ artifact_name : artifact_name ,
101+ bundle_id : flavor_config . fetch ( :bundle_id ) ,
102+ bundle_id_name : flavor_config . fetch ( :bundle_id_name ) ,
103+ zcash_default_network : flavor_config . fetch ( :zcash_default_network ) ,
104+ volume_name : flavor_config . fetch ( :volume_name ) ,
48105 version : version ,
49106 build_number : build_number ,
50107 tag : tag ,
51108 release_name : ENV [ "RELEASE_NAME" ] || tag ,
52109 dist_dir : dist_dir ,
53110 app_path : app_path ,
54- zip_path : File . join ( dist_dir , "#{ app_name } -#{ asset_version } -macos.zip" ) ,
55- dmg_path : File . join ( dist_dir , "#{ app_name } -#{ asset_version } -macos.dmg" )
111+ zip_path : File . join ( dist_dir , "#{ artifact_name } -#{ asset_version } -macos.zip" ) ,
112+ dmg_path : File . join ( dist_dir , "#{ artifact_name } -#{ asset_version } -macos.dmg" )
56113 }
57114end
58115
@@ -164,6 +221,46 @@ def import_match_signing_assets!(output_dir, keychain)
164221 ensure_developer_id_identity! ( keychain [ :path ] )
165222end
166223
224+ def ensure_bundle_id_exists! ( metadata , api_key_path )
225+ Spaceship ::ConnectAPI . token = Spaceship ::ConnectAPI ::Token . from ( filepath : api_key_path )
226+
227+ if Spaceship ::ConnectAPI ::BundleId . find ( metadata [ :bundle_id ] )
228+ UI . message ( "Bundle ID #{ metadata [ :bundle_id ] } already exists" )
229+ return
230+ end
231+
232+ UI . important ( "Creating macOS Bundle ID #{ metadata [ :bundle_id ] } " )
233+ Spaceship ::ConnectAPI ::BundleId . create (
234+ name : metadata [ :bundle_id_name ] ,
235+ platform : Spaceship ::ConnectAPI ::BundleIdPlatform ::MAC_OS ,
236+ identifier : metadata [ :bundle_id ]
237+ )
238+ end
239+
240+ def macos_flutter_build_environment ( metadata )
241+ {
242+ "FLUTTER_XCODE_PRODUCT_NAME" => metadata [ :app_name ] ,
243+ "FLUTTER_XCODE_PRODUCT_BUNDLE_IDENTIFIER" => metadata [ :bundle_id ]
244+ } . map { |key , value | "#{ key } =#{ shell_escape ( value ) } " } . join ( " " )
245+ end
246+
247+ def plist_value ( path , key )
248+ stdout , stderr , status = Open3 . capture3 ( "/usr/libexec/PlistBuddy" , "-c" , "Print :#{ key } " , path )
249+ UI . user_error! ( "Failed to read #{ key } from #{ path } : #{ stderr } " ) unless status . success?
250+ stdout . strip
251+ end
252+
253+ def verify_built_app_metadata! ( metadata )
254+ info_plist = File . join ( metadata [ :app_path ] , "Contents" , "Info.plist" )
255+ UI . user_error! ( "Missing Info.plist at #{ info_plist } " ) unless File . exist? ( info_plist )
256+
257+ actual_bundle_id = plist_value ( info_plist , "CFBundleIdentifier" )
258+ actual_bundle_name = plist_value ( info_plist , "CFBundleName" )
259+
260+ UI . user_error! ( "Expected bundle id #{ metadata [ :bundle_id ] } , got #{ actual_bundle_id } " ) unless actual_bundle_id == metadata [ :bundle_id ]
261+ UI . user_error! ( "Expected bundle name #{ metadata [ :app_name ] } , got #{ actual_bundle_name } " ) unless actual_bundle_name == metadata [ :app_name ]
262+ end
263+
167264platform :mac do
168265 private_lane :validate_signing_environment do |options |
169266 readonly = options . fetch ( :readonly , match_readonly? )
@@ -179,13 +276,15 @@ platform :mac do
179276 end
180277
181278 private_lane :prepare_signing do |options |
182- metadata = release_metadata
279+ metadata = release_metadata ( flavor : single_macos_release_flavor ( options ) )
183280 keychain = setup_signing_keychain
184281 readonly = options . fetch ( :readonly , match_readonly? )
185282 validate_signing_environment ( readonly : readonly )
186283
187284 Dir . mktmpdir ( "match-signing-assets" ) do |output_dir |
188285 with_app_store_connect_api_key_path ( required : !readonly ) do |api_key_path |
286+ ensure_bundle_id_exists! ( metadata , api_key_path ) unless readonly
287+
189288 match_options = {
190289 type : "developer_id" ,
191290 app_identifier : metadata [ :bundle_id ] ,
@@ -219,8 +318,8 @@ platform :mac do
219318 )
220319 end
221320
222- private_lane :notarize_app_bundle do
223- metadata = release_metadata
321+ private_lane :notarize_app_bundle do | options |
322+ metadata = release_metadata ( flavor : single_macos_release_flavor ( options ) )
224323 UI . user_error! ( "Missing app bundle at #{ metadata [ :app_path ] } " ) unless File . directory? ( metadata [ :app_path ] )
225324
226325 with_app_store_connect_api_key_path do |api_key_path |
@@ -236,8 +335,8 @@ platform :mac do
236335 sh ( "xcrun stapler staple #{ shell_escape ( metadata [ :app_path ] ) } " )
237336 end
238337
239- private_lane :notarize_dmg do
240- metadata = release_metadata
338+ private_lane :notarize_dmg do | options |
339+ metadata = release_metadata ( flavor : single_macos_release_flavor ( options ) )
241340 UI . user_error! ( "Missing disk image at #{ metadata [ :dmg_path ] } " ) unless File . exist? ( metadata [ :dmg_path ] )
242341
243342 with_app_store_connect_api_key_path do |api_key_path |
@@ -253,8 +352,11 @@ platform :mac do
253352 sh ( "xcrun stapler staple #{ shell_escape ( metadata [ :dmg_path ] ) } " )
254353 end
255354
256- private_lane :upload_github_release do
257- metadata = release_metadata
355+ private_lane :upload_github_release do |options |
356+ flavors = options [ :flavors ] || macos_release_flavors
357+ metadata_items = flavors . map { |flavor | release_metadata ( flavor : flavor ) }
358+ metadata = metadata_items . first
359+ assets = metadata_items . flat_map { |item | [ item [ :zip_path ] , item [ :dmg_path ] ] }
258360
259361 set_github_release (
260362 repository_name : ENV . fetch ( "RELEASE_REPOSITORY" ) ,
@@ -265,29 +367,31 @@ platform :mac do
265367 is_draft : release_boolean_env ( "GITHUB_RELEASE_DRAFT" ) ,
266368 is_prerelease : release_boolean_env ( "GITHUB_RELEASE_PRERELEASE" ) ,
267369 is_generate_release_notes : true ,
268- upload_assets : [ metadata [ :zip_path ] , metadata [ :dmg_path ] ]
370+ upload_assets : assets
269371 )
270372 end
271373
272374 desc "Build the macOS release app bundle"
273- lane :build do
274- metadata = release_metadata
375+ lane :build do | options |
376+ metadata = release_metadata ( flavor : single_macos_release_flavor ( options ) )
275377
276378 FileUtils . mkdir_p ( metadata [ :dist_dir ] )
277379
278380 sh ( "cd #{ shell_escape ( workspace_root ) } && fvm flutter pub get" )
279381 Bundler . with_unbundled_env do
280- sh ( "cd #{ shell_escape ( workspace_root ) } && fvm flutter build macos --release --no-pub" )
382+ dart_define = shell_escape ( "ZCASH_DEFAULT_NETWORK=#{ metadata [ :zcash_default_network ] } " )
383+ sh ( "cd #{ shell_escape ( workspace_root ) } && #{ macos_flutter_build_environment ( metadata ) } fvm flutter build macos --release --no-pub --dart-define=#{ dart_define } " )
281384 end
282385
283386 UI . user_error! ( "Expected app bundle at #{ metadata [ :app_path ] } " ) unless File . directory? ( metadata [ :app_path ] )
387+ verify_built_app_metadata! ( metadata )
284388 end
285389
286390 desc "Package the macOS release build into ZIP and DMG"
287391 lane :package do |options |
288- build unless options [ :skip_build ]
392+ build ( flavor : single_macos_release_flavor ( options ) ) unless options [ :skip_build ]
289393
290- metadata = release_metadata
394+ metadata = release_metadata ( flavor : single_macos_release_flavor ( options ) )
291395 FileUtils . mkdir_p ( metadata [ :dist_dir ] )
292396 FileUtils . rm_f ( [ metadata [ :zip_path ] , metadata [ :dmg_path ] ] )
293397
@@ -303,22 +407,27 @@ platform :mac do
303407 "--app-path" , shell_escape ( metadata [ :app_path ] ) ,
304408 "--output" , shell_escape ( metadata [ :dmg_path ] ) ,
305409 "--app-name" , shell_escape ( metadata [ :app_name ] ) ,
306- "--volume-name" , shell_escape ( "Install Vizor Wallet" )
410+ "--volume-name" , shell_escape ( metadata [ :volume_name ] )
307411 ] . join ( " " ) )
308412 end
309413
310414 desc "Install macOS signing identities and provisioning profiles via match"
311415 lane :sync_signing do |options |
312- prepare_signing ( readonly : options . fetch ( :readonly , match_readonly? ) )
416+ prepare_signing ( readonly : options . fetch ( :readonly , match_readonly? ) , flavor : single_macos_release_flavor ( options ) )
313417 end
314418
315419 desc "Build, notarize, package, and upload the macOS release"
316420 lane :release do
317- prepare_signing ( readonly : match_readonly? )
318- build
319- notarize_app_bundle
320- package ( skip_build : true )
321- notarize_dmg
322- upload_github_release
421+ flavors = macos_release_flavors
422+
423+ flavors . each do |flavor |
424+ prepare_signing ( readonly : match_readonly? , flavor : flavor )
425+ build ( flavor : flavor )
426+ notarize_app_bundle ( flavor : flavor )
427+ package ( skip_build : true , flavor : flavor )
428+ notarize_dmg ( flavor : flavor )
429+ end
430+
431+ upload_github_release ( flavors : flavors )
323432 end
324433end
0 commit comments