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.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +271 -0
- data/Rakefile +6 -0
- data/ann-flutter-flavor.gemspec +25 -0
- data/lib/ann-flutter-flavor.rb +10 -0
- data/lib/ann_flutter_flavor/version.rb +3 -0
- data/lib/fastlane/plugin/ann_flutter_flavor/actions/ann_compile_build_action.rb +219 -0
- data/lib/fastlane/plugin/ann_flutter_flavor/actions/ann_download_from_app_store_action.rb +183 -0
- data/lib/fastlane/plugin/ann_flutter_flavor/actions/ann_download_from_play_store_action.rb +168 -0
- data/lib/fastlane/plugin/ann_flutter_flavor/actions/ann_emulators_action.rb +180 -0
- data/lib/fastlane/plugin/ann_flutter_flavor/actions/ann_run_tests_action.rb +128 -0
- data/lib/fastlane/plugin/ann_flutter_flavor/actions/ann_setup_action.rb +137 -0
- data/lib/fastlane/plugin/ann_flutter_flavor/actions/ann_upload_to_app_store_action.rb +280 -0
- data/lib/fastlane/plugin/ann_flutter_flavor/actions/ann_upload_to_firebase_action.rb +199 -0
- data/lib/fastlane/plugin/ann_flutter_flavor/actions/ann_upload_to_play_store_action.rb +248 -0
- data/lib/fastlane/plugin/ann_flutter_flavor/helper/lanes_android.rb +120 -0
- data/lib/fastlane/plugin/ann_flutter_flavor/helper/lanes_annai.rb +502 -0
- data/lib/fastlane/plugin/ann_flutter_flavor/helper/lanes_ios.rb +157 -0
- data/lib/fastlane/plugin/ann_flutter_flavor/helper/lanes_setup.rb +161 -0
- data/lib/fastlane/plugin/ann_flutter_flavor/helper/podspec_bridge.rb +153 -0
- data/lib/fastlane/plugin/ann_flutter_flavor/helper/test_integration.rb +346 -0
- data/lib/fastlane/plugin/ann_flutter_flavor/helper/utils_project_config.rb +96 -0
- data/lib/fastlane/plugin/ann_flutter_flavor/helper/utils_spec_loader.rb +363 -0
- data/lib/fastlane/plugin/ann_flutter_flavor/helper/utils_status.rb +115 -0
- data/lib/fastlane/plugin/ann_flutter_flavor.rb +23 -0
- data/plugin_manager.sh +140 -0
- metadata +112 -0
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
require 'yaml'
|
|
2
|
+
require 'fileutils'
|
|
3
|
+
require 'fastlane/action'
|
|
4
|
+
|
|
5
|
+
require 'fastlane/plugin/ann_flutter_flavor/helper/lanes_android'
|
|
6
|
+
require 'fastlane/plugin/ann_flutter_flavor/helper/lanes_ios'
|
|
7
|
+
require 'fastlane/plugin/ann_flutter_flavor/helper/lanes_setup'
|
|
8
|
+
require 'fastlane/plugin/ann_flutter_flavor/helper/utils_project_config'
|
|
9
|
+
require 'fastlane/plugin/ann_flutter_flavor/helper/utils_spec_loader'
|
|
10
|
+
require 'fastlane/plugin/ann_flutter_flavor/helper/utils_status'
|
|
11
|
+
|
|
12
|
+
module FastlaneFlutterFlavor
|
|
13
|
+
|
|
14
|
+
# ------------------------------------
|
|
15
|
+
# AnnaiLanes (Main Wrapper Class)
|
|
16
|
+
# ------------------------------------
|
|
17
|
+
class AnnaiLanes
|
|
18
|
+
|
|
19
|
+
# Refactored initialize to use a single 'platform' symbol and accept an optional 'spec_file'
|
|
20
|
+
def initialize(lane:, platform:, spec_file: nil)
|
|
21
|
+
@lane = lane
|
|
22
|
+
@statusManager = StatusManager.new(lane: lane)
|
|
23
|
+
|
|
24
|
+
# Validate the platform input: now includes :web
|
|
25
|
+
unless [:android, :ios, :web].include?(platform)
|
|
26
|
+
raise "Invalid Fastfile configuration: 'platform' must be :android, :ios, or :web"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Set internal flags based on the single parameter
|
|
30
|
+
@platform = platform
|
|
31
|
+
@isAndroid = (platform == :android)
|
|
32
|
+
@isIos = (platform == :ios)
|
|
33
|
+
@isWeb = (platform == :web) # New flag for web platform
|
|
34
|
+
|
|
35
|
+
# 1. Find the project root folder using ProjectUtil helper
|
|
36
|
+
@root_folder = FastlaneFlutterFlavor::ProjectUtil.find_flutter_root
|
|
37
|
+
|
|
38
|
+
# 2. Resolve the main spec file path
|
|
39
|
+
if spec_file
|
|
40
|
+
# If a spec_file path is explicitly provided, resolve it relative to the root folder.
|
|
41
|
+
full_spec_file = File.expand_path(spec_file, @root_folder)
|
|
42
|
+
else
|
|
43
|
+
# Otherwise, use ProjectUtil's logic which checks for ANN_SPEC_FILE env var and defaults.
|
|
44
|
+
# The method handles checking the ANN_SPEC_FILE environment variable and applying defaults.
|
|
45
|
+
full_spec_file = FastlaneFlutterFlavor::ProjectUtil.find_annai_spec_path(nil)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# full_spec_file contains the absolute path if found/resolved.
|
|
49
|
+
unless File.exist?(full_spec_file)
|
|
50
|
+
error_msg = "Configuration Error: The required specification file was not found. Path checked: #{full_spec_file}. "
|
|
51
|
+
unless spec_file
|
|
52
|
+
error_msg += "Please ensure 'annspec.yaml' exists or set the ANN_SPEC_FILE environment variable correctly."
|
|
53
|
+
end
|
|
54
|
+
raise error_msg
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Determine the root folder for resolving relative paths used in the spec file
|
|
58
|
+
@spec_root_folder = File.dirname(full_spec_file)
|
|
59
|
+
|
|
60
|
+
# Initialize the YamlSpecLoader using the determined root folder and file path
|
|
61
|
+
# We pass the root folder for context, and the full file path for loading
|
|
62
|
+
@specLoader = YamlSpecLoader.new(@root_folder, full_spec_file)
|
|
63
|
+
|
|
64
|
+
# Helper classes are only initialized if they are mobile platforms
|
|
65
|
+
@androidLanes = AndroidLanes.new(lane: lane, root_folder:@root_folder) if @isAndroid
|
|
66
|
+
@iosLanes = IosLanes.new(lane: lane, root_folder:@root_folder) if @isIos
|
|
67
|
+
|
|
68
|
+
@setup_lanes = SetupLanes.new(root_folder: @root_folder,status_manager: @statusManager)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def finalize()
|
|
72
|
+
@statusManager.displayStatus
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Handles unexpected exceptions during lane execution
|
|
76
|
+
def onError(exception)
|
|
77
|
+
@statusManager.logError "", "Unknown Error", exception.message
|
|
78
|
+
@statusManager.displayStatus
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def compile_build(flavor: "", sub_command: "appbundle", build_config: "release",
|
|
82
|
+
main_file: "lib/main.dart", skip_sound_null_safety: false,
|
|
83
|
+
skip_code_sign: false, export_options_plist: "", additional_parameter: "", platform:
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
begin
|
|
87
|
+
# nullSafety is the argument string or empty
|
|
88
|
+
nullSafety = skip_sound_null_safety ? "--no-sound-null-safety" : ""
|
|
89
|
+
|
|
90
|
+
# Determine command-line flavor parameters. The original 'flavor' parameter is preserved for logging.
|
|
91
|
+
cli_flavor = flavor
|
|
92
|
+
cli_flavor_option = cli_flavor.empty? ? "" : "--flavor"
|
|
93
|
+
|
|
94
|
+
# Initialize platform-specific options as empty
|
|
95
|
+
codeSign = ""
|
|
96
|
+
export_option = ""
|
|
97
|
+
|
|
98
|
+
if @isIos
|
|
99
|
+
# Only iOS uses codesign and export options plist
|
|
100
|
+
codeSign = skip_code_sign ? "--no-codesign" : ""
|
|
101
|
+
export_option = export_options_plist.empty? ? "" : "--export-options-plist=#{export_options_plist}"
|
|
102
|
+
|
|
103
|
+
elsif @isWeb
|
|
104
|
+
# Web compilation does not support --flavor. Override CLI parameters.
|
|
105
|
+
Fastlane::UI.message("Web compilation detected. Omitting '--flavor' from Flutter build command, but using '#{flavor}' for logging.")
|
|
106
|
+
cli_flavor = ""
|
|
107
|
+
cli_flavor_option = ""
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Base command parts
|
|
111
|
+
command_parts = [
|
|
112
|
+
"flutter", "build",
|
|
113
|
+
sub_command,
|
|
114
|
+
"--#{build_config}",
|
|
115
|
+
]
|
|
116
|
+
|
|
117
|
+
# Conditionally add nullSafety parameter
|
|
118
|
+
command_parts << nullSafety unless nullSafety.empty?
|
|
119
|
+
|
|
120
|
+
# Conditionally add flavor parts (only for non-web, non-empty flavor scenarios)
|
|
121
|
+
if !cli_flavor_option.empty?
|
|
122
|
+
command_parts << cli_flavor_option
|
|
123
|
+
command_parts << cli_flavor
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
command_parts << "-t"
|
|
127
|
+
command_parts << main_file
|
|
128
|
+
command_parts << additional_parameter
|
|
129
|
+
|
|
130
|
+
# Conditionally add iOS-specific parts
|
|
131
|
+
if @isIos
|
|
132
|
+
command_parts << codeSign unless codeSign.empty?
|
|
133
|
+
command_parts << export_option unless export_option.empty?
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Ensure the command runs from the Flutter root
|
|
137
|
+
flutter_root = @root_folder
|
|
138
|
+
|
|
139
|
+
# Clean up empty strings and execute
|
|
140
|
+
command_to_execute = command_parts.flatten.compact.map(&:to_s).reject(&:empty?)
|
|
141
|
+
|
|
142
|
+
Fastlane::UI.message("Executing command: #{command_to_execute.join(' ')}")
|
|
143
|
+
|
|
144
|
+
Dir.chdir flutter_root do
|
|
145
|
+
Fastlane::Actions::sh(*command_to_execute)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Logging uses the original 'flavor' parameter
|
|
149
|
+
@statusManager.logSuccess flavor, "compile_build", platform
|
|
150
|
+
return true
|
|
151
|
+
|
|
152
|
+
rescue => e
|
|
153
|
+
Fastlane::UI.error "Error while compiling flavor #{flavor}"
|
|
154
|
+
Fastlane::UI.error e
|
|
155
|
+
|
|
156
|
+
# Logging uses the original 'flavor' parameter
|
|
157
|
+
@statusManager.logError flavor, "compile_build", platform, e
|
|
158
|
+
return false
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# New method to compile web build and deploy to Firebase Hosting
|
|
163
|
+
def upload_to_firebase(flavor: "", main_file: "lib/main.dart", build_config: "release",
|
|
164
|
+
skip_compile: false, skip_sound_null_safety: false,
|
|
165
|
+
firebase_project:, firebase_token:) # firebase_project is the user override
|
|
166
|
+
|
|
167
|
+
begin
|
|
168
|
+
unless @isWeb
|
|
169
|
+
raise "Invalid call to upload_to_firebase. This action is only supported for the :web platform. Error during uploading of flavor #{flavor}"
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# --- 1. Determine the Firebase Project ID (Prioritization Logic) ---
|
|
173
|
+
project_id = firebase_project.to_s.strip # Check user-provided argument first
|
|
174
|
+
|
|
175
|
+
if project_id.empty?
|
|
176
|
+
# If user argument is empty, fallback to spec file configuration
|
|
177
|
+
project_id = @specLoader.get_firebase_project_id(@platform, flavor, "release").to_s.strip
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
if project_id.empty?
|
|
181
|
+
raise "Missing Firebase Project ID. Please provide it via the 'firebase_project' parameter or define it under 'project_id' in your annai spec file for flavor '#{flavor}' and platform '#{@platform}'."
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
final_project_id = project_id
|
|
185
|
+
Fastlane::UI.message("Using Firebase Project ID: '#{final_project_id}'")
|
|
186
|
+
|
|
187
|
+
# 2. Compile the web build unless skipped
|
|
188
|
+
unless skip_compile
|
|
189
|
+
unless(
|
|
190
|
+
compile_build(
|
|
191
|
+
flavor: flavor,
|
|
192
|
+
sub_command: "web", # Use 'web' as sub_command for 'flutter build web'
|
|
193
|
+
build_config: build_config,
|
|
194
|
+
main_file: main_file,
|
|
195
|
+
skip_sound_null_safety: skip_sound_null_safety,
|
|
196
|
+
skip_code_sign: true, # Not applicable for web
|
|
197
|
+
export_options_plist: "", # Not applicable for web
|
|
198
|
+
platform: @platform,
|
|
199
|
+
)
|
|
200
|
+
)
|
|
201
|
+
raise "Error during compilation of web flavor #{flavor}"
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# 3. Read the existing firebase.json
|
|
206
|
+
firebase_config_path = File.join(@root_folder, "firebase.json")
|
|
207
|
+
config = JSON.parse(File.read(firebase_config_path))
|
|
208
|
+
|
|
209
|
+
# 4. Update the hosting block
|
|
210
|
+
config['hosting'] = {
|
|
211
|
+
"public" => "build/web",
|
|
212
|
+
"ignore" => ["firebase.json", "**/.*", "**/node_modules/**"],
|
|
213
|
+
"rewrites" => [{"source" => "**", "destination" => "/index.html"}]
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
# 5. Write the updated firebase.json back to disk
|
|
217
|
+
File.write(firebase_config_path, JSON.pretty_generate(config))
|
|
218
|
+
|
|
219
|
+
token = firebase_token.to_s.strip
|
|
220
|
+
if token.empty?
|
|
221
|
+
token = @specLoader.get_firebase_token_from_properties.to_s.strip
|
|
222
|
+
unless token.empty?
|
|
223
|
+
Fastlane::UI.message("Successfully retrieved Firebase token from external properties file.")
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# 6. Deploy to Firebase Hosting using the determined ID
|
|
228
|
+
command_parts = [
|
|
229
|
+
"firebase", "deploy",
|
|
230
|
+
"--only", "hosting",
|
|
231
|
+
"--project", final_project_id,
|
|
232
|
+
]
|
|
233
|
+
# Only add the token flags if token is present
|
|
234
|
+
if !token.empty?
|
|
235
|
+
command_parts.push("--token", token)
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
firebase_root = @root_folder
|
|
239
|
+
|
|
240
|
+
Fastlane::UI.message("Executing Firebase deploy command: #{command_parts.join(' ')}")
|
|
241
|
+
|
|
242
|
+
Dir.chdir firebase_root do
|
|
243
|
+
Fastlane::Actions::sh(*command_parts)
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
@statusManager.logSuccess flavor, "upload_to_firebase", @platform
|
|
247
|
+
return true
|
|
248
|
+
|
|
249
|
+
rescue => e
|
|
250
|
+
Fastlane::UI.error "Error while uploading to Firebase for web flavor #{flavor}"
|
|
251
|
+
Fastlane::UI.error e
|
|
252
|
+
@statusManager.logError flavor, "upload_to_firebase", @platform, e
|
|
253
|
+
return false
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def download_from_store(package_name:, api_key_path:, flavor: "", platform:, use_live_version:, metadata_path:, screenshots_path:, distribution_platform:)
|
|
258
|
+
begin
|
|
259
|
+
if @isWeb
|
|
260
|
+
raise "Invalid call: Web compilation does not support downloading from mobile app stores."
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# API key path is relative to the spec file root
|
|
264
|
+
api_key_absolute_path = File.join(@spec_root_folder, api_key_path)
|
|
265
|
+
|
|
266
|
+
if @isAndroid
|
|
267
|
+
@androidLanes.download_from_store(
|
|
268
|
+
package_name: package_name,
|
|
269
|
+
api_key_path: api_key_absolute_path,
|
|
270
|
+
metadata_path: @androidLanes.get_metadata_path(metadata_path: metadata_path, flavor: flavor),
|
|
271
|
+
platform: platform,
|
|
272
|
+
)
|
|
273
|
+
end
|
|
274
|
+
if @isIos
|
|
275
|
+
@iosLanes.download_from_store(
|
|
276
|
+
package_name: package_name,
|
|
277
|
+
api_key_path: api_key_absolute_path,
|
|
278
|
+
metadata_path: @iosLanes.get_metadata_path(metadata_path: metadata_path, flavor: flavor, distribution_platform: distribution_platform),
|
|
279
|
+
screenshots_path: @iosLanes.get_screenshots_path(screenshots_path: screenshots_path, flavor: flavor, distribution_platform: distribution_platform),
|
|
280
|
+
deliver_file: @iosLanes.get_deliver_file(),
|
|
281
|
+
use_live_version: use_live_version,
|
|
282
|
+
)
|
|
283
|
+
end
|
|
284
|
+
@statusManager.logSuccess flavor, "download_from_store", platform
|
|
285
|
+
return true
|
|
286
|
+
rescue => e
|
|
287
|
+
Fastlane::UI.error "Error while downloading from store for flavor #{flavor}"
|
|
288
|
+
Fastlane::UI.error e
|
|
289
|
+
@statusManager.logError flavor, "download_from_store", platform, e
|
|
290
|
+
return false
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def upload_to_play_store(package_name:, api_key_path:, in_app_update_priority: 1, flavor: "",
|
|
295
|
+
main_file: "lib/main.dart", track: "beta", track_promote_to: "production", skip_sound_null_safety: false, skip_compile: false,
|
|
296
|
+
skip_upload_prod: false, skip_upload_binary: false, skip_upload_changelogs: false,
|
|
297
|
+
skip_upload_metadata: false, skip_upload_images: false, skip_upload_screenshots: false, platform:,
|
|
298
|
+
metadata_path: nil
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
begin
|
|
302
|
+
|
|
303
|
+
unless @isAndroid
|
|
304
|
+
# This handles both general invalid calls and the new :web case
|
|
305
|
+
raise "Invalid call to upload_to_play_store. This action is only supported for the :android platform. Error during uploading of flavor #{flavor}"
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
build_config = "release"
|
|
309
|
+
sub_command = "appbundle"
|
|
310
|
+
# Adjusted aabFile path to be relative from the root
|
|
311
|
+
aabFile = "build/app/outputs/bundle/release/app-release.aab"
|
|
312
|
+
final_metadata_path = @androidLanes.get_metadata_path(metadata_path: metadata_path, flavor: flavor)
|
|
313
|
+
|
|
314
|
+
if flavor != ""
|
|
315
|
+
aabFile = "build/app/outputs/bundle/" + flavor + "Release/app-" + flavor + "-release.aab"
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
unless skip_compile
|
|
319
|
+
unless(
|
|
320
|
+
compile_build(
|
|
321
|
+
flavor: flavor,
|
|
322
|
+
sub_command: sub_command,
|
|
323
|
+
build_config: build_config,
|
|
324
|
+
main_file: main_file,
|
|
325
|
+
skip_sound_null_safety: skip_sound_null_safety,
|
|
326
|
+
skip_code_sign: false,
|
|
327
|
+
platform: platform,
|
|
328
|
+
)
|
|
329
|
+
)
|
|
330
|
+
raise "Error during compilation of flavor #{flavor}"
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
# Prepend the root folder to the aabFile path since Fastlane actions require absolute paths
|
|
335
|
+
aab_absolute_path = File.join(@root_folder, aabFile)
|
|
336
|
+
# API key path is relative to the spec file root
|
|
337
|
+
api_key_absolute_path = File.join(@spec_root_folder, api_key_path)
|
|
338
|
+
|
|
339
|
+
@androidLanes.upload_to_store(
|
|
340
|
+
aab: aab_absolute_path,
|
|
341
|
+
package_name: package_name,
|
|
342
|
+
in_app_update_priority: in_app_update_priority,
|
|
343
|
+
track: track,
|
|
344
|
+
track_promote_to: track_promote_to,
|
|
345
|
+
metadata_path: final_metadata_path,
|
|
346
|
+
api_key_path: api_key_absolute_path,
|
|
347
|
+
skip_upload_prod: skip_upload_prod,
|
|
348
|
+
skip_upload_binary: skip_upload_binary,
|
|
349
|
+
skip_upload_changelogs: skip_upload_changelogs,
|
|
350
|
+
skip_upload_metadata: skip_upload_metadata,
|
|
351
|
+
skip_upload_images: skip_upload_images,
|
|
352
|
+
skip_upload_screenshots: skip_upload_screenshots,
|
|
353
|
+
platform: platform,
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
@statusManager.logSuccess flavor, "upload_to_play_store", platform
|
|
357
|
+
return true
|
|
358
|
+
|
|
359
|
+
rescue => e
|
|
360
|
+
Fastlane::UI.error "Error while uploading to store for flavor #{flavor}"
|
|
361
|
+
Fastlane::UI.error e
|
|
362
|
+
@statusManager.logError flavor, "upload_to_play_store", platform, e
|
|
363
|
+
return false
|
|
364
|
+
end
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
def upload_to_app_store(bundle_identifier:, api_key_path:, flavor: "",
|
|
368
|
+
main_file: "lib/main.dart", display_name:, export_option_file:,
|
|
369
|
+
team_id:, signing_certificate:, app_version:, build_number:,
|
|
370
|
+
skip_sound_null_safety: false, skip_compile: false, skip_upload_binary: false,
|
|
371
|
+
skip_upload_prod: false,
|
|
372
|
+
skip_upload_metadata: false, skip_upload_screenshots: false,
|
|
373
|
+
export_compliance_uses_encryption: nil, add_id_info_uses_idfa: nil, platform:,
|
|
374
|
+
metadata_path: nil, screenshots_path: nil, distribution_platform: nil
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
begin
|
|
378
|
+
|
|
379
|
+
unless @isIos
|
|
380
|
+
# This handles both general invalid calls and the new :web case
|
|
381
|
+
raise "Invalid call to upload_to_app_store. This action is only supported for the :ios platform. Error during uploading of flavor #{flavor}"
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
build_config = "release"
|
|
385
|
+
sub_command = "ipa"
|
|
386
|
+
build_config_xcode = "Release"
|
|
387
|
+
|
|
388
|
+
# Paths are now relative to the root folder, which is @root_folder
|
|
389
|
+
root_folder = @root_folder
|
|
390
|
+
ipa_folder = File.join(root_folder, "build/ios/ipa/") # Source folder for built IPA
|
|
391
|
+
archive_folder = File.join(root_folder, "build/ios/archive/")
|
|
392
|
+
ipa_flavor_folder = File.join(ipa_folder, flavor, platform) # Destination folder
|
|
393
|
+
ios_project_path = File.join(root_folder, "ios")
|
|
394
|
+
|
|
395
|
+
# The path the upload process will use
|
|
396
|
+
ipa_file = File.join(ipa_flavor_folder, display_name + ".ipa")
|
|
397
|
+
|
|
398
|
+
final_metadata_path = @iosLanes.get_metadata_path(metadata_path: metadata_path, flavor: flavor, distribution_platform: distribution_platform)
|
|
399
|
+
final_screenshots_path = @iosLanes.get_screenshots_path(screenshots_path: screenshots_path, flavor: flavor, distribution_platform: distribution_platform)
|
|
400
|
+
api_key_absolute_path = File.join(@spec_root_folder, api_key_path)
|
|
401
|
+
|
|
402
|
+
if flavor != ""
|
|
403
|
+
build_config_xcode = "Release-" + flavor
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
# 1. Ensure provisioning profile is correct
|
|
407
|
+
Dir.chdir ios_project_path do
|
|
408
|
+
@iosLanes.sigh(
|
|
409
|
+
app_identifier: bundle_identifier,
|
|
410
|
+
api_key_path: api_key_absolute_path,
|
|
411
|
+
team_id: team_id,
|
|
412
|
+
)
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
unless skip_compile
|
|
416
|
+
|
|
417
|
+
# 2. Update Xcode code signing settings
|
|
418
|
+
Dir.chdir ios_project_path do
|
|
419
|
+
@iosLanes.update_code_signing_settings(
|
|
420
|
+
team_id: team_id,
|
|
421
|
+
code_sign_identity: signing_certificate,
|
|
422
|
+
profile_name: ENV["SIGH_NAME"],
|
|
423
|
+
build_configurations: build_config_xcode,
|
|
424
|
+
)
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
# 3. Compile the build (creates an IPA in ipa_folder)
|
|
428
|
+
unless(
|
|
429
|
+
compile_build(
|
|
430
|
+
flavor: flavor,
|
|
431
|
+
sub_command: sub_command,
|
|
432
|
+
build_config: build_config,
|
|
433
|
+
main_file: main_file,
|
|
434
|
+
skip_sound_null_safety: skip_sound_null_safety,
|
|
435
|
+
skip_code_sign: false,
|
|
436
|
+
export_options_plist: export_option_file,
|
|
437
|
+
platform: platform,
|
|
438
|
+
)
|
|
439
|
+
)
|
|
440
|
+
raise "Error during compilation of flavor #{flavor}"
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
# 4. Find the generated IPA, move it to the flavor folder, and rename it.
|
|
444
|
+
ipa_candidates = Dir.glob(File.join(ipa_folder, "*.ipa"))
|
|
445
|
+
if ipa_candidates.empty?
|
|
446
|
+
raise "Compilation Error: No IPA file found in expected directory: #{ipa_folder}"
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
original_ipa_path = ipa_candidates.first
|
|
450
|
+
|
|
451
|
+
# Create the destination directory
|
|
452
|
+
FileUtils.mkdir_p ipa_flavor_folder
|
|
453
|
+
|
|
454
|
+
# Move and rename the IPA file to the expected display_name.ipa
|
|
455
|
+
FileUtils.mv(original_ipa_path, ipa_file)
|
|
456
|
+
Fastlane::UI.message "Moved and renamed IPA from #{File.basename(original_ipa_path)} to #{File.expand_path(ipa_file)}"
|
|
457
|
+
|
|
458
|
+
# 5. Backup the XCArchive
|
|
459
|
+
@iosLanes.backup_xcarchive(
|
|
460
|
+
xcarchive: File.join(archive_folder, 'Runner.xcarchive'),
|
|
461
|
+
destination: ipa_flavor_folder,
|
|
462
|
+
zip_filename: 'Runner',
|
|
463
|
+
)
|
|
464
|
+
end
|
|
465
|
+
|
|
466
|
+
# 6. Upload to App Store Connect
|
|
467
|
+
unless skip_upload_prod && skip_upload_binary && skip_upload_metadata && skip_upload_screenshots
|
|
468
|
+
@iosLanes.upload_to_store(
|
|
469
|
+
ipa_file: ipa_file,
|
|
470
|
+
bundle_identifier: bundle_identifier,
|
|
471
|
+
api_key_path: api_key_absolute_path,
|
|
472
|
+
metadata_path: final_metadata_path,
|
|
473
|
+
screenshots_path: final_screenshots_path,
|
|
474
|
+
app_version: app_version,
|
|
475
|
+
build_number: build_number,
|
|
476
|
+
skip_upload_prod: skip_upload_prod,
|
|
477
|
+
skip_upload_binary: skip_upload_binary,
|
|
478
|
+
skip_upload_metadata: skip_upload_metadata,
|
|
479
|
+
skip_upload_screenshots: skip_upload_screenshots,
|
|
480
|
+
export_compliance_uses_encryption: export_compliance_uses_encryption,
|
|
481
|
+
add_id_info_uses_idfa: add_id_info_uses_idfa,
|
|
482
|
+
platform: platform,
|
|
483
|
+
)
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
@statusManager.logSuccess flavor, "upload_to_app_store", platform
|
|
487
|
+
return true
|
|
488
|
+
|
|
489
|
+
rescue => e
|
|
490
|
+
Fastlane::UI.error "Error while uploading to store for flavor #{flavor}"
|
|
491
|
+
Fastlane::UI.error e
|
|
492
|
+
@statusManager.logError flavor, "upload_to_app_store", platform, e
|
|
493
|
+
return false
|
|
494
|
+
end
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
def clean_build
|
|
498
|
+
@setup_lanes.cleanup
|
|
499
|
+
end
|
|
500
|
+
|
|
501
|
+
end
|
|
502
|
+
end
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
require 'yaml'
|
|
2
|
+
require 'fileutils'
|
|
3
|
+
require 'fastlane/action'
|
|
4
|
+
|
|
5
|
+
module FastlaneFlutterFlavor
|
|
6
|
+
|
|
7
|
+
# ------------------------------------
|
|
8
|
+
# IosLanes (Helper Class)
|
|
9
|
+
# ------------------------------------
|
|
10
|
+
class IosLanes
|
|
11
|
+
|
|
12
|
+
def initialize(lane: ,root_folder:)
|
|
13
|
+
@lane = lane
|
|
14
|
+
@root_folder = root_folder
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def upload_to_store(ipa_file:, bundle_identifier:, api_key_path:, metadata_path:, screenshots_path:,
|
|
18
|
+
app_version:, build_number:,
|
|
19
|
+
skip_upload_binary: false, skip_upload_prod: false,
|
|
20
|
+
skip_upload_metadata: false,
|
|
21
|
+
skip_upload_screenshots: false, export_compliance_uses_encryption: nil,
|
|
22
|
+
add_id_info_uses_idfa: nil, platform: "ios")
|
|
23
|
+
|
|
24
|
+
final_build_number = build_number.to_s
|
|
25
|
+
|
|
26
|
+
Fastlane::UI.message "App Version: " + app_version
|
|
27
|
+
Fastlane::UI.message "Build Number: " + final_build_number
|
|
28
|
+
Fastlane::UI.message "App identifier: " + bundle_identifier
|
|
29
|
+
|
|
30
|
+
unless skip_upload_binary
|
|
31
|
+
@lane.other_action.upload_to_testflight(
|
|
32
|
+
ipa: ipa_file,
|
|
33
|
+
app_identifier: bundle_identifier,
|
|
34
|
+
api_key_path: api_key_path,
|
|
35
|
+
app_version: app_version,
|
|
36
|
+
build_number: final_build_number,
|
|
37
|
+
expire_previous_builds: true,
|
|
38
|
+
skip_waiting_for_build_processing: skip_upload_prod,
|
|
39
|
+
app_platform: platform,
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
unless skip_upload_prod
|
|
44
|
+
|
|
45
|
+
submission_information = ""
|
|
46
|
+
unless export_compliance_uses_encryption == nil
|
|
47
|
+
submission_information += "\"export_compliance_uses_encryption\": #{export_compliance_uses_encryption}"
|
|
48
|
+
end
|
|
49
|
+
unless add_id_info_uses_idfa == nil
|
|
50
|
+
unless submission_information == ""
|
|
51
|
+
submission_information += ","
|
|
52
|
+
end
|
|
53
|
+
submission_information += "\"add_id_info_uses_idfa\": #{add_id_info_uses_idfa}"
|
|
54
|
+
end
|
|
55
|
+
submission_information = "{#{submission_information}}"
|
|
56
|
+
|
|
57
|
+
@lane.other_action.upload_to_app_store(
|
|
58
|
+
build_number: final_build_number,
|
|
59
|
+
app_identifier: bundle_identifier,
|
|
60
|
+
app_version: app_version,
|
|
61
|
+
api_key_path: api_key_path,
|
|
62
|
+
metadata_path: metadata_path,
|
|
63
|
+
screenshots_path: screenshots_path,
|
|
64
|
+
skip_binary_upload: skip_upload_binary,
|
|
65
|
+
skip_metadata: skip_upload_metadata,
|
|
66
|
+
skip_screenshots: skip_upload_screenshots,
|
|
67
|
+
overwrite_screenshots: skip_upload_screenshots == false,
|
|
68
|
+
submit_for_review: true,
|
|
69
|
+
automatic_release: true,
|
|
70
|
+
force: true,
|
|
71
|
+
precheck_include_in_app_purchases: false,
|
|
72
|
+
submission_information:submission_information,
|
|
73
|
+
platform: platform,
|
|
74
|
+
)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def download_from_store(package_name:, api_key_path:, metadata_path:, screenshots_path:, deliver_file:, platform:"ios", use_live_version:false)
|
|
79
|
+
FileUtils.rm_rf(metadata_path)
|
|
80
|
+
FileUtils.rm_rf(screenshots_path)
|
|
81
|
+
FileUtils.remove_file(deliver_file, force = true)
|
|
82
|
+
|
|
83
|
+
Fastlane::Actions::sh(
|
|
84
|
+
"fastlane", "deliver", "init",
|
|
85
|
+
"--metadata_path", metadata_path,
|
|
86
|
+
"--screenshots_path", screenshots_path,
|
|
87
|
+
"--app_identifier", package_name,
|
|
88
|
+
"--api_key_path", api_key_path,
|
|
89
|
+
"--platform", platform,
|
|
90
|
+
"--use_live_version", use_live_version ? "true" : "false",
|
|
91
|
+
)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def sigh(app_identifier:, api_key_path:, team_id:)
|
|
95
|
+
@lane.other_action.sigh(
|
|
96
|
+
force: true,
|
|
97
|
+
app_identifier: app_identifier,
|
|
98
|
+
api_key_path: api_key_path,
|
|
99
|
+
team_id: team_id,
|
|
100
|
+
)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def update_code_signing_settings(team_id:, code_sign_identity:, profile_name:, build_configurations:)
|
|
104
|
+
@lane.other_action.update_code_signing_settings(
|
|
105
|
+
team_id: team_id,
|
|
106
|
+
code_sign_identity: code_sign_identity,
|
|
107
|
+
use_automatic_signing: false,
|
|
108
|
+
profile_name: profile_name,
|
|
109
|
+
build_configurations: build_configurations,
|
|
110
|
+
)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def backup_xcarchive(xcarchive:, destination:, zip_filename: )
|
|
114
|
+
@lane.other_action.backup_xcarchive(
|
|
115
|
+
xcarchive: xcarchive,
|
|
116
|
+
destination: destination,
|
|
117
|
+
zip: true,
|
|
118
|
+
zip_filename: zip_filename,
|
|
119
|
+
versioned: false,
|
|
120
|
+
)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Resolves the metadata path: either a custom one relative to the root,
|
|
124
|
+
# or the standard Fastlane structure.
|
|
125
|
+
def get_metadata_path(platform: "ios", metadata_path: nil, flavor:, distribution_platform:)
|
|
126
|
+
# If a custom path is provided (and not nil or empty string), use it.
|
|
127
|
+
unless metadata_path.to_s.empty?
|
|
128
|
+
# Path structure: @root_folder / custom_path / flavor
|
|
129
|
+
return File.join(@root_folder, metadata_path, flavor, distribution_platform)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Default Path: Use the standard Fastlane structure.
|
|
133
|
+
# Path structure: @root_folder / fastlane / platform / metadata / flavor
|
|
134
|
+
File.join(@root_folder, "fastlane", platform, 'metadata', flavor, distribution_platform)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
# Resolves the screenshots path: either a custom one relative to the root,
|
|
139
|
+
# or the standard Fastlane structure.
|
|
140
|
+
def get_screenshots_path(platform: "ios", screenshots_path: nil, flavor:, distribution_platform:)
|
|
141
|
+
# If a custom path is provided (and not nil or empty string), use it.
|
|
142
|
+
unless screenshots_path.to_s.empty?
|
|
143
|
+
# Path structure: @root_folder / custom_path / flavor
|
|
144
|
+
return File.join(@root_folder, screenshots_path, flavor, distribution_platform)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Default Path: Use the standard Fastlane structure.
|
|
148
|
+
# Path structure: @root_folder / fastlane / platform / metadata / flavor
|
|
149
|
+
File.join(@root_folder, "fastlane", platform, 'screenshots', flavor, distribution_platform)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def get_deliver_file(platform: "ios")
|
|
153
|
+
return File.join(@root_folder, "fastlane", "Deliverfile")
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
end
|
|
157
|
+
end
|