ann-flutter-flavor 0.1.6

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 (30) 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/README.md +283 -0
  6. data/Rakefile +6 -0
  7. data/ann-flutter-flavor.gemspec +25 -0
  8. data/lib/ann-flutter-flavor.rb +10 -0
  9. data/lib/ann_flutter_flavor/version.rb +3 -0
  10. data/lib/fastlane/plugin/ann_flutter_flavor/actions/ann_compile_build_action.rb +219 -0
  11. data/lib/fastlane/plugin/ann_flutter_flavor/actions/ann_download_from_app_store_action.rb +183 -0
  12. data/lib/fastlane/plugin/ann_flutter_flavor/actions/ann_download_from_play_store_action.rb +168 -0
  13. data/lib/fastlane/plugin/ann_flutter_flavor/actions/ann_emulators_action.rb +180 -0
  14. data/lib/fastlane/plugin/ann_flutter_flavor/actions/ann_run_tests_action.rb +128 -0
  15. data/lib/fastlane/plugin/ann_flutter_flavor/actions/ann_setup_action.rb +137 -0
  16. data/lib/fastlane/plugin/ann_flutter_flavor/actions/ann_upload_to_app_store_action.rb +280 -0
  17. data/lib/fastlane/plugin/ann_flutter_flavor/actions/ann_upload_to_firebase_action.rb +199 -0
  18. data/lib/fastlane/plugin/ann_flutter_flavor/actions/ann_upload_to_play_store_action.rb +248 -0
  19. data/lib/fastlane/plugin/ann_flutter_flavor/helper/lanes_android.rb +120 -0
  20. data/lib/fastlane/plugin/ann_flutter_flavor/helper/lanes_annai.rb +502 -0
  21. data/lib/fastlane/plugin/ann_flutter_flavor/helper/lanes_ios.rb +157 -0
  22. data/lib/fastlane/plugin/ann_flutter_flavor/helper/lanes_setup.rb +161 -0
  23. data/lib/fastlane/plugin/ann_flutter_flavor/helper/podspec_bridge.rb +153 -0
  24. data/lib/fastlane/plugin/ann_flutter_flavor/helper/test_integration.rb +346 -0
  25. data/lib/fastlane/plugin/ann_flutter_flavor/helper/utils_project_config.rb +102 -0
  26. data/lib/fastlane/plugin/ann_flutter_flavor/helper/utils_spec_loader.rb +363 -0
  27. data/lib/fastlane/plugin/ann_flutter_flavor/helper/utils_status.rb +115 -0
  28. data/lib/fastlane/plugin/ann_flutter_flavor.rb +23 -0
  29. data/plugin_manager.sh +140 -0
  30. metadata +113 -0
@@ -0,0 +1,219 @@
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
+ class AnnaiCompileBuildAction < Action
8
+
9
+ def self.run(params)
10
+ platform = params[:platform]
11
+ requested_flavor = params[:flavor]
12
+ requested_clean_build = params[:clean]
13
+ spec_file = params[:spec_file]
14
+
15
+ # 1. Initialize AnnaiLanes
16
+ annai_lanes = FastlaneFlutterFlavor::AnnaiLanes.new(
17
+ lane: self,
18
+ platform: platform, # Pass platform symbol directly
19
+ spec_file: spec_file # Pass optional spec_file
20
+ )
21
+
22
+ # Get the StatusManager instance for logging errors explicitly
23
+ status_manager = annai_lanes.instance_variable_get(:@statusManager)
24
+
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
+ # Flag to track if any flavor compilation failed
29
+ any_build_failed = false
30
+
31
+ # 2. Perform clean build if requested
32
+ if requested_clean_build
33
+ UI.header("๐Ÿงน Cleaning builds as requested...")
34
+ begin
35
+ annai_lanes.clean_build
36
+ rescue => e
37
+ UI.error("Failed to perform clean build: #{e.message}. Attempting to proceed with compilation.")
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 platform '#{platform}' in the Annai spec. Nothing to build.")
47
+ annai_lanes.finalize
48
+ return
49
+ end
50
+
51
+ all_flavor_names = flavors_hash.keys
52
+
53
+ # 4. Determine which flavors to build
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 platform '#{platform}': #{invalid_flavors.join(', ')}. Available flavors: #{all_flavor_names.join(', ')}")
62
+ end
63
+
64
+ flavors_to_build = requested_flavors
65
+
66
+ else
67
+ # No specific flavor requested, build all of them
68
+ flavors_to_build = all_flavor_names
69
+ end
70
+
71
+ UI.header("Starting build for #{platform}. Flavors to compile: #{flavors_to_build.join(', ')}")
72
+
73
+
74
+ # 5. Loop through each flavor and directly call compile_build on AnnaiLanes
75
+ flavors_to_build.each do |flavor_key|
76
+
77
+ flavor = flavor_key.to_s
78
+
79
+ # Use the new getter, which uses the internal spec data
80
+ flavor_main_file = spec_loader.get_main_file(platform, flavor)
81
+
82
+ # Use the main_file provided by the user as a fallback if the spec doesn't define one
83
+ final_main_file = flavor_main_file || params[:main_file]
84
+
85
+ if final_main_file.nil? || final_main_file.empty?
86
+ error_msg = "Could not determine 'main_file' for flavor '#{flavor}' on platform '#{platform}'. Check your Annai spec or provide a default 'main_file'."
87
+ UI.error("โŒ #{error_msg}")
88
+ status_manager.logError(flavor, "ann_compile_build_all", platform, error_msg)
89
+ any_build_failed = true
90
+ next
91
+ end
92
+
93
+ UI.message("โš™๏ธ Compiling **#{flavor}** flavor using main file: #{final_main_file}...")
94
+
95
+ begin
96
+ # CRITICAL: Direct call to the AnnaiLanes helper method
97
+ success = annai_lanes.compile_build(
98
+ flavor: flavor,
99
+ sub_command: params[:sub_command],
100
+ build_config: params[:build_config],
101
+ main_file: final_main_file,
102
+ skip_sound_null_safety: params[:skip_sound_null_safety],
103
+ skip_code_sign: params[:skip_code_sign],
104
+ export_options_plist: params[:export_options_plist],
105
+ additional_parameter: params[:additional_parameter],
106
+ platform: platform.to_s # Ensure platform is passed as a string/symbol as expected by the helper
107
+ )
108
+
109
+ if success
110
+ UI.success("๐ŸŽ‰ Successfully compiled flavor: #{flavor}")
111
+ else
112
+ # If compile_build returns false (meaning it caught an error and logged it), flag the overall failure.
113
+ raise "Compilation failed for flavor '#{flavor}' (error logged previously)."
114
+ end
115
+ rescue => e
116
+ # Failure handling: Log the error using StatusManager (if not already logged by compile_build) and continue
117
+ if !status_manager.instance_variable_get(:@status).key?([flavor, "compile_build", platform.to_s])
118
+ status_manager.logError(flavor, "ann_compile_build_all", platform, e.message)
119
+ end
120
+
121
+ UI.error("โŒ Failed to compile flavor: #{flavor}. Continuing to next flavor...")
122
+ any_build_failed = true
123
+ # Continue to the next flavor
124
+ end
125
+ end
126
+
127
+ # 6. Final Reporting and Status Check
128
+ # Finalize calls statusManager.displayStatus to show the comprehensive report
129
+ annai_lanes.finalize
130
+
131
+ if any_build_failed
132
+ # If any build failed, we stop the lane with an error after the summary is displayed.
133
+ UI.user_error!("Compilation failed for one or more flavors. See the Annai Fastlane summary above.")
134
+ else
135
+ UI.success("โœ… Successfully compiled all #{flavors_to_build.count} requested flavor(s) for #{platform}!")
136
+ end
137
+ end
138
+
139
+ # ----------------------------------------------------
140
+ # Define Parameters
141
+ # ----------------------------------------------------
142
+ def self.available_options
143
+ [
144
+ FastlaneCore::ConfigItem.new(key: :platform,
145
+ description: "The platform (:ios, :android, or :web)",
146
+ is_string: false,
147
+ verify_block: proc do |value|
148
+ UI.user_error!("Platform must be :ios, :android, or :web") unless [:ios, :android, :web].include?(value)
149
+ end),
150
+
151
+ FastlaneCore::ConfigItem.new(key: :spec_file,
152
+ description: "Path to the annai spec configuration file (relative to flutter root). Defaults to standard discovery",
153
+ optional: true,
154
+ default_value: nil),
155
+
156
+ FastlaneCore::ConfigItem.new(key: :clean,
157
+ description: "If true, runs annai_clean_build before compiling all flavors",
158
+ type: Boolean,
159
+ default_value: false),
160
+
161
+ FastlaneCore::ConfigItem.new(key: :flavor,
162
+ description: "The specific flavor(s) to build (comma-separated). If nil, all flavors are built",
163
+ optional: true,
164
+ default_value: nil),
165
+
166
+ # All following options are passed directly to the single build action
167
+ FastlaneCore::ConfigItem.new(key: :sub_command,
168
+ description: "Flutter build subcommand (e.g., appbundle, ipa, web) to use for all flavors",
169
+ # Note: If platform is :web, this should typically be 'web' or similar.
170
+ default_value: "appbundle"),
171
+ FastlaneCore::ConfigItem.new(key: :build_config,
172
+ description: "Build configuration (e.g., debug, profile, release) to use for all flavors",
173
+ default_value: "release"),
174
+ FastlaneCore::ConfigItem.new(key: :main_file,
175
+ description: "Path to the main dart file (used as default for all flavors unless overridden in the Annai spec)",
176
+ default_value: "lib/main.dart"),
177
+ FastlaneCore::ConfigItem.new(key: :skip_sound_null_safety,
178
+ type: Boolean,
179
+ default_value: false),
180
+ FastlaneCore::ConfigItem.new(key: :skip_code_sign,
181
+ type: Boolean,
182
+ default_value: false),
183
+ FastlaneCore::ConfigItem.new(key: :export_options_plist,
184
+ description: "Path to export options plist (iOS only)",
185
+ optional: true,
186
+ default_value: ""),
187
+ FastlaneCore::ConfigItem.new(key: :additional_parameter,
188
+ description: "Additional flutter build parameters",
189
+ optional: true,
190
+ default_value: ""),
191
+ ]
192
+ end
193
+
194
+ def self.description
195
+ "Compiles all defined Flutter flavors for a single platform (:ios, :android, or :web) using the Annai spec configuration"
196
+ end
197
+
198
+ def self.is_supported?(platform)
199
+ [:ios, :android, :web].include?(platform)
200
+ end
201
+
202
+ def self.example_code
203
+ 'ann_compile_build_all(
204
+ platform: :android,
205
+ spec_file: "config/my_annai.yaml", # Optional custom spec path
206
+ build_config: "release",
207
+ clean: true,
208
+ flavor: "staging,production" # Optional comma-separated list
209
+ )
210
+ ann_compile_build_all(
211
+ platform: :web,
212
+ sub_command: "web", # Specify the web subcommand
213
+ build_config: "release",
214
+ flavor: "test_web"
215
+ )'
216
+ end
217
+ end
218
+ end
219
+ end
@@ -0,0 +1,183 @@
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_download_from_app_store'
8
+ class AnnaiDownloadFromAppStoreAction < Action
9
+
10
+ def self.run(params)
11
+ # Platform is hardcoded to :ios
12
+ platform = :ios
13
+ action_name = "ann_download_from_app_store"
14
+ requested_flavor = params[:flavor]
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
+ # Get the StatusManager and SpecLoader instances
25
+ status_manager = annai_lanes.instance_variable_get(:@statusManager)
26
+ spec_loader = annai_lanes.instance_variable_get(:@specLoader)
27
+
28
+ UI.user_error!("Internal Error: AnnaiLanes failed to initialize specLoader") unless spec_loader
29
+
30
+ # Flag to track if any flavor download failed
31
+ any_download_failed = false
32
+
33
+ # 2. Get the list of all flavors defined for the platform
34
+ flavors_hash = spec_loader.get_platform_flavors(platform)
35
+
36
+ if flavors_hash.empty?
37
+ UI.important("No flavors found for iOS in the Annai spec. Nothing to download")
38
+ annai_lanes.finalize
39
+ return
40
+ end
41
+
42
+ all_flavor_names = flavors_hash.keys
43
+
44
+ # 3. Determine which flavors to download
45
+ if requested_flavor && !requested_flavor.to_s.empty?
46
+ requested_flavors = requested_flavor.to_s.split(',').map(&:strip).reject(&:empty?)
47
+
48
+ # Validate all requested flavors
49
+ invalid_flavors = requested_flavors.reject { |f| all_flavor_names.include?(f) }
50
+
51
+ if invalid_flavors.any?
52
+ 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(', ')}")
53
+ end
54
+
55
+ flavors_to_download = requested_flavors
56
+ else
57
+ # No specific flavor requested, download all of them
58
+ flavors_to_download = all_flavor_names
59
+ end
60
+
61
+ UI.header("Starting App Store download. Flavors to process: #{flavors_to_download.join(', ')}")
62
+
63
+ # 4. Loop through each flavor and download
64
+ flavors_to_download.each do |flavor_key|
65
+
66
+ flavor = flavor_key
67
+
68
+ # spec_loader.get_package_id handles the bundle_identifier
69
+ final_api_key_path = spec_loader.get_api_key_path(platform, flavor) || params[:api_key_path]
70
+ final_package_name = spec_loader.get_package_id(platform, flavor) || params[:package_name]
71
+
72
+ UI.message("โฌ‡๏ธ Downloading store content for **#{flavor}** flavor...")
73
+
74
+ begin
75
+ success = annai_lanes.download_from_store(
76
+ package_name: final_package_name,
77
+ api_key_path: final_api_key_path,
78
+ flavor: flavor,
79
+ platform: platform.to_s,
80
+ use_live_version: params[:use_live_version],
81
+ metadata_path: params[:metadata_path],
82
+ screenshots_path: params[:screenshots_path],
83
+ distribution_platform: params[:distribution_platform],
84
+ )
85
+
86
+ if success
87
+ UI.success("โœ… Successfully downloaded app store content for flavor: #{flavor}")
88
+ else
89
+ # If download_from_store returns false (meaning it caught an error and logged it), flag the overall failure.
90
+ # We only raise/log here if the inner action didn't handle it, to ensure logging consistency.
91
+ if !status_manager.instance_variable_get(:@status).key?([flavor, "download_from_store", platform.to_s])
92
+ raise "Download failed for flavor '#{flavor}'"
93
+ else
94
+ raise "Download failed for flavor '#{flavor}' (error logged previously)"
95
+ end
96
+ end
97
+
98
+ rescue => e
99
+ # Failure handling: Log the error and continue
100
+ status_manager.logError(flavor, action_name, platform, e.message) unless status_manager.instance_variable_get(:@status).key?([flavor, "download_from_store", platform.to_s])
101
+
102
+ UI.error("โŒ Failed to download content for flavor: #{flavor}. Continuing to next flavor...")
103
+ any_download_failed = true
104
+ # Continue to the next flavor
105
+ end
106
+ end
107
+
108
+ # 5. Final Reporting and Status Check
109
+ annai_lanes.finalize
110
+
111
+ if any_download_failed
112
+ UI.user_error!("Download failed for one or more flavors. See the Annai Fastlane summary above")
113
+ else
114
+ UI.success("๐ŸŽ‰ Successfully downloaded App Store content for all #{flavors_to_download.count} requested flavor(s)!")
115
+ end
116
+ end
117
+
118
+ # ----------------------------------------------------
119
+ # Define Parameters
120
+ # ----------------------------------------------------
121
+ def self.available_options
122
+ [
123
+ FastlaneCore::ConfigItem.new(key: :flavor,
124
+ description: "The specific flavor(s) to download (comma-separated). If nil, all flavors are processed",
125
+ optional: true,
126
+ default_value: nil),
127
+
128
+ FastlaneCore::ConfigItem.new(key: :spec_file,
129
+ description: "Path to the annai spec configuration file (relative to flutter root). Defaults to standard discovery",
130
+ optional: true,
131
+ default_value: nil),
132
+
133
+ FastlaneCore::ConfigItem.new(key: :api_key_path,
134
+ description: "Path to the App Store Connect API Key JSON file (iOS). Optional if defined in the Annai spec",
135
+ optional: true,
136
+ default_value: nil),
137
+ FastlaneCore::ConfigItem.new(key: :package_name,
138
+ description: "The bundle identifier (iOS). Optional if defined in the Annai spec",
139
+ optional: true,
140
+ default_value: nil),
141
+
142
+ FastlaneCore::ConfigItem.new(key: :distribution_platform,
143
+ description: "The distribution platform (e.g., 'ios' or 'app_store_connect') for App Store uploads",
144
+ optional: true,
145
+ default_value: "ios"),
146
+
147
+ FastlaneCore::ConfigItem.new(key: :metadata_path,
148
+ description: "The path where localization metadata (e.g., descriptions, titles) should be downloaded",
149
+ optional: true,
150
+ default_value: nil),
151
+ FastlaneCore::ConfigItem.new(key: :screenshots_path,
152
+ description: "The path where screenshots should be downloaded (iOS only)",
153
+ optional: true,
154
+ default_value: nil),
155
+
156
+ FastlaneCore::ConfigItem.new(key: :use_live_version,
157
+ description: "If true, downloads the currently published live version. If false (default), downloads the editable version (iOS only)",
158
+ optional: true,
159
+ type: Boolean,
160
+ default_value: false),
161
+ ].compact
162
+ end
163
+
164
+ def self.description
165
+ "Downloads store metadata, screenshots, and other assets for one or all flavors from the Apple App Store"
166
+ end
167
+
168
+ def self.is_supported?(platform)
169
+ platform == :ios
170
+ end
171
+
172
+ def self.example_code
173
+ 'ann_download_from_app_store(
174
+ flavor: "production",
175
+ spec_file: "config/annai_config.yaml", # Optional custom spec path
176
+ api_key_path: "./api_keys/asc.json",
177
+ package_name: "com.example.app",
178
+ use_live_version: true
179
+ )'
180
+ end
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,168 @@
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_download_from_play_store'
8
+ class AnnaiDownloadFromPlayStoreAction < Action
9
+
10
+ def self.run(params)
11
+ # Platform is hardcoded to :android
12
+ platform = :android
13
+ action_name = "ann_download_from_play_store"
14
+ requested_flavor = params[:flavor]
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
+ # Get the StatusManager and SpecLoader instances
25
+ status_manager = annai_lanes.instance_variable_get(:@statusManager)
26
+ spec_loader = annai_lanes.instance_variable_get(:@specLoader)
27
+
28
+ UI.user_error!("Internal Error: AnnaiLanes failed to initialize specLoader") unless spec_loader
29
+
30
+ # Flag to track if any flavor download failed
31
+ any_download_failed = false
32
+
33
+ # 2. Get the list of all flavors defined for the platform
34
+ flavors_hash = spec_loader.get_platform_flavors(platform)
35
+
36
+ if flavors_hash.empty?
37
+ UI.important("No flavors found for Android in the Annai spec. Nothing to download")
38
+ annai_lanes.finalize
39
+ return
40
+ end
41
+
42
+ all_flavor_names = flavors_hash.keys
43
+
44
+ # 3. Determine which flavors to download
45
+ if requested_flavor && !requested_flavor.to_s.empty?
46
+ requested_flavors = requested_flavor.to_s.split(',').map(&:strip).reject(&:empty?)
47
+
48
+ # Validate all requested flavors
49
+ invalid_flavors = requested_flavors.reject { |f| all_flavor_names.include?(f) }
50
+
51
+ if invalid_flavors.any?
52
+ UI.user_error!("The following requested flavor(s) are not found in the Annai spec for Android: #{invalid_flavors.join(', ')}. Available flavors: #{all_flavor_names.join(', ')}")
53
+ end
54
+
55
+ flavors_to_download = requested_flavors
56
+ else
57
+ # No specific flavor requested, download all of them
58
+ flavors_to_download = all_flavor_names
59
+ end
60
+
61
+ UI.header("Starting Google Play download. Flavors to process: #{flavors_to_download.join(', ')}")
62
+
63
+ # 4. Loop through each flavor and download
64
+ flavors_to_download.each do |flavor_key|
65
+
66
+ flavor = flavor_key
67
+
68
+ # spec_loader.get_package_id handles the package_name
69
+ final_api_key_path = spec_loader.get_api_key_path(platform, flavor) || params[:api_key_path]
70
+ final_package_name = spec_loader.get_package_id(platform, flavor) || params[:package_name]
71
+
72
+ UI.message("โฌ‡๏ธ Downloading store content for **#{flavor}** flavor...")
73
+
74
+ begin
75
+ success = annai_lanes.download_from_store(
76
+ package_name: final_package_name,
77
+ api_key_path: final_api_key_path,
78
+ flavor: flavor,
79
+ platform: platform.to_s,
80
+ # Android only needs metadata path
81
+ metadata_path: params[:metadata_path],
82
+ screenshots_path: nil, # Omitted for Android download action
83
+ use_live_version: nil, # Omitted for Android download action
84
+ distribution_platform: nil # Omitted for Android download action
85
+ )
86
+
87
+ if success
88
+ UI.success("โœ… Successfully downloaded store content for flavor: #{flavor}")
89
+ else
90
+ # If download_from_store returns false (meaning it caught an error and logged it), flag the overall failure.
91
+ # We only raise/log here if the inner action didn't handle it, to ensure logging consistency.
92
+ if !status_manager.instance_variable_get(:@status).key?([flavor, "download_from_store", platform.to_s])
93
+ raise "Download failed for flavor '#{flavor}'"
94
+ else
95
+ raise "Download failed for flavor '#{flavor}' (error logged previously)"
96
+ end
97
+ end
98
+
99
+ rescue => e
100
+ # Failure handling: Log the error and continue
101
+ status_manager.logError(flavor, action_name, platform, e.message) unless status_manager.instance_variable_get(:@status).key?([flavor, "download_from_store", platform.to_s])
102
+
103
+ UI.error("โŒ Failed to download content for flavor: #{flavor}. Continuing to next flavor...")
104
+ any_download_failed = true
105
+ # Continue to the next flavor
106
+ end
107
+ end
108
+
109
+ # 5. Final Reporting and Status Check
110
+ annai_lanes.finalize
111
+
112
+ if any_download_failed
113
+ UI.user_error!("Download failed for one or more flavors. See the Annai Fastlane summary above")
114
+ else
115
+ UI.success("๐ŸŽ‰ Successfully downloaded Google Play content for all #{flavors_to_download.count} requested flavor(s)!")
116
+ end
117
+ end
118
+
119
+ # ----------------------------------------------------
120
+ # Define Parameters
121
+ # ----------------------------------------------------
122
+ def self.available_options
123
+ [
124
+ FastlaneCore::ConfigItem.new(key: :flavor,
125
+ description: "The specific flavor(s) to download (comma-separated). If nil, all flavors are processed",
126
+ optional: true,
127
+ default_value: nil),
128
+
129
+ FastlaneCore::ConfigItem.new(key: :spec_file,
130
+ description: "Path to the annai spec configuration file (relative to flutter root). Defaults to standard discovery",
131
+ optional: true,
132
+ default_value: nil),
133
+
134
+ FastlaneCore::ConfigItem.new(key: :api_key_path,
135
+ description: "Path to the Google Service Account JSON file (Android). Optional if defined in the Annai spec",
136
+ optional: true,
137
+ default_value: nil),
138
+ FastlaneCore::ConfigItem.new(key: :package_name,
139
+ description: "The package name (Android). Optional if defined in the Annai spec",
140
+ optional: true,
141
+ default_value: nil),
142
+
143
+ FastlaneCore::ConfigItem.new(key: :metadata_path,
144
+ description: "The path where localization metadata (e.g., descriptions, titles) should be downloaded",
145
+ optional: true,
146
+ default_value: nil),
147
+ ].compact
148
+ end
149
+
150
+ def self.description
151
+ "Downloads store metadata for one or all flavors from the Google Play Store"
152
+ end
153
+
154
+ def self.is_supported?(platform)
155
+ platform == :android
156
+ end
157
+
158
+ def self.example_code
159
+ 'ann_download_from_play_store(
160
+ flavor: "production",
161
+ spec_file: "config/annai_config.yaml", # Optional custom spec path
162
+ api_key_path: "./api_keys/google_play_key.json",
163
+ package_name: "com.example.app",
164
+ )'
165
+ end
166
+ end
167
+ end
168
+ end