ann-flutter-flavor 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +1 -0
  3. data/Gemfile +3 -0
  4. data/Gemfile.lock +271 -0
  5. data/Rakefile +6 -0
  6. data/ann-flutter-flavor.gemspec +25 -0
  7. data/lib/ann-flutter-flavor.rb +10 -0
  8. data/lib/ann_flutter_flavor/version.rb +3 -0
  9. data/lib/fastlane/plugin/ann_flutter_flavor/actions/ann_compile_build_action.rb +219 -0
  10. data/lib/fastlane/plugin/ann_flutter_flavor/actions/ann_download_from_app_store_action.rb +183 -0
  11. data/lib/fastlane/plugin/ann_flutter_flavor/actions/ann_download_from_play_store_action.rb +168 -0
  12. data/lib/fastlane/plugin/ann_flutter_flavor/actions/ann_emulators_action.rb +180 -0
  13. data/lib/fastlane/plugin/ann_flutter_flavor/actions/ann_run_tests_action.rb +128 -0
  14. data/lib/fastlane/plugin/ann_flutter_flavor/actions/ann_setup_action.rb +137 -0
  15. data/lib/fastlane/plugin/ann_flutter_flavor/actions/ann_upload_to_app_store_action.rb +280 -0
  16. data/lib/fastlane/plugin/ann_flutter_flavor/actions/ann_upload_to_firebase_action.rb +199 -0
  17. data/lib/fastlane/plugin/ann_flutter_flavor/actions/ann_upload_to_play_store_action.rb +248 -0
  18. data/lib/fastlane/plugin/ann_flutter_flavor/helper/lanes_android.rb +120 -0
  19. data/lib/fastlane/plugin/ann_flutter_flavor/helper/lanes_annai.rb +502 -0
  20. data/lib/fastlane/plugin/ann_flutter_flavor/helper/lanes_ios.rb +157 -0
  21. data/lib/fastlane/plugin/ann_flutter_flavor/helper/lanes_setup.rb +161 -0
  22. data/lib/fastlane/plugin/ann_flutter_flavor/helper/podspec_bridge.rb +153 -0
  23. data/lib/fastlane/plugin/ann_flutter_flavor/helper/test_integration.rb +346 -0
  24. data/lib/fastlane/plugin/ann_flutter_flavor/helper/utils_project_config.rb +96 -0
  25. data/lib/fastlane/plugin/ann_flutter_flavor/helper/utils_spec_loader.rb +363 -0
  26. data/lib/fastlane/plugin/ann_flutter_flavor/helper/utils_status.rb +115 -0
  27. data/lib/fastlane/plugin/ann_flutter_flavor.rb +23 -0
  28. data/plugin_manager.sh +140 -0
  29. metadata +112 -0
@@ -0,0 +1,137 @@
1
+ require 'fastlane/action'
2
+
3
+ require 'fastlane/plugin/ann_flutter_flavor/helper/utils_status'
4
+ require 'fastlane/plugin/ann_flutter_flavor/helper/utils_project_config'
5
+ require 'fastlane/plugin/ann_flutter_flavor/helper/lanes_setup'
6
+
7
+
8
+ module Fastlane
9
+ module Actions
10
+ # =========================================================================
11
+ # AnnaiUpgradeSetupAction: Runs comprehensive setup and maintenance
12
+ # =========================================================================
13
+
14
+ class AnnaiUpgradeSetupAction < Action
15
+ def self.run(params)
16
+
17
+ # 1. Determine the necessary paths and managers
18
+ root_folder = FastlaneFlutterFlavor::ProjectUtil.find_flutter_root
19
+ status_manager = FastlaneFlutterFlavor::StatusManager.new(lane: self)
20
+
21
+ # 2. Instantiate SetupLanes directly
22
+ setup_lanes = FastlaneFlutterFlavor::SetupLanes.new(
23
+ root_folder: root_folder,
24
+ status_manager: status_manager,
25
+ )
26
+
27
+ Fastlane::UI.message("Starting comprehensive setup and maintenance (Force: #{params[:force]})...")
28
+
29
+ # 3. Execute the maintenance routine
30
+ setup_lanes.upgrade_setup(force: params[:force])
31
+
32
+ Fastlane::UI.success("✅ Upgrade setup completed successfully.")
33
+
34
+ # 4. Finalize status reporting
35
+ status_manager.displayStatus
36
+ rescue => e
37
+ # Log the error using the StatusManager instance
38
+ if status_manager
39
+ # Pass 'self' (the class) as the lane context for consistency
40
+ status_manager.logError(self, 'annai_upgrade_setup', nil, e)
41
+ status_manager.displayStatus
42
+ end
43
+
44
+ raise "AnnaiUpgradeSetupAction failed: #{e.message}"
45
+ end
46
+
47
+ def self.available_options
48
+ [
49
+ FastlaneCore::ConfigItem.new(key: :force,
50
+ description: "Force the execution even if conditions might advise skipping",
51
+ optional: true,
52
+ is_string: false,
53
+ default_value: false,
54
+ type: Boolean)
55
+ ]
56
+ end
57
+
58
+ def self.description
59
+ "Runs comprehensive Flutter and Fastlane maintenance (upgrade, doctor, bundle update, gem install)."
60
+ end
61
+
62
+ def self.is_supported?(platform)
63
+ true
64
+ end
65
+
66
+ def self.example_code
67
+ [
68
+ 'annai_upgrade_setup',
69
+ 'annai_upgrade_setup(force: true)'
70
+ ]
71
+ end
72
+ end
73
+
74
+ # =========================================================================
75
+ # AnnaiCleanupAction: Cleans up all local build artifacts
76
+ # =========================================================================
77
+
78
+ class AnnaiCleanupAction < Action
79
+ def self.run(params)
80
+
81
+ # 1. Determine the necessary paths and managers
82
+ root_folder = FastlaneFlutterFlavor::ProjectUtil.find_flutter_root
83
+ status_manager = FastlaneFlutterFlavor::StatusManager.new(lane: self)
84
+
85
+ # 2. Instantiate SetupLanes directly
86
+ setup_lanes = FastlaneFlutterFlavor::SetupLanes.new(
87
+ root_folder: root_folder,
88
+ status_manager: status_manager,
89
+ )
90
+
91
+ Fastlane::UI.message("Starting full build artifact cleanup (Force: #{params[:force]})...")
92
+
93
+ # 3. Execute the cleanup routine
94
+ setup_lanes.cleanup(force: params[:force])
95
+
96
+ Fastlane::UI.success("✅ Build artifact cleanup completed successfully.")
97
+
98
+ # 4. Finalize status reporting
99
+ status_manager.displayStatus
100
+ rescue => e
101
+ # Log the error using the StatusManager instance
102
+ if status_manager
103
+ # Pass 'self' (the class) as the lane context for consistency
104
+ status_manager.logError(self, 'annai_cleanup', nil, e)
105
+ status_manager.displayStatus
106
+ end
107
+ raise "AnnaiCleanupAction failed: #{e.message}"
108
+ end
109
+
110
+ def self.available_options
111
+ [
112
+ FastlaneCore::ConfigItem.new(key: :force,
113
+ description: "Force the execution even if conditions might advise skipping",
114
+ optional: true,
115
+ is_string: false,
116
+ default_value: false,
117
+ type: Boolean)
118
+ ]
119
+ end
120
+
121
+ def self.description
122
+ "Performs full build artifact cleanup (runs 'flutter clean' and deletes platform build folders)."
123
+ end
124
+
125
+ def self.is_supported?(platform)
126
+ true
127
+ end
128
+
129
+ def self.example_code
130
+ [
131
+ 'annai_cleanup',
132
+ 'annai_cleanup(force: true)',
133
+ ]
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,280 @@
1
+ require 'fastlane/action'
2
+ require 'fastlane/plugin/ann_flutter_flavor/helper/lanes_annai'
3
+ require 'fastlane/plugin/ann_flutter_flavor/helper/utils_spec_loader'
4
+
5
+ module Fastlane
6
+ module Actions
7
+ # This class name dictates the action name 'ann_upload_to_app_store'
8
+ class AnnaiUploadToAppStoreAction < Action
9
+
10
+ def self.run(params)
11
+ platform = :ios # Hardcoded platform
12
+ action_name = "ann_upload_to_app_store"
13
+ requested_flavor = params[:flavor]
14
+ requested_clean_build = params[:clean]
15
+ spec_file = params[:spec_file]
16
+
17
+ # 1. Initialize AnnaiLanes
18
+ annai_lanes = FastlaneFlutterFlavor::AnnaiLanes.new(
19
+ lane: self,
20
+ platform: platform, # Pass platform symbol directly
21
+ spec_file: spec_file # Pass optional spec_file
22
+ )
23
+
24
+ status_manager = annai_lanes.instance_variable_get(:@statusManager)
25
+ spec_loader = annai_lanes.instance_variable_get(:@specLoader)
26
+ UI.user_error!("Internal Error: AnnaiLanes failed to initialize specLoader") unless spec_loader
27
+
28
+ any_upload_failed = false
29
+
30
+ # 2. Perform clean build if requested
31
+ if requested_clean_build
32
+ UI.header("🧹 Cleaning builds as requested...")
33
+ begin
34
+ annai_lanes.clean_build
35
+ rescue => e
36
+ UI.error("Failed to perform clean build: #{e.message}. Attempting to proceed with upload")
37
+ # Note: Using 'annai_clean_build' as the action name for the failure log
38
+ status_manager.logError("All", "annai_clean_build", platform, "Clean build failed: #{e.message}")
39
+ end
40
+ end
41
+
42
+ # 3. Get the list of all flavors defined for the platform
43
+ flavors_hash = spec_loader.get_platform_flavors(platform)
44
+
45
+ if flavors_hash.empty?
46
+ UI.important("No flavors found for iOS in the Annai spec. Nothing to upload")
47
+ annai_lanes.finalize
48
+ return
49
+ end
50
+
51
+ all_flavor_names = flavors_hash.keys
52
+
53
+ # 4. Determine which flavors to upload
54
+ if requested_flavor && !requested_flavor.to_s.empty?
55
+ requested_flavors = requested_flavor.to_s.split(',').map(&:strip).reject(&:empty?)
56
+
57
+ # Validate all requested flavors
58
+ invalid_flavors = requested_flavors.reject { |f| all_flavor_names.include?(f) }
59
+
60
+ if invalid_flavors.any?
61
+ UI.user_error!("The following requested flavor(s) are not found in the Annai spec for IOS: #{invalid_flavors.join(', ')}. Available flavors: #{all_flavor_names.join(', ')}")
62
+ end
63
+
64
+ flavors_to_upload = requested_flavors
65
+ else
66
+ flavors_to_upload = all_flavor_names
67
+ end
68
+
69
+ UI.header("Starting App Store upload. Flavors to process: #{flavors_to_upload.join(', ')}")
70
+
71
+ # 5. Loop through each flavor and upload
72
+ flavors_to_upload.each do |flavor_key|
73
+
74
+ flavor = flavor_key.to_s
75
+
76
+ # Get values, prioritizing spec over parameters
77
+ final_main_file = spec_loader.get_main_file(platform, flavor) || params[:main_file]
78
+ final_api_key_path = spec_loader.get_api_key_path(platform, flavor) || params[:api_key_path]
79
+ final_package_name = spec_loader.get_package_id(platform, flavor) || params[:bundle_identifier]
80
+ final_export_option_file = spec_loader.get_export_option_file(platform, flavor) || params[:export_option_file]
81
+ final_display_name = spec_loader.get_display_name(platform, flavor) || params[:display_name]
82
+ final_team_id = spec_loader.get_team_id(platform, flavor) || params[:team_id]
83
+ final_app_version = spec_loader.get_version_name(platform, flavor) || params[:app_version]
84
+ final_build_number = spec_loader.get_version_code(platform, flavor) || params[:build_number]
85
+
86
+ UI.message("🚀 Preparing **#{flavor}** flavor...")
87
+
88
+ # Use 'upload_to_store' as the consistent action name for flavor-specific failures
89
+ flavor_action_name = "upload_to_store"
90
+
91
+ begin
92
+ # iOS Upload Logic (upload_to_app_store)
93
+ success = annai_lanes.upload_to_app_store(
94
+ bundle_identifier: final_package_name, # Use package name from spec for bundle ID
95
+ api_key_path: final_api_key_path,
96
+ flavor: flavor,
97
+ main_file: final_main_file,
98
+ display_name: final_display_name,
99
+ export_option_file: final_export_option_file,
100
+ team_id: final_team_id,
101
+ signing_certificate: params[:signing_certificate],
102
+ app_version: final_app_version,
103
+ build_number: final_build_number,
104
+ skip_sound_null_safety: params[:skip_sound_null_safety],
105
+ skip_compile: params[:skip_compile],
106
+ skip_upload_binary: params[:skip_upload_binary],
107
+ skip_upload_prod: params[:skip_upload_prod],
108
+ skip_upload_metadata: params[:skip_upload_metadata],
109
+ skip_upload_screenshots: params[:skip_upload_screenshots],
110
+ export_compliance_uses_encryption: params[:export_compliance_uses_encryption],
111
+ add_id_info_uses_idfa: params[:add_id_info_uses_idfa],
112
+ platform: platform.to_s,
113
+ metadata_path: params[:metadata_path],
114
+ screenshots_path: params[:screenshots_path],
115
+ distribution_platform: params[:distribution_platform],
116
+ )
117
+
118
+ if success
119
+ UI.success("✅ Successfully uploaded flavor: #{flavor}")
120
+ else
121
+ # If upload_to_app_store returns false (meaning it caught an error and logged it), flag the overall failure.
122
+ # We only raise/log here if the inner action didn't handle it, to ensure logging consistency.
123
+ if !status_manager.instance_variable_get(:@status).key?([flavor, flavor_action_name, platform.to_s])
124
+ raise "Upload failed for flavor '#{flavor}'"
125
+ else
126
+ raise "Upload failed for flavor '#{flavor}' (error logged previously)"
127
+ end
128
+ end
129
+
130
+ rescue => e
131
+ # Failure handling: Log the error and continue
132
+ if !status_manager.instance_variable_get(:@status).key?([flavor, flavor_action_name, platform.to_s])
133
+ status_manager.logError(flavor, flavor_action_name, platform, e.message)
134
+ end
135
+
136
+ UI.error("❌ Failed to upload flavor: #{flavor}. Continuing to next flavor...")
137
+ any_upload_failed = true
138
+ # Continue to the next flavor
139
+ end
140
+ end
141
+
142
+ # 6. Final Reporting and Status Check
143
+ annai_lanes.finalize
144
+
145
+ if any_upload_failed
146
+ UI.user_error!("Upload failed for one or more flavors. See the Annai Fastlane summary above")
147
+ else
148
+ UI.success("🎉 Successfully uploaded all #{flavors_to_upload.count} requested flavor(s) to the App Store!")
149
+ end
150
+ end
151
+
152
+ # ----------------------------------------------------
153
+ # Define Parameters
154
+ # ----------------------------------------------------
155
+ def self.available_options
156
+ [
157
+ FastlaneCore::ConfigItem.new(key: :flavor,
158
+ description: "The specific flavor(s) to upload (comma-separated). If nil, all flavors are uploaded",
159
+ optional: true,
160
+ default_value: nil),
161
+
162
+ FastlaneCore::ConfigItem.new(key: :spec_file,
163
+ description: "Path to the annai spec configuration file (relative to flutter root). Defaults to standard discovery",
164
+ optional: true,
165
+ default_value: nil),
166
+
167
+ FastlaneCore::ConfigItem.new(key: :clean,
168
+ description: "If true, runs annai_clean_build before uploading (and compiling if not skipped)",
169
+ type: Boolean,
170
+ default_value: false),
171
+
172
+ FastlaneCore::ConfigItem.new(key: :distribution_platform,
173
+ description: "The distribution platform (e.g., 'ios' or 'app_store_connect') for App Store uploads",
174
+ optional: true,
175
+ default_value: "ios"),
176
+
177
+ # --- Common & iOS Specific Parameters ---
178
+ FastlaneCore::ConfigItem.new(key: :api_key_path,
179
+ description: "Path to the App Store Connect API Key JSON file (iOS)",
180
+ optional: true,
181
+ type: String),
182
+ FastlaneCore::ConfigItem.new(key: :main_file,
183
+ description: "Path to the main dart file (used if compilation is required)",
184
+ default_value: "lib/main.dart"),
185
+ FastlaneCore::ConfigItem.new(key: :skip_compile,
186
+ description: "If true, skips the compilation step and assumes artifacts already exist",
187
+ type: Boolean,
188
+ default_value: false),
189
+ FastlaneCore::ConfigItem.new(key: :skip_sound_null_safety,
190
+ description: "Skips sound null safety checks during compilation (if skip_compile is false)",
191
+ type: Boolean,
192
+ default_value: false),
193
+ FastlaneCore::ConfigItem.new(key: :skip_upload_prod,
194
+ description: "If true, skips uploading to the production track/stage",
195
+ type: Boolean,
196
+ default_value: false),
197
+ FastlaneCore::ConfigItem.new(key: :skip_upload_binary,
198
+ description: "If true, skips uploading the binary (.ipa)",
199
+ type: Boolean,
200
+ default_value: false),
201
+ FastlaneCore::ConfigItem.new(key: :skip_upload_metadata,
202
+ description: "If true, skips uploading store metadata (description, title, etc.)",
203
+ type: Boolean,
204
+ default_value: false),
205
+ FastlaneCore::ConfigItem.new(key: :skip_upload_screenshots,
206
+ description: "If true, skips uploading screenshots and app previews",
207
+ type: Boolean,
208
+ default_value: false),
209
+ FastlaneCore::ConfigItem.new(key: :metadata_path,
210
+ description: "The path to the localized store metadata (e.g., fastlane/metadata/ios)",
211
+ optional: true,
212
+ default_value: nil),
213
+ FastlaneCore::ConfigItem.new(key: :screenshots_path,
214
+ description: "The path to the screenshots and app previews",
215
+ optional: true,
216
+ default_value: nil),
217
+
218
+ FastlaneCore::ConfigItem.new(key: :bundle_identifier,
219
+ description: "The bundle identifier (iOS)",
220
+ optional: true,
221
+ default_value: nil),
222
+ FastlaneCore::ConfigItem.new(key: :display_name,
223
+ description: "The display name of the app (used for IPA filename)",
224
+ optional: true,
225
+ default_value: "App"),
226
+ FastlaneCore::ConfigItem.new(key: :export_option_file,
227
+ description: "Path to export options plist",
228
+ optional: true,
229
+ default_value: ""),
230
+ FastlaneCore::ConfigItem.new(key: :team_id,
231
+ description: "Apple Developer Team ID",
232
+ optional: true,
233
+ default_value: nil),
234
+ FastlaneCore::ConfigItem.new(key: :signing_certificate,
235
+ description: "Code signing certificate name",
236
+ optional: true,
237
+ default_value: "iPhone Distribution"),
238
+ FastlaneCore::ConfigItem.new(key: :app_version,
239
+ description: "The app version number",
240
+ optional: true,
241
+ default_value: nil),
242
+ FastlaneCore::ConfigItem.new(key: :build_number,
243
+ description: "The build number",
244
+ optional: true,
245
+ default_value: nil),
246
+ FastlaneCore::ConfigItem.new(key: :export_compliance_uses_encryption,
247
+ description: "Whether the app uses encryption",
248
+ optional: true,
249
+ type: Boolean,
250
+ default_value: nil),
251
+ FastlaneCore::ConfigItem.new(key: :add_id_info_uses_idfa,
252
+ description: "Whether the app uses IDFA",
253
+ optional: true,
254
+ type: Boolean,
255
+ default_value: nil),
256
+ ].compact
257
+ end
258
+
259
+ def self.description
260
+ "Handles compilation (optional) and uploading of one or all flavors to the Apple App Store"
261
+ end
262
+
263
+ def self.is_supported?(platform)
264
+ platform == :ios
265
+ end
266
+
267
+ def self.example_code
268
+ 'ann_upload_to_app_store(
269
+ flavor: "production",
270
+ spec_file: "config/annai_config.yaml", # Optional custom spec path
271
+ api_key_path: "./api_keys/asc.json",
272
+ bundle_identifier: "com.example.app",
273
+ team_id: "ABCDEFG",
274
+ clean: true,
275
+ skip_compile: false
276
+ )'
277
+ end
278
+ end
279
+ end
280
+ end
@@ -0,0 +1,199 @@
1
+ require 'fastlane/action'
2
+ require 'fastlane/plugin/ann_flutter_flavor/helper/lanes_annai'
3
+ require 'fastlane/plugin/ann_flutter_flavor/helper/utils_spec_loader'
4
+
5
+ module Fastlane
6
+ module Actions
7
+ # This class name dictates the action name 'ann_upload_to_firebase'
8
+ class AnnaiUploadToFirebaseAction < Action
9
+
10
+ def self.run(params)
11
+ platform = :web # Hardcoded platform for web hosting
12
+ action_name = "ann_upload_to_firebase"
13
+ requested_flavor = params[:flavor]
14
+ requested_clean_build = params[:clean]
15
+ spec_file = params[:spec_file]
16
+
17
+ # 1. Initialize AnnaiLanes
18
+ annai_lanes = FastlaneFlutterFlavor::AnnaiLanes.new(
19
+ lane: self,
20
+ platform: platform, # Pass platform symbol directly
21
+ spec_file: spec_file # Pass optional spec_file
22
+ )
23
+
24
+ status_manager = annai_lanes.instance_variable_get(:@statusManager)
25
+ spec_loader = annai_lanes.instance_variable_get(:@specLoader)
26
+ UI.user_error!("Internal Error: AnnaiLanes failed to initialize specLoader") unless spec_loader
27
+
28
+ any_upload_failed = false
29
+
30
+ # 2. Perform clean build if requested
31
+ if requested_clean_build
32
+ UI.header("🧹 Cleaning builds as requested...")
33
+ begin
34
+ annai_lanes.clean_build
35
+ rescue => e
36
+ UI.error("Failed to perform clean build: #{e.message}. Attempting to proceed with upload")
37
+ # Note: Using 'annai_clean_build' as the action name for the failure log
38
+ status_manager.logError("All", "annai_clean_build", platform, "Clean build failed: #{e.message}")
39
+ end
40
+ end
41
+
42
+ # 3. Get the list of all flavors defined for the platform
43
+ flavors_hash = spec_loader.get_platform_flavors(platform)
44
+
45
+ if flavors_hash.empty?
46
+ UI.important("No flavors found for Web in the Annai spec. Nothing to upload")
47
+ annai_lanes.finalize
48
+ return
49
+ end
50
+
51
+ all_flavor_names = flavors_hash.keys
52
+
53
+ # 4. Determine which flavors to upload
54
+ if requested_flavor && !requested_flavor.to_s.empty?
55
+ requested_flavors = requested_flavor.to_s.split(',').map(&:strip).reject(&:empty?)
56
+
57
+ # Validate all requested flavors
58
+ invalid_flavors = requested_flavors.reject { |f| all_flavor_names.include?(f) }
59
+
60
+ if invalid_flavors.any?
61
+ UI.user_error!("The following requested flavor(s) are not found in the Annai spec for Web: #{invalid_flavors.join(', ')}. Available flavors: #{all_flavor_names.join(', ')}")
62
+ end
63
+
64
+ flavors_to_upload = requested_flavors
65
+ else
66
+ flavors_to_upload = all_flavor_names
67
+ end
68
+
69
+ UI.header("Starting Firebase Hosting upload. Flavors to process: #{flavors_to_upload.join(', ')}")
70
+
71
+
72
+ # 5. Loop through each flavor and upload
73
+ flavors_to_upload.each do |flavor_key|
74
+
75
+ flavor = flavor_key.to_s
76
+
77
+ # Get values from spec, falling back to parameters
78
+ # Note: firebase_project is intentionally NOT loaded from spec here,
79
+ # as it's passed directly to AnnaiLanes which handles the prioritization
80
+ # (CLI param > Spec file).
81
+ final_main_file = spec_loader.get_main_file(platform, flavor) || params[:main_file]
82
+
83
+ UI.message("🚀 Preparing **#{flavor}** flavor...")
84
+
85
+ # Use 'upload_to_firebase' as the consistent action name for flavor-specific failures
86
+ flavor_action_name = "upload_to_firebase"
87
+
88
+ begin
89
+ # Web Upload Logic (upload_to_firebase)
90
+ success = annai_lanes.upload_to_firebase(
91
+ flavor: flavor,
92
+ main_file: final_main_file,
93
+ build_config: params[:build_config],
94
+ skip_compile: params[:skip_compile],
95
+ skip_sound_null_safety: params[:skip_sound_null_safety],
96
+ firebase_project: params[:firebase_project],
97
+ firebase_token: params[:firebase_token] || ENV["FIREBASE_TOKEN"],
98
+ )
99
+
100
+ if success
101
+ UI.success("✅ Successfully deployed flavor: #{flavor}")
102
+ else
103
+ # If upload_to_firebase returns false, flag the overall failure.
104
+ if !status_manager.instance_variable_get(:@status).key?([flavor, flavor_action_name, platform.to_s])
105
+ raise "Deployment failed for flavor '#{flavor}'"
106
+ else
107
+ raise "Deployment failed for flavor '#{flavor}' (error logged previously)"
108
+ end
109
+ end
110
+
111
+ rescue => e
112
+ # Failure handling: Log the error and continue
113
+ if !status_manager.instance_variable_get(:@status).key?([flavor, flavor_action_name, platform.to_s])
114
+ status_manager.logError(flavor, flavor_action_name, platform, e.message)
115
+ end
116
+
117
+ UI.error("❌ Failed to deploy flavor: #{flavor}. Continuing to next flavor...")
118
+ any_upload_failed = true
119
+ # Continue to the next flavor
120
+ end
121
+ end
122
+
123
+ # 6. Final Reporting and Status Check
124
+ annai_lanes.finalize
125
+
126
+ if any_upload_failed
127
+ UI.user_error!("Deployment failed for one or more flavors. See the Annai Fastlane summary above")
128
+ else
129
+ UI.success("🎉 Successfully deployed all #{flavors_to_upload.count} requested flavor(s) to Firebase Hosting!")
130
+ end
131
+ end
132
+
133
+ # ----------------------------------------------------
134
+ # Define Parameters
135
+ # ----------------------------------------------------
136
+ def self.available_options
137
+ [
138
+ FastlaneCore::ConfigItem.new(key: :flavor,
139
+ description: "The specific flavor(s) to deploy (comma-separated). If nil, all Web flavors are deployed",
140
+ optional: true,
141
+ default_value: nil),
142
+
143
+ FastlaneCore::ConfigItem.new(key: :spec_file,
144
+ description: "Path to the annai spec configuration file (relative to flutter root). Defaults to standard discovery",
145
+ optional: true,
146
+ default_value: nil),
147
+
148
+ FastlaneCore::ConfigItem.new(key: :clean,
149
+ description: "If true, runs annai_clean_build before deploying (and compiling if not skipped)",
150
+ type: Boolean,
151
+ default_value: false),
152
+
153
+ # --- Web / Firebase Specific Parameters ---
154
+ FastlaneCore::ConfigItem.new(key: :firebase_token,
155
+ description: "Firebase CLI CI token for authentication (generated via 'firebase login:ci')",
156
+ optional: true,
157
+ env_name: "FIREBASE_TOKEN",
158
+ type: String),
159
+ FastlaneCore::ConfigItem.new(key: :firebase_project,
160
+ description: "The Firebase Project ID to deploy to. This value overrides the 'project_id' in the annai spec file",
161
+ optional: true,
162
+ type: String),
163
+ FastlaneCore::ConfigItem.new(key: :main_file,
164
+ description: "Path to the main dart file (used if compilation is required)",
165
+ default_value: "lib/main.dart"),
166
+ FastlaneCore::ConfigItem.new(key: :build_config,
167
+ description: "The Flutter build configuration ('release', 'profile', or 'debug')",
168
+ optional: true,
169
+ default_value: "release"),
170
+ FastlaneCore::ConfigItem.new(key: :skip_compile,
171
+ description: "If true, skips the compilation step ('flutter build web') and assumes artifacts already exist",
172
+ type: Boolean,
173
+ default_value: false),
174
+ FastlaneCore::ConfigItem.new(key: :skip_sound_null_safety,
175
+ description: "Skips sound null safety checks during compilation (if skip_compile is false)",
176
+ type: Boolean,
177
+ default_value: false),
178
+ ].compact
179
+ end
180
+
181
+ def self.description
182
+ "Handles compilation (optional) and deployment of one or all Web flavors to Firebase Hosting"
183
+ end
184
+
185
+ def self.is_supported?(platform)
186
+ platform == :web
187
+ end
188
+
189
+ def self.example_code
190
+ 'ann_upload_to_firebase(
191
+ flavor: "staging,production",
192
+ firebase_project: "my-custom-staging-project", # Overrides spec file
193
+ build_config: "release",
194
+ skip_compile: false
195
+ )'
196
+ end
197
+ end
198
+ end
199
+ end