fastlane 2.165.0 → 2.170.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +76 -76
- data/cert/lib/cert/options.rb +3 -3
- data/cert/lib/cert/runner.rb +1 -1
- data/deliver/lib/deliver/app_screenshot.rb +6 -2
- data/deliver/lib/deliver/loader.rb +136 -18
- data/deliver/lib/deliver/upload_metadata.rb +4 -10
- data/deliver/lib/deliver/upload_screenshots.rb +1 -64
- data/fastlane/lib/fastlane/actions/actions_helper.rb +1 -1
- data/fastlane/lib/fastlane/actions/add_git_tag.rb +9 -2
- data/fastlane/lib/fastlane/actions/appledoc.rb +1 -1
- data/fastlane/lib/fastlane/actions/docs/capture_ios_screenshots.md +4 -0
- data/fastlane/lib/fastlane/actions/docs/frame_screenshots.md +1 -1
- data/fastlane/lib/fastlane/actions/docs/sync_code_signing.md +1 -1
- data/fastlane/lib/fastlane/actions/docs/upload_to_app_store.md.erb +4 -0
- data/fastlane/lib/fastlane/actions/onesignal.rb +13 -3
- data/fastlane/lib/fastlane/actions/slather.rb +2 -2
- data/fastlane/lib/fastlane/actions/spm.rb +6 -0
- data/fastlane/lib/fastlane/actions/update_fastlane.rb +29 -8
- data/fastlane/lib/fastlane/actions/upload_app_privacy_details_to_app_store.rb +289 -0
- data/fastlane/lib/fastlane/actions/upload_to_app_store.rb +3 -3
- data/fastlane/lib/fastlane/actions/xcode_install.rb +8 -5
- data/fastlane/lib/fastlane/cli_tools_distributor.rb +2 -2
- data/fastlane/lib/fastlane/features.rb +1 -1
- data/fastlane/lib/fastlane/plugins/template/.rubocop.yml +2 -1
- data/fastlane/lib/fastlane/swift_fastlane_api_generator.rb +3 -0
- data/fastlane/lib/fastlane/swift_fastlane_function.rb +1 -1
- data/fastlane/lib/fastlane/version.rb +1 -1
- data/fastlane/swift/Deliverfile.swift +1 -1
- data/fastlane/swift/DeliverfileProtocol.swift +1 -1
- data/fastlane/swift/Fastfile.swift +1 -1
- data/fastlane/swift/Fastlane.swift +97 -26
- data/fastlane/swift/Gymfile.swift +1 -1
- data/fastlane/swift/GymfileProtocol.swift +5 -1
- data/fastlane/swift/LaneFileProtocol.swift +2 -2
- data/fastlane/swift/MainProcess.swift +2 -0
- data/fastlane/swift/Matchfile.swift +1 -1
- data/fastlane/swift/MatchfileProtocol.swift +3 -3
- data/fastlane/swift/Precheckfile.swift +1 -1
- data/fastlane/swift/PrecheckfileProtocol.swift +1 -1
- data/fastlane/swift/Runner.swift +1 -1
- data/fastlane/swift/Scanfile.swift +1 -1
- data/fastlane/swift/ScanfileProtocol.swift +9 -1
- data/fastlane/swift/Screengrabfile.swift +1 -1
- data/fastlane/swift/ScreengrabfileProtocol.swift +1 -1
- data/fastlane/swift/Snapshotfile.swift +1 -1
- data/fastlane/swift/SnapshotfileProtocol.swift +1 -1
- data/fastlane/swift/SocketClient.swift +1 -1
- data/fastlane_core/lib/fastlane_core/cert_checker.rb +12 -7
- data/fastlane_core/lib/fastlane_core/helper.rb +10 -2
- data/fastlane_core/lib/fastlane_core/itunes_transporter.rb +3 -3
- data/fastlane_core/lib/fastlane_core/provisioning_profile.rb +3 -1
- data/fastlane_core/lib/fastlane_core/ui/disable_colors.rb +8 -0
- data/gym/lib/gym/code_signing_mapping.rb +1 -1
- data/gym/lib/gym/generators/build_command_generator.rb +1 -0
- data/gym/lib/gym/options.rb +7 -1
- data/match/lib/match/module.rb +1 -1
- data/match/lib/match/nuke.rb +9 -5
- data/match/lib/match/options.rb +1 -1
- data/match/lib/match/runner.rb +1 -1
- data/pilot/lib/pilot/build_manager.rb +9 -3
- data/scan/lib/scan/detect_values.rb +3 -1
- data/scan/lib/scan/module.rb +4 -0
- data/scan/lib/scan/options.rb +16 -1
- data/scan/lib/scan/runner.rb +2 -2
- data/scan/lib/scan/test_command_generator.rb +1 -0
- data/snapshot/lib/assets/SnapshotHelper.swift +5 -1
- data/snapshot/lib/snapshot/test_command_generator.rb +1 -1
- data/snapshot/lib/snapshot/test_command_generator_base.rb +3 -1
- data/snapshot/lib/snapshot/test_command_generator_xcode_8.rb +1 -1
- data/spaceship/lib/spaceship/client.rb +14 -0
- data/spaceship/lib/spaceship/connect_api.rb +6 -0
- data/spaceship/lib/spaceship/connect_api/models/.app_data_usage_data_protection.rb.swp +0 -0
- data/spaceship/lib/spaceship/connect_api/models/age_rating_declaration.rb +3 -2
- data/spaceship/lib/spaceship/connect_api/models/app.rb +88 -54
- data/spaceship/lib/spaceship/connect_api/models/app_data_usage.rb +59 -0
- data/spaceship/lib/spaceship/connect_api/models/app_data_usage_category.rb +65 -0
- data/spaceship/lib/spaceship/connect_api/models/app_data_usage_data_protection.rb +27 -0
- data/spaceship/lib/spaceship/connect_api/models/app_data_usage_grouping.rb +18 -0
- data/spaceship/lib/spaceship/connect_api/models/app_data_usage_purposes.rb +37 -0
- data/spaceship/lib/spaceship/connect_api/models/app_data_usages_publish_state.rb +36 -0
- data/spaceship/lib/spaceship/connect_api/models/app_info.rb +16 -10
- data/spaceship/lib/spaceship/connect_api/models/app_info_localization.rb +8 -4
- data/spaceship/lib/spaceship/connect_api/models/app_preview.rb +15 -11
- data/spaceship/lib/spaceship/connect_api/models/app_preview_set.rb +13 -9
- data/spaceship/lib/spaceship/connect_api/models/app_screenshot.rb +9 -7
- data/spaceship/lib/spaceship/connect_api/models/app_screenshot_set.rb +15 -11
- data/spaceship/lib/spaceship/connect_api/models/app_store_review_attachment.rb +7 -5
- data/spaceship/lib/spaceship/connect_api/models/app_store_review_detail.rb +6 -4
- data/spaceship/lib/spaceship/connect_api/models/app_store_version.rb +55 -36
- data/spaceship/lib/spaceship/connect_api/models/app_store_version_localization.rb +21 -14
- data/spaceship/lib/spaceship/connect_api/models/app_store_version_submission.rb +3 -2
- data/spaceship/lib/spaceship/connect_api/models/beta_app_review_submission.rb +3 -2
- data/spaceship/lib/spaceship/connect_api/models/beta_feedback.rb +6 -4
- data/spaceship/lib/spaceship/connect_api/models/beta_group.rb +12 -2
- data/spaceship/lib/spaceship/connect_api/models/beta_tester.rb +12 -8
- data/spaceship/lib/spaceship/connect_api/models/build.rb +24 -16
- data/spaceship/lib/spaceship/connect_api/models/build_delivery.rb +3 -2
- data/spaceship/lib/spaceship/connect_api/models/bundle_id.rb +9 -6
- data/spaceship/lib/spaceship/connect_api/models/bundle_id_capability.rb +6 -4
- data/spaceship/lib/spaceship/connect_api/models/certificate.rb +12 -8
- data/spaceship/lib/spaceship/connect_api/models/device.rb +10 -4
- data/spaceship/lib/spaceship/connect_api/models/idfa_declaration.rb +6 -4
- data/spaceship/lib/spaceship/connect_api/models/profile.rb +12 -8
- data/spaceship/lib/spaceship/connect_api/models/reset_ratings_request.rb +3 -2
- data/spaceship/lib/spaceship/connect_api/models/sandbox_tester.rb +9 -6
- data/spaceship/lib/spaceship/connect_api/models/territory.rb +3 -2
- data/spaceship/lib/spaceship/connect_api/models/user.rb +6 -4
- data/spaceship/lib/spaceship/connect_api/models/user_invitation.rb +9 -6
- data/spaceship/lib/spaceship/connect_api/testflight/testflight.rb +12 -0
- data/spaceship/lib/spaceship/connect_api/tunes/tunes.rb +103 -0
- data/spaceship/lib/spaceship/errors.rb +19 -0
- data/spaceship/lib/spaceship/tunes/iap_detail.rb +1 -1
- data/spaceship/lib/spaceship/tunes/tunes_client.rb +2 -2
- data/spaceship/lib/spaceship/two_step_or_factor_client.rb +18 -6
- data/supply/lib/supply.rb +1 -1
- data/supply/lib/supply/options.rb +1 -1
- data/supply/lib/supply/uploader.rb +3 -2
- metadata +26 -18
@@ -375,9 +375,7 @@ module Deliver
|
|
375
375
|
# Check folder list (an empty folder signifies a language is required)
|
376
376
|
ignore_validation = options[:ignore_language_directory_validation]
|
377
377
|
Loader.language_folders(options[:metadata_path], ignore_validation).each do |lang_folder|
|
378
|
-
|
379
|
-
language = File.basename(lang_folder)
|
380
|
-
enabled_languages << language unless enabled_languages.include?(language)
|
378
|
+
enabled_languages << lang_folder.basename unless enabled_languages.include?(lang_folder.basename)
|
381
379
|
end
|
382
380
|
|
383
381
|
return unless enabled_languages.include?("default")
|
@@ -416,10 +414,7 @@ module Deliver
|
|
416
414
|
# Check folder list (an empty folder signifies a language is required)
|
417
415
|
ignore_validation = options[:ignore_language_directory_validation]
|
418
416
|
Loader.language_folders(options[:metadata_path], ignore_validation).each do |lang_folder|
|
419
|
-
|
420
|
-
|
421
|
-
language = File.basename(lang_folder)
|
422
|
-
enabled_languages << language unless enabled_languages.include?(language)
|
417
|
+
enabled_languages << lang_folder.basename unless enabled_languages.include?(lang_folder.basename)
|
423
418
|
end
|
424
419
|
|
425
420
|
# Mapping to strings because :default symbol can be passed in
|
@@ -530,14 +525,13 @@ module Deliver
|
|
530
525
|
# Load localised data
|
531
526
|
ignore_validation = options[:ignore_language_directory_validation]
|
532
527
|
Loader.language_folders(options[:metadata_path], ignore_validation).each do |lang_folder|
|
533
|
-
language = File.basename(lang_folder)
|
534
528
|
(LOCALISED_VERSION_VALUES.keys + LOCALISED_APP_VALUES.keys).each do |key|
|
535
|
-
path = File.join(lang_folder, "#{key}.txt")
|
529
|
+
path = File.join(lang_folder.path, "#{key}.txt")
|
536
530
|
next unless File.exist?(path)
|
537
531
|
|
538
532
|
UI.message("Loading '#{path}'...")
|
539
533
|
options[key] ||= {}
|
540
|
-
options[key][
|
534
|
+
options[key][lang_folder.basename] ||= File.read(path)
|
541
535
|
end
|
542
536
|
end
|
543
537
|
|
@@ -251,70 +251,7 @@ module Deliver
|
|
251
251
|
|
252
252
|
def collect_screenshots(options)
|
253
253
|
return [] if options[:skip_screenshots]
|
254
|
-
return
|
255
|
-
end
|
256
|
-
|
257
|
-
def collect_screenshots_for_languages(path, ignore_validation)
|
258
|
-
screenshots = []
|
259
|
-
extensions = '{png,jpg,jpeg}'
|
260
|
-
|
261
|
-
available_languages = UploadScreenshots.available_languages.each_with_object({}) do |lang, lang_hash|
|
262
|
-
lang_hash[lang.downcase] = lang
|
263
|
-
end
|
264
|
-
|
265
|
-
Loader.language_folders(path, ignore_validation).each do |lng_folder|
|
266
|
-
language = File.basename(lng_folder)
|
267
|
-
|
268
|
-
# Check to see if we need to traverse multiple platforms or just a single platform
|
269
|
-
if language == Loader::APPLE_TV_DIR_NAME || language == Loader::IMESSAGE_DIR_NAME
|
270
|
-
screenshots.concat(collect_screenshots_for_languages(File.join(path, language), ignore_validation))
|
271
|
-
next
|
272
|
-
end
|
273
|
-
|
274
|
-
files = Dir.glob(File.join(lng_folder, "*.#{extensions}"), File::FNM_CASEFOLD).sort
|
275
|
-
next if files.count == 0
|
276
|
-
|
277
|
-
framed_screenshots_found = Dir.glob(File.join(lng_folder, "*_framed.#{extensions}"), File::FNM_CASEFOLD).count > 0
|
278
|
-
|
279
|
-
UI.important("Framed screenshots are detected! 🖼 Non-framed screenshot files may be skipped. 🏃") if framed_screenshots_found
|
280
|
-
|
281
|
-
language_dir_name = File.basename(lng_folder)
|
282
|
-
|
283
|
-
if available_languages[language_dir_name.downcase].nil?
|
284
|
-
UI.user_error!("#{language_dir_name} is not an available language. Please verify that your language codes are available in iTunesConnect. See https://developer.apple.com/library/content/documentation/LanguagesUtilities/Conceptual/iTunesConnect_Guide/Chapters/AppStoreTerritories.html for more information.")
|
285
|
-
end
|
286
|
-
|
287
|
-
language = available_languages[language_dir_name.downcase]
|
288
|
-
|
289
|
-
files.each do |file_path|
|
290
|
-
is_framed = file_path.downcase.include?("_framed.")
|
291
|
-
is_watch = file_path.downcase.include?("watch")
|
292
|
-
|
293
|
-
if framed_screenshots_found && !is_framed && !is_watch
|
294
|
-
UI.important("🏃 Skipping screenshot file: #{file_path}")
|
295
|
-
next
|
296
|
-
end
|
297
|
-
|
298
|
-
screenshots << AppScreenshot.new(file_path, language)
|
299
|
-
end
|
300
|
-
end
|
301
|
-
|
302
|
-
# Checking if the device type exists in spaceship
|
303
|
-
# Ex: iPhone 6.1 inch isn't supported in App Store Connect but need
|
304
|
-
# to have it in there for frameit support
|
305
|
-
unaccepted_device_shown = false
|
306
|
-
screenshots.select! do |screenshot|
|
307
|
-
exists = !screenshot.device_type.nil?
|
308
|
-
unless exists
|
309
|
-
UI.important("Unaccepted device screenshots are detected! 🚫 Screenshot file will be skipped. 🏃") unless unaccepted_device_shown
|
310
|
-
unaccepted_device_shown = true
|
311
|
-
|
312
|
-
UI.important("🏃 Skipping screenshot file: #{screenshot.path} - Not an accepted App Store Connect device...")
|
313
|
-
end
|
314
|
-
exists
|
315
|
-
end
|
316
|
-
|
317
|
-
return screenshots
|
254
|
+
return Loader.load_app_screenshots(options[:screenshots_path], options[:ignore_language_directory_validation])
|
318
255
|
end
|
319
256
|
|
320
257
|
# helper method so Spaceship::Tunes.client.available_languages is easier to test
|
@@ -5,7 +5,7 @@ module Fastlane
|
|
5
5
|
PLATFORM_NAME = :PLATFORM_NAME
|
6
6
|
ENVIRONMENT = :ENVIRONMENT
|
7
7
|
|
8
|
-
# A
|
8
|
+
# A slightly decorated hash that will store and fetch sensitive data
|
9
9
|
# but not display it while iterating keys and values
|
10
10
|
class LaneContextValues < Hash
|
11
11
|
def initialize
|
@@ -6,7 +6,13 @@ module Fastlane
|
|
6
6
|
# lane name in lane_context could be nil because you can just call $fastlane add_git_tag which has no context
|
7
7
|
lane_name = Actions.lane_context[Actions::SharedValues::LANE_NAME].to_s.delete(' ') # no spaces allowed
|
8
8
|
|
9
|
-
|
9
|
+
if options[:tag]
|
10
|
+
tag = options[:tag]
|
11
|
+
elsif options[:build_number]
|
12
|
+
tag = "#{options[:grouping]}/#{lane_name}/#{options[:prefix]}#{options[:build_number]}#{options[:postfix]}"
|
13
|
+
else
|
14
|
+
UI.user_error!("No value found for 'tag' or 'build_number'. At least one of them must be provided. Note that if you do specify a tag, all other arguments are ignored.")
|
15
|
+
end
|
10
16
|
message = options[:message] || "#{tag} (fastlane)"
|
11
17
|
|
12
18
|
cmd = ['git tag']
|
@@ -64,7 +70,8 @@ module Fastlane
|
|
64
70
|
description: "The build number. Defaults to the result of increment_build_number if you\'re using it",
|
65
71
|
default_value: Actions.lane_context[Actions::SharedValues::BUILD_NUMBER],
|
66
72
|
default_value_dynamic: true,
|
67
|
-
is_string: false
|
73
|
+
is_string: false,
|
74
|
+
optional: true),
|
68
75
|
FastlaneCore::ConfigItem.new(key: :message,
|
69
76
|
env_name: "FL_GIT_TAG_MESSAGE",
|
70
77
|
description: "The tag message. Defaults to the tag's name",
|
@@ -55,7 +55,7 @@ module Fastlane
|
|
55
55
|
|
56
56
|
def self.run(params)
|
57
57
|
unless Helper.test?
|
58
|
-
UI.message("Install using `brew install
|
58
|
+
UI.message("Install using `brew install appledoc`")
|
59
59
|
UI.user_error!("appledoc not installed") if `which appledoc`.length == 0
|
60
60
|
end
|
61
61
|
|
@@ -282,6 +282,10 @@ launch_arguments([
|
|
282
282
|
])
|
283
283
|
```
|
284
284
|
|
285
|
+
## Xcode Environment Variables
|
286
|
+
|
287
|
+
_snapshot_ includes `FASTLANE_SNAPSHOT=YES` and `FASTLANE_LANGUAGE=<language>` as arguments when executing `xcodebuild`. This means you may use these environment variables in a custom build phase run script to do any additional configuration.
|
288
|
+
|
285
289
|
# How does it work?
|
286
290
|
|
287
291
|
The easiest solution would be to just render the UIWindow into a file. That's not possible because UI Tests don't run on a main thread. So _snapshot_ uses a different approach:
|
@@ -237,7 +237,7 @@ The `keyword.strings` and `title.strings` are standard `.strings` file you alrea
|
|
237
237
|
**Notes**
|
238
238
|
|
239
239
|
- These `.strings` files **MUST** be utf-8 (UTF-8) or utf-16 encoded (UTF-16 BE with BOM). They also must begin with an empty line. If you are having trouble see [issue #1740](https://github.com/fastlane/fastlane/issues/1740)
|
240
|
-
- You **MUST** provide a background if you want titles. _frameit_ will not add the
|
240
|
+
- You **MUST** provide a background if you want titles. _frameit_ will not add the titles if a background is not specified.
|
241
241
|
|
242
242
|
### Screenshot orientation
|
243
243
|
|
@@ -390,7 +390,7 @@ lane :beta do
|
|
390
390
|
end
|
391
391
|
```
|
392
392
|
|
393
|
-
By using the `force_for_new_devices` parameter, _match_ will check if the device count has changed since the last time you ran _match_, and automatically re-generate the provisioning profile if necessary. You can also use `force: true` to re-generate the provisioning profile on each run.
|
393
|
+
By using the `force_for_new_devices` parameter, _match_ will check if the (enabled) device count has changed since the last time you ran _match_, and automatically re-generate the provisioning profile if necessary. You can also use `force: true` to re-generate the provisioning profile on each run.
|
394
394
|
|
395
395
|
_**Important:** The `force_for_new_devices` parameter is ignored for App Store provisioning profiles since they don't contain any device information._
|
396
396
|
|
@@ -439,6 +439,10 @@ Use the `submission_information` parameter for additional submission specifiers,
|
|
439
439
|
fastlane deliver submit_build --build_number 830 --submission_information "{\"export_compliance_uses_encryption\": false, \"add_id_info_uses_idfa\": false }"
|
440
440
|
```
|
441
441
|
|
442
|
+
### App Privacy Details
|
443
|
+
|
444
|
+
Starting on December 8, 2020, Apple announced that developers are required to provide app privacy details that will help users understand an app's privacy practies. _deliver_ does not allow for updating of this information but this can be done with the _upload_app_privacy_details_to_app_store_ action. More information on [Uploading App Privacy Details](https://docs.fastlane.tools/uploading-app-privacy-details)
|
445
|
+
|
442
446
|
# Credentials
|
443
447
|
|
444
448
|
A detailed description about how your credentials are handled is available in a [credentials_manager](https://github.com/fastlane/fastlane/tree/master/credentials_manager).
|
@@ -17,6 +17,7 @@ module Fastlane
|
|
17
17
|
apns_p12_password = params[:apns_p12_password]
|
18
18
|
android_token = params[:android_token]
|
19
19
|
android_gcm_sender_id = params[:android_gcm_sender_id]
|
20
|
+
organization_id = params[:organization_id]
|
20
21
|
|
21
22
|
has_app_id = !app_id.empty?
|
22
23
|
has_app_name = !app_name.empty?
|
@@ -43,6 +44,7 @@ module Fastlane
|
|
43
44
|
|
44
45
|
payload["gcm_key"] = android_token unless android_token.nil?
|
45
46
|
payload["android_gcm_sender_id"] = android_gcm_sender_id unless android_gcm_sender_id.nil?
|
47
|
+
payload["organization_id"] = organization_id unless organization_id.nil?
|
46
48
|
|
47
49
|
# here's the actual lifting - POST or PUT to OneSignal
|
48
50
|
|
@@ -135,7 +137,13 @@ module Fastlane
|
|
135
137
|
env_name: "APNS_ENV",
|
136
138
|
description: "APNS environment",
|
137
139
|
optional: true,
|
138
|
-
default_value: 'production')
|
140
|
+
default_value: 'production'),
|
141
|
+
|
142
|
+
FastlaneCore::ConfigItem.new(key: :organization_id,
|
143
|
+
env_name: "ONE_SIGNAL_ORGANIZATION_ID",
|
144
|
+
sensitive: true,
|
145
|
+
description: "OneSignal Organization ID",
|
146
|
+
optional: true)
|
139
147
|
]
|
140
148
|
end
|
141
149
|
|
@@ -163,7 +171,8 @@ module Fastlane
|
|
163
171
|
android_gcm_sender_id: "Your Android GCM Sender ID (optional)",
|
164
172
|
apns_p12: "Path to Apple .p12 file (optional)",
|
165
173
|
apns_p12_password: "Password for .p12 file (optional)",
|
166
|
-
apns_env: "production/sandbox (defaults to production)"
|
174
|
+
apns_env: "production/sandbox (defaults to production)",
|
175
|
+
organization_id: "Onesignal organization id (optional)"
|
167
176
|
)',
|
168
177
|
'onesignal(
|
169
178
|
app_id: "Your OneSignal App ID",
|
@@ -173,7 +182,8 @@ module Fastlane
|
|
173
182
|
android_gcm_sender_id: "Your Android GCM Sender ID (optional)",
|
174
183
|
apns_p12: "Path to Apple .p12 file (optional)",
|
175
184
|
apns_p12_password: "Password for .p12 file (optional)",
|
176
|
-
apns_env: "production/sandbox (defaults to production)"
|
185
|
+
apns_env: "production/sandbox (defaults to production)",
|
186
|
+
organization_id: "Onesignal organization id (optional)"
|
177
187
|
)'
|
178
188
|
]
|
179
189
|
end
|
@@ -272,8 +272,8 @@ module Fastlane
|
|
272
272
|
FastlaneCore::ConfigItem.new(key: :binary_basename,
|
273
273
|
env_name: "FL_SLATHER_BINARY_BASENAME",
|
274
274
|
description: "Basename of the binary file, this should match the name of your bundle excluding its extension (i.e. YourApp [for YourApp.app bundle])",
|
275
|
-
|
276
|
-
|
275
|
+
type: Array,
|
276
|
+
optional: true),
|
277
277
|
FastlaneCore::ConfigItem.new(key: :binary_file,
|
278
278
|
env_name: "FL_SLATHER_BINARY_FILE",
|
279
279
|
description: "Binary file name to be used for code coverage",
|
@@ -11,6 +11,7 @@ module Fastlane
|
|
11
11
|
cmd << "--disable-sandbox" if params[:disable_sandbox]
|
12
12
|
cmd << "--verbose" if params[:verbose]
|
13
13
|
cmd << params[:command] if package_commands.include?(params[:command])
|
14
|
+
cmd << "--enable-code-coverage" if params[:enable_code_coverage] && params[:command] == 'generate-xcodeproj'
|
14
15
|
if params[:xcconfig]
|
15
16
|
cmd << "--xcconfig-overrides #{params[:xcconfig]}"
|
16
17
|
end
|
@@ -44,6 +45,11 @@ module Fastlane
|
|
44
45
|
verify_block: proc do |value|
|
45
46
|
UI.user_error!("Please pass a valid command. Use one of the following: #{available_commands.join(', ')}") unless available_commands.include?(value)
|
46
47
|
end),
|
48
|
+
FastlaneCore::ConfigItem.new(key: :enable_code_coverage,
|
49
|
+
env_name: "FL_SPM_ENABLE_CODE_COVERAGE",
|
50
|
+
description: "Enables code coverage for the generated Xcode project when using the generate-xcodeproj command",
|
51
|
+
is_string: false,
|
52
|
+
optional: true),
|
47
53
|
FastlaneCore::ConfigItem.new(key: :build_path,
|
48
54
|
env_name: "FL_SPM_BUILD_PATH",
|
49
55
|
description: "Specify build/cache directory [default: ./.build]",
|
@@ -45,25 +45,36 @@ module Fastlane
|
|
45
45
|
end
|
46
46
|
|
47
47
|
# suppress updater output - very noisy
|
48
|
-
Gem::DefaultUserInteraction.ui = Gem::SilentUI.new
|
48
|
+
Gem::DefaultUserInteraction.ui = Gem::SilentUI.new unless FastlaneCore::Globals.verbose?
|
49
49
|
|
50
50
|
update_needed.each do |tool_info|
|
51
|
-
tool = tool_info
|
51
|
+
tool = self.get_gem_name(tool_info)
|
52
52
|
local_version = Gem::Version.new(highest_versions[tool].version)
|
53
53
|
latest_official_version = FastlaneCore::UpdateChecker.fetch_latest(tool)
|
54
54
|
|
55
55
|
UI.message("Updating #{tool} from #{local_version.to_s.yellow} to #{latest_official_version.to_s.yellow}... 🚀")
|
56
56
|
|
57
|
-
|
58
|
-
|
59
|
-
|
57
|
+
if Helper.homebrew?
|
58
|
+
Helper.backticks('brew upgrade fastlane')
|
59
|
+
else
|
60
|
+
# Approximate_recommendation will create a string like "~> 0.10" from a version 0.10.0, e.g. one that is valid for versions >= 0.10 and <1.0
|
61
|
+
requirement_version = local_version.approximate_recommendation
|
62
|
+
updater.update_gem(tool, Gem::Requirement.new(requirement_version))
|
63
|
+
end
|
60
64
|
|
61
65
|
UI.success("Finished updating #{tool}")
|
62
66
|
end
|
63
67
|
|
64
|
-
|
65
|
-
|
66
|
-
|
68
|
+
unless Helper.homebrew?
|
69
|
+
UI.message("Cleaning up old versions...")
|
70
|
+
cleaner.options[:args] = tools_to_update
|
71
|
+
cleaner.execute
|
72
|
+
end
|
73
|
+
|
74
|
+
if FastlaneCore::FastlaneFolder.swift?
|
75
|
+
upgrader = SwiftRunnerUpgrader.new
|
76
|
+
upgrader.upgrade_if_needed!
|
77
|
+
end
|
67
78
|
|
68
79
|
UI.message("fastlane.tools successfully updated! I will now restart myself... 😴")
|
69
80
|
|
@@ -71,6 +82,16 @@ module Fastlane
|
|
71
82
|
exec("FL_NO_UPDATE=true #{$PROGRAM_NAME} #{ARGV.join(' ')}")
|
72
83
|
end
|
73
84
|
|
85
|
+
def self.get_gem_name(tool_info)
|
86
|
+
if tool_info.kind_of?(Array)
|
87
|
+
return tool_info[0]
|
88
|
+
elsif tool_info.respond_to?(:name) # Gem::NameTuple in RubyGems >= 3.1.0
|
89
|
+
return tool_info.name
|
90
|
+
else
|
91
|
+
UI.crash!("Unknown gem update information returned from RubyGems. Please file a new issue for this... 🤷")
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
74
95
|
def self.description
|
75
96
|
"Makes sure fastlane-tools are up-to-date when running fastlane"
|
76
97
|
end
|
@@ -0,0 +1,289 @@
|
|
1
|
+
module Fastlane
|
2
|
+
module Actions
|
3
|
+
class UploadAppPrivacyDetailsToAppStoreAction < Action
|
4
|
+
DEFAULT_PATH = Fastlane::Helper.fastlane_enabled_folder_path
|
5
|
+
DEFAULT_FILE_NAME = "app_privacy_details.json"
|
6
|
+
|
7
|
+
def self.run(params)
|
8
|
+
require 'spaceship'
|
9
|
+
|
10
|
+
# Prompts select team if multiple teams and none specified
|
11
|
+
UI.message("Login to App Store Connect (#{params[:username]})")
|
12
|
+
Spaceship::ConnectAPI.login(params[:username], use_portal: false, use_tunes: true, tunes_team_id: params[:team_id], team_name: params[:team_name])
|
13
|
+
UI.message("Login successful")
|
14
|
+
|
15
|
+
# Get App
|
16
|
+
app = Spaceship::ConnectAPI::App.find(params[:app_identifier])
|
17
|
+
unless app
|
18
|
+
UI.user_error!("Could not find app with bundle identifier '#{params[:app_identifier]}' on account #{params[:username]}")
|
19
|
+
end
|
20
|
+
|
21
|
+
# Attempt to load JSON file
|
22
|
+
usages_config = load_json_file(params)
|
23
|
+
|
24
|
+
# Start interactive questions to generate and save JSON file
|
25
|
+
unless usages_config
|
26
|
+
usages_config = ask_interactive_questions_for_json
|
27
|
+
|
28
|
+
if params[:skip_json_file_saving]
|
29
|
+
UI.message("Skipping JSON file saving...")
|
30
|
+
else
|
31
|
+
json = JSON.pretty_generate(usages_config)
|
32
|
+
path = output_path(params)
|
33
|
+
|
34
|
+
UI.message("Writing file to #{path}")
|
35
|
+
File.write(path, json)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Process JSON file to save app data usages to API
|
40
|
+
if params[:skip_upload]
|
41
|
+
UI.message("Skipping uploading of data... (so you can verify your JSON file)")
|
42
|
+
else
|
43
|
+
upload_app_data_usages(params, app, usages_config)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.load_json_file(params)
|
48
|
+
path = params[:json_path]
|
49
|
+
return nil if path.nil?
|
50
|
+
return JSON.parse(File.read(path))
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.output_path(params)
|
54
|
+
path = params[:output_json_path]
|
55
|
+
return File.absolute_path(path)
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.ask_interactive_questions_for_json(show_intro = true)
|
59
|
+
if show_intro
|
60
|
+
UI.important("You did not provide a JSON file for updating the app data usages")
|
61
|
+
UI.important("fastlane will now run you through interactive question to generate the JSON file")
|
62
|
+
UI.important("")
|
63
|
+
UI.important("This JSON file can be saved in source control and used in this action with the :json_file option")
|
64
|
+
|
65
|
+
unless UI.confirm("Ready to start?")
|
66
|
+
UI.user_error!("Cancelled")
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Fetch categories and purposes used for generating interactive questions
|
71
|
+
categories = Spaceship::ConnectAPI::AppDataUsageCategory.all(includes: "grouping")
|
72
|
+
purposes = Spaceship::ConnectAPI::AppDataUsagePurpose.all
|
73
|
+
|
74
|
+
json = []
|
75
|
+
|
76
|
+
unless UI.confirm("Are you collecting data?")
|
77
|
+
json << {
|
78
|
+
"data_protections" => [Spaceship::ConnectAPI::AppDataUsageDataProtection::ID::DATA_NOT_COLLECTED]
|
79
|
+
}
|
80
|
+
|
81
|
+
return json
|
82
|
+
end
|
83
|
+
|
84
|
+
categories.each do |category|
|
85
|
+
# Ask if using category
|
86
|
+
next unless UI.confirm("Collect data for #{category.id}?")
|
87
|
+
|
88
|
+
purpose_names = purposes.map(&:id).join(', ')
|
89
|
+
UI.message("How will this data be used? You'll be offered with #{purpose_names}")
|
90
|
+
|
91
|
+
# Ask purposes
|
92
|
+
selected_purposes = []
|
93
|
+
loop do
|
94
|
+
purposes.each do |purpose|
|
95
|
+
selected_purposes << purpose if UI.confirm("Used for #{purpose.id}?")
|
96
|
+
end
|
97
|
+
|
98
|
+
break unless selected_purposes.empty?
|
99
|
+
break unless UI.confirm("No purposes selected. Do you want to try again?")
|
100
|
+
end
|
101
|
+
|
102
|
+
# Skip asking protections if purposes were skipped
|
103
|
+
next if selected_purposes.empty?
|
104
|
+
|
105
|
+
# Ask protections
|
106
|
+
is_linked_to_user = UI.confirm("Is #{category.id} linked to the user?")
|
107
|
+
is_used_for_tracking = UI.confirm("Is #{category.id} used for tracking purposes?")
|
108
|
+
|
109
|
+
# Map answers to values for API requests
|
110
|
+
protection_id = is_linked_to_user ? Spaceship::ConnectAPI::AppDataUsageDataProtection::ID::DATA_LINKED_TO_YOU : Spaceship::ConnectAPI::AppDataUsageDataProtection::ID::DATA_NOT_LINKED_TO_YOU
|
111
|
+
tracking_id = is_used_for_tracking ? Spaceship::ConnectAPI::AppDataUsageDataProtection::ID::DATA_USED_TO_TRACK_YOU : nil
|
112
|
+
|
113
|
+
json << {
|
114
|
+
"category" => category.id,
|
115
|
+
"purposes" => selected_purposes.map(&:id),
|
116
|
+
"data_protections" => [
|
117
|
+
protection_id, tracking_id
|
118
|
+
].compact
|
119
|
+
}
|
120
|
+
end
|
121
|
+
|
122
|
+
# Recursively call this method if no categories were selected for data collection
|
123
|
+
if json.empty?
|
124
|
+
UI.error("No categories were selected for data collection.")
|
125
|
+
json = ask_interactive_questions_for_json(false)
|
126
|
+
end
|
127
|
+
|
128
|
+
return json
|
129
|
+
end
|
130
|
+
|
131
|
+
def self.upload_app_data_usages(params, app, usages_config)
|
132
|
+
UI.message("Preparing to upload App Data Usage")
|
133
|
+
|
134
|
+
# Delete all existing usages for new ones
|
135
|
+
all_usages = Spaceship::ConnectAPI::AppDataUsage.all(app_id: app.id, includes: "category,grouping,purpose,dataProtection", limit: 500)
|
136
|
+
all_usages.each(&:delete!)
|
137
|
+
|
138
|
+
usages_config.each do |usage_config|
|
139
|
+
category = usage_config["category"]
|
140
|
+
purposes = usage_config["purposes"] || []
|
141
|
+
data_protections = usage_config["data_protections"] || []
|
142
|
+
|
143
|
+
# There will not be any purposes if "not collecting data"
|
144
|
+
# However, an AppDataUsage still needs to be created for not collecting data
|
145
|
+
# Creating an array with nil so that purposes can be iterated over and
|
146
|
+
# that AppDataUsage can be created
|
147
|
+
purposes = [nil] if purposes.empty?
|
148
|
+
|
149
|
+
purposes.each do |purpose|
|
150
|
+
data_protections.each do |data_protection|
|
151
|
+
if data_protection == Spaceship::ConnectAPI::AppDataUsageDataProtection::ID::DATA_NOT_COLLECTED
|
152
|
+
UI.message("Setting #{data_protection}")
|
153
|
+
else
|
154
|
+
UI.message("Setting #{category} and #{purpose} to #{data_protection}")
|
155
|
+
end
|
156
|
+
|
157
|
+
Spaceship::ConnectAPI::AppDataUsage.create(
|
158
|
+
app_id: app.id,
|
159
|
+
app_data_usage_category_id: category,
|
160
|
+
app_data_usage_protection_id: data_protection,
|
161
|
+
app_data_usage_purpose_id: purpose
|
162
|
+
)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
# Publish
|
168
|
+
if params[:skip_publish]
|
169
|
+
UI.message("Skipping app data usage publishing... (so you can verify on App Store Connect)")
|
170
|
+
else
|
171
|
+
publish_state = Spaceship::ConnectAPI::AppDataUsagesPublishState.get(app_id: app.id)
|
172
|
+
if publish_state.published
|
173
|
+
UI.important("App data usage is already published")
|
174
|
+
else
|
175
|
+
UI.important("App data usage not published! Going to publish...")
|
176
|
+
publish_state.publish!
|
177
|
+
UI.important("App data usage is now published")
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def self.description
|
183
|
+
"Upload App Privacy Details for an app in App Store Connect"
|
184
|
+
end
|
185
|
+
|
186
|
+
def self.available_options
|
187
|
+
user = CredentialsManager::AppfileConfig.try_fetch_value(:itunes_connect_id)
|
188
|
+
user ||= CredentialsManager::AppfileConfig.try_fetch_value(:apple_id)
|
189
|
+
|
190
|
+
[
|
191
|
+
FastlaneCore::ConfigItem.new(key: :username,
|
192
|
+
env_name: "FASTLANE_USER",
|
193
|
+
description: "Your Apple ID Username for App Store Connect",
|
194
|
+
default_value: user,
|
195
|
+
default_value_dynamic: true),
|
196
|
+
FastlaneCore::ConfigItem.new(key: :app_identifier,
|
197
|
+
env_name: "UPLOAD_APP_PRIVACY_DETAILS_TO_APP_STORE_APP_IDENTIFIER",
|
198
|
+
description: "The bundle identifier of your app",
|
199
|
+
code_gen_sensitive: true,
|
200
|
+
default_value: CredentialsManager::AppfileConfig.try_fetch_value(:app_identifier),
|
201
|
+
default_value_dynamic: true),
|
202
|
+
FastlaneCore::ConfigItem.new(key: :team_id,
|
203
|
+
env_name: "FASTLANE_ITC_TEAM_ID",
|
204
|
+
description: "The ID of your App Store Connect team if you're in multiple teams",
|
205
|
+
optional: true,
|
206
|
+
is_string: false, # as we also allow integers, which we convert to strings anyway
|
207
|
+
code_gen_sensitive: true,
|
208
|
+
default_value: CredentialsManager::AppfileConfig.try_fetch_value(:itc_team_id),
|
209
|
+
default_value_dynamic: true),
|
210
|
+
FastlaneCore::ConfigItem.new(key: :team_name,
|
211
|
+
env_name: "FASTLANE_ITC_TEAM_NAME",
|
212
|
+
description: "The name of your App Store Connect team if you're in multiple teams",
|
213
|
+
optional: true,
|
214
|
+
code_gen_sensitive: true,
|
215
|
+
default_value: CredentialsManager::AppfileConfig.try_fetch_value(:itc_team_name),
|
216
|
+
default_value_dynamic: true),
|
217
|
+
|
218
|
+
# JSON paths
|
219
|
+
FastlaneCore::ConfigItem.new(key: :json_path,
|
220
|
+
env_name: "UPLOAD_APP_PRIVACY_DETAILS_TO_APP_STORE_JSON_PATH",
|
221
|
+
description: "Path to the app usage data JSON",
|
222
|
+
is_string: true,
|
223
|
+
optional: true,
|
224
|
+
verify_block: proc do |value|
|
225
|
+
UI.user_error!("Could not find JSON file at path '#{File.expand_path(value)}'") unless File.exist?(value)
|
226
|
+
UI.user_error!("'#{value}' doesn't seem to be a JSON file") unless FastlaneCore::Helper.json_file?(File.expand_path(value))
|
227
|
+
end),
|
228
|
+
FastlaneCore::ConfigItem.new(key: :output_json_path,
|
229
|
+
env_name: "UPLOAD_APP_PRIVACY_DETAILS_TO_APP_STORE_OUTPUT_JSON_PATH",
|
230
|
+
description: "Path to the app usage data JSON file generated by interactive questions",
|
231
|
+
conflicting_options: [:skip_json_file_saving],
|
232
|
+
default_value: File.join(DEFAULT_PATH, DEFAULT_FILE_NAME)),
|
233
|
+
|
234
|
+
# Skipping options
|
235
|
+
FastlaneCore::ConfigItem.new(key: :skip_json_file_saving,
|
236
|
+
env_name: "UPLOAD_APP_PRIVACY_DETAILS_TO_APP_STORE_OUTPUT_SKIP_JSON_FILE_SAVING",
|
237
|
+
description: "Whether to skip the saving of the JSON file",
|
238
|
+
conflicting_options: [:skip_output_json_path],
|
239
|
+
type: Boolean,
|
240
|
+
default_value: false),
|
241
|
+
FastlaneCore::ConfigItem.new(key: :skip_upload,
|
242
|
+
env_name: "UPLOAD_APP_PRIVACY_DETAILS_TO_APP_STORE_OUTPUT_SKIP_UPLOAD",
|
243
|
+
description: "Whether to skip the upload and only create the JSON file with interactive questions",
|
244
|
+
conflicting_options: [:skip_publish],
|
245
|
+
type: Boolean,
|
246
|
+
default_value: false),
|
247
|
+
FastlaneCore::ConfigItem.new(key: :skip_publish,
|
248
|
+
env_name: "UPLOAD_APP_PRIVACY_DETAILS_TO_APP_STORE_OUTPUT_SKIP_PUBLISH",
|
249
|
+
description: "Whether to skip the publishing",
|
250
|
+
conflicting_options: [:skip_upload],
|
251
|
+
type: Boolean,
|
252
|
+
default_value: false)
|
253
|
+
]
|
254
|
+
end
|
255
|
+
|
256
|
+
def self.author
|
257
|
+
"joshdholtz"
|
258
|
+
end
|
259
|
+
|
260
|
+
def self.is_supported?(platform)
|
261
|
+
[:ios, :mac, :tvos].include?(platform)
|
262
|
+
end
|
263
|
+
|
264
|
+
def self.details
|
265
|
+
"Upload App Privacy Details for an app in App Store Connect. For more detail information, view https://docs.fastlane.tools/uploading-app-privacy-details"
|
266
|
+
end
|
267
|
+
|
268
|
+
def self.example_code
|
269
|
+
[
|
270
|
+
'upload_app_privacy_details_to_app_store(
|
271
|
+
username: "your@email.com",
|
272
|
+
team_name: "Your Team",
|
273
|
+
app_identifier: "com.your.bundle"
|
274
|
+
)',
|
275
|
+
'upload_app_privacy_details_to_app_store(
|
276
|
+
username: "your@email.com",
|
277
|
+
team_name: "Your Team",
|
278
|
+
app_identifier: "com.your.bundle",
|
279
|
+
json_path: "fastlane/app_data_usages.json"
|
280
|
+
)'
|
281
|
+
]
|
282
|
+
end
|
283
|
+
|
284
|
+
def self.category
|
285
|
+
:production
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|