fastlane 2.196.0 → 2.199.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +94 -94
  3. data/cert/lib/cert/runner.rb +5 -2
  4. data/deliver/lib/assets/ScreenshotsHelp +29 -6
  5. data/deliver/lib/deliver/app_screenshot.rb +5 -4
  6. data/deliver/lib/deliver/app_screenshot_iterator.rb +1 -1
  7. data/deliver/lib/deliver/runner.rb +1 -1
  8. data/deliver/lib/deliver/upload_screenshots.rb +1 -1
  9. data/fastlane/lib/.DS_Store +0 -0
  10. data/fastlane/lib/fastlane/.DS_Store +0 -0
  11. data/fastlane/lib/fastlane/actions/.DS_Store +0 -0
  12. data/fastlane/lib/fastlane/actions/docs/upload_to_app_store.md.erb +1 -1
  13. data/fastlane/lib/fastlane/actions/download_dsyms.rb +61 -45
  14. data/fastlane/lib/fastlane/actions/ensure_xcode_version.rb +1 -1
  15. data/fastlane/lib/fastlane/actions/get_push_certificate.rb +1 -1
  16. data/fastlane/lib/fastlane/actions/get_version_number.rb +6 -2
  17. data/fastlane/lib/fastlane/actions/notarize.rb +29 -11
  18. data/fastlane/lib/fastlane/actions/set_github_release.rb +11 -5
  19. data/fastlane/lib/fastlane/actions/update_code_signing_settings.rb +18 -1
  20. data/fastlane/lib/fastlane/actions/xcversion.rb +18 -3
  21. data/fastlane/lib/fastlane/documentation/docs_generator.rb +17 -12
  22. data/fastlane/lib/fastlane/version.rb +1 -1
  23. data/fastlane/swift/Deliverfile.swift +1 -1
  24. data/fastlane/swift/DeliverfileProtocol.swift +1 -1
  25. data/fastlane/swift/Fastlane.swift +66 -21
  26. data/fastlane/swift/Gymfile.swift +1 -1
  27. data/fastlane/swift/GymfileProtocol.swift +5 -1
  28. data/fastlane/swift/Matchfile.swift +1 -1
  29. data/fastlane/swift/MatchfileProtocol.swift +1 -1
  30. data/fastlane/swift/Precheckfile.swift +1 -1
  31. data/fastlane/swift/PrecheckfileProtocol.swift +1 -1
  32. data/fastlane/swift/Runner.swift +3 -7
  33. data/fastlane/swift/Scanfile.swift +1 -1
  34. data/fastlane/swift/ScanfileProtocol.swift +3 -3
  35. data/fastlane/swift/Screengrabfile.swift +1 -1
  36. data/fastlane/swift/ScreengrabfileProtocol.swift +1 -1
  37. data/fastlane/swift/Snapshotfile.swift +1 -1
  38. data/fastlane/swift/SnapshotfileProtocol.swift +1 -1
  39. data/fastlane/swift/formatting/Brewfile.lock.json +26 -21
  40. data/fastlane_core/lib/fastlane_core/itunes_transporter.rb +38 -7
  41. data/frameit/lib/frameit/editor.rb +16 -18
  42. data/frameit/lib/frameit/trim_box.rb +6 -0
  43. data/gym/lib/gym/generators/build_command_generator.rb +1 -1
  44. data/gym/lib/gym/options.rb +6 -0
  45. data/match/lib/match/nuke.rb +79 -1
  46. data/match/lib/match/spaceship_ensure.rb +1 -0
  47. data/pem/lib/pem/manager.rb +29 -6
  48. data/pem/lib/pem/options.rb +9 -0
  49. data/pilot/lib/pilot/build_manager.rb +1 -1
  50. data/scan/lib/scan/options.rb +2 -2
  51. data/scan/lib/scan/runner.rb +1 -1
  52. data/snapshot/lib/snapshot/simulator_launchers/simulator_launcher_base.rb +1 -1
  53. data/spaceship/lib/spaceship/client.rb +35 -15
  54. data/spaceship/lib/spaceship/commands_generator.rb +1 -1
  55. data/spaceship/lib/spaceship/connect_api/models/build.rb +4 -2
  56. data/spaceship/lib/spaceship/connect_api/models/build_bundle.rb +59 -0
  57. data/spaceship/lib/spaceship/connect_api/models/certificate.rb +3 -0
  58. data/spaceship/lib/spaceship/connect_api/response.rb +13 -0
  59. data/spaceship/lib/spaceship/connect_api.rb +1 -0
  60. data/spaceship/lib/spaceship/globals.rb +9 -0
  61. data/spaceship/lib/spaceship/spaceauth_runner.rb +1 -1
  62. data/trainer/lib/.DS_Store +0 -0
  63. metadata +24 -18
@@ -1,4 +1,5 @@
1
1
  require 'shellwords'
2
+ require 'tmpdir'
2
3
  require 'fileutils'
3
4
  require 'credentials_manager/account_manager'
4
5
 
@@ -148,6 +149,17 @@ module FastlaneCore
148
149
  end
149
150
  end
150
151
 
152
+ def file_upload_option(source)
153
+ ext = File.extname(source).downcase
154
+ is_asset_file_type = !File.directory?(source) && [".ipa", ".pkg", ".dmg", ".zip"].include?(ext)
155
+
156
+ if is_asset_file_type
157
+ return "-assetFile #{source.shellescape}"
158
+ else
159
+ return "-f #{source.shellescape}"
160
+ end
161
+ end
162
+
151
163
  def additional_upload_parameters
152
164
  # As Apple recommends in Transporter User Guide we shouldn't specify the -t transport parameter
153
165
  # and instead allow Transporter to use automatic transport discovery
@@ -177,7 +189,7 @@ module FastlaneCore
177
189
  ("-u #{username.shellescape}" unless use_jwt),
178
190
  ("-p #{shell_escaped_password(password)}" unless use_jwt),
179
191
  ("-jwt #{jwt}" if use_jwt),
180
- "-f \"#{source}\"",
192
+ file_upload_option(source),
181
193
  additional_upload_parameters, # that's here, because the user might overwrite the -t option
182
194
  "-k 100000",
183
195
  ("-WONoPause true" if Helper.windows?), # Windows only: process instantly returns instead of waiting for key press
@@ -261,7 +273,7 @@ module FastlaneCore
261
273
  ("-u #{username.shellescape}" unless use_jwt),
262
274
  ("-p @env:ITMS_TRANSPORTER_PASSWORD" unless use_jwt),
263
275
  ("-jwt #{jwt}" if use_jwt),
264
- "-f #{source.shellescape}",
276
+ file_upload_option(source),
265
277
  additional_upload_parameters, # that's here, because the user might overwrite the -t option
266
278
  '-k 100000',
267
279
  ("-itc_provider #{provider_short_name}" if jwt.nil? && !provider_short_name.to_s.empty?),
@@ -282,7 +294,7 @@ module FastlaneCore
282
294
  ("-u #{username.shellescape}" unless use_jwt),
283
295
  ("-p #{password.shellescape}" unless use_jwt),
284
296
  ("-jwt #{jwt}" if use_jwt),
285
- "-f #{source.shellescape}",
297
+ file_upload_option(source),
286
298
  additional_upload_parameters, # that's here, because the user might overwrite the -t option
287
299
  '-k 100000',
288
300
  ("-itc_provider #{provider_short_name}" if jwt.nil? && !provider_short_name.to_s.empty?),
@@ -472,12 +484,31 @@ module FastlaneCore
472
484
  # @param app_id [Integer] The unique App ID
473
485
  # @param dir [String] the path in which the package file is located
474
486
  # @param package_path [String] the path to the package file (used instead of app_id and dir)
487
+ # @param asset_path [String] the path to the ipa/dmg/pkg file (used instead of package_path if running on macOS)
475
488
  # @return (Bool) True if everything worked fine
476
489
  # @raise [Deliver::TransporterTransferError] when something went wrong
477
490
  # when transferring
478
- def upload(app_id = nil, dir = nil, package_path: nil)
479
- raise "app_id and dir are required or package_path is required" if (app_id.nil? || dir.nil?) && package_path.nil?
480
- actual_dir = package_path || File.join(dir, "#{app_id}.itmsp")
491
+ def upload(app_id = nil, dir = nil, package_path: nil, asset_path: nil)
492
+ raise "app_id and dir are required or package_path or asset_path is required" if (app_id.nil? || dir.nil?) && package_path.nil? && asset_path.nil?
493
+
494
+ # Transport can upload .ipa, .dmg, and .pkg files directly with -assetFile
495
+ # However, -assetFile requires -assetDescription if Linux or Windows
496
+ # This will give the asset directly if macOS and asset_path exists
497
+ # otherwise it will use the .itmsp package
498
+
499
+ force_itmsp = FastlaneCore::Env.truthy?("ITMSTRANSPORTER_FORCE_ITMS_PACKAGE_UPLOAD")
500
+ can_use_asset_path = Helper.is_mac? && asset_path
501
+
502
+ actual_dir = if can_use_asset_path && !force_itmsp
503
+ # The asset gets deleted upon completion so copying to a temp directory
504
+ tmp_asset_path = File.join(Dir.tmpdir, File.basename(asset_path))
505
+ FileUtils.cp(asset_path, tmp_asset_path)
506
+ tmp_asset_path
507
+ elsif package_path
508
+ package_path
509
+ else
510
+ File.join(dir, "#{app_id}.itmsp")
511
+ end
481
512
 
482
513
  UI.message("Going to upload updated app to App Store Connect")
483
514
  UI.success("This might take a few minutes. Please don't interrupt the script.")
@@ -492,7 +523,7 @@ module FastlaneCore
492
523
  result = @transporter_executor.execute(command, ItunesTransporter.hide_transporter_output?)
493
524
  rescue TransporterRequiresApplicationSpecificPasswordError => ex
494
525
  handle_two_step_failure(ex)
495
- return upload(app_id, dir, package_path: package_path)
526
+ return upload(app_id, dir, package_path: package_path, asset_path: asset_path)
496
527
  end
497
528
 
498
529
  if result
@@ -467,26 +467,24 @@ module Frameit
467
467
  # Get matching trim box:
468
468
  trim_box = trim_boxes[key]
469
469
 
470
- # For side-by-side text images (e.g. stack_title is false) adjust the trim box based on top_vertical_trim_offset and bottom_vertical_trim_offset to maintain the text baseline:
471
- unless stack_title
472
- # Determine the trim area by maintaining the same vertical top offset based on the smallest value from all trim boxes (top_vertical_trim_offset).
473
- # When the vertical top offset is larger than the smallest vertical top offset, the trim box needs to be adjusted:
474
- if trim_box.offset_y > top_vertical_trim_offset
475
- # Increase the height of the trim box with the difference in vertical top offset:
476
- trim_box.height += trim_box.offset_y - top_vertical_trim_offset
477
- # Change the vertical top offset to match that of the others:
478
- trim_box.offset_y = top_vertical_trim_offset
479
-
480
- UI.verbose("Trim box for key \"#{key}\" is adjusted to align top: #{trim_box}\n")
481
- end
470
+ # Adjust the trim box based on top_vertical_trim_offset and bottom_vertical_trim_offset to maintain the text baseline:
471
+ # Determine the trim area by maintaining the same vertical top offset based on the smallest value from all trim boxes (top_vertical_trim_offset).
472
+ # When the vertical top offset is larger than the smallest vertical top offset, the trim box needs to be adjusted:
473
+ if trim_box.offset_y > top_vertical_trim_offset
474
+ # Increase the height of the trim box with the difference in vertical top offset:
475
+ trim_box.height += trim_box.offset_y - top_vertical_trim_offset
476
+ # Change the vertical top offset to match that of the others:
477
+ trim_box.offset_y = top_vertical_trim_offset
478
+
479
+ UI.verbose("Trim box for key \"#{key}\" is adjusted to align top: #{trim_box.json_string_format}")
480
+ end
482
481
 
483
- # Check if the height needs to be adjusted to reach the bottom offset:
484
- if (trim_box.offset_y + trim_box.height) < bottom_vertical_trim_offset
485
- # Set the height of the trim box to the difference between vertical bottom and top offset:
486
- trim_box.height = bottom_vertical_trim_offset - trim_box.offset_y
482
+ # Check if the height needs to be adjusted to reach the bottom offset:
483
+ if (trim_box.offset_y + trim_box.height) < bottom_vertical_trim_offset
484
+ # Set the height of the trim box to the difference between vertical bottom and top offset:
485
+ trim_box.height = bottom_vertical_trim_offset - trim_box.offset_y
487
486
 
488
- UI.verbose("Trim box for key \"#{key}\" is adjusted to align bottom: #{trim_box}\n")
489
- end
487
+ UI.verbose("Trim box for key \"#{key}\" is adjusted to align bottom: #{trim_box.json_string_format}")
490
488
  end
491
489
 
492
490
  # Crop image with (adjusted) trim box parameters in MiniMagick string format:
@@ -31,5 +31,11 @@ module Frameit
31
31
  # Convert trim box parameters to string with syntax: "<width>x<height>+<offset_x>+<offset_y>":
32
32
  return "#{@width}x#{@height}+#{@offset_x}+#{@offset_y}"
33
33
  end
34
+
35
+ # Get the trimbox parameters in a human readable JSON string format
36
+ def json_string_format
37
+ # Create a JSON string from the trim box parameters:
38
+ return "{\"width\" : #{@width}, \"height\" : #{@height} , \"offset_x\" : #{@offset_x}, \"offset_y\" : #{@offset_y}}"
39
+ end
34
40
  end
35
41
  end
@@ -7,7 +7,7 @@ module Gym
7
7
  class << self
8
8
  def generate
9
9
  parts = prefix
10
- parts << "xcodebuild"
10
+ parts << Gym.config[:xcodebuild_command]
11
11
  parts += options
12
12
  parts += buildactions
13
13
  parts += setting
@@ -275,6 +275,12 @@ module Gym
275
275
  optional: true,
276
276
  type: Boolean,
277
277
  default_value: false),
278
+ FastlaneCore::ConfigItem.new(key: :xcodebuild_command,
279
+ env_name: "GYM_XCODE_BUILD_COMMAND",
280
+ description: "Allows for override of the default `xcodebuild` command",
281
+ type: String,
282
+ optional: true,
283
+ default_value: "xcodebuild"),
278
284
  FastlaneCore::ConfigItem.new(key: :cloned_source_packages_path,
279
285
  env_name: "GYM_CLONED_SOURCE_PACKAGES_PATH",
280
286
  description: "Sets a custom path for Swift Package Manager dependencies",
@@ -9,7 +9,11 @@ require_relative 'module'
9
9
  require_relative 'storage'
10
10
  require_relative 'encryption'
11
11
 
12
+ require 'tempfile'
13
+ require 'base64'
14
+
12
15
  module Match
16
+ # rubocop:disable Metrics/ClassLength
13
17
  class Nuke
14
18
  attr_accessor :params
15
19
  attr_accessor :type
@@ -67,6 +71,7 @@ module Match
67
71
  title: "Summary for match nuke #{Fastlane::VERSION}")
68
72
 
69
73
  prepare_list
74
+ filter_by_cert
70
75
  print_tables
71
76
 
72
77
  if params[:readonly]
@@ -139,7 +144,7 @@ module Match
139
144
  types = profile_types(prov_type)
140
145
  # Filtering on 'profileType' seems to be undocumented as of 2020-07-30
141
146
  # but works on both web session and official API
142
- self.profiles += Spaceship::ConnectAPI::Profile.all(filter: { profileType: types.join(",") })
147
+ self.profiles += Spaceship::ConnectAPI::Profile.all(filter: { profileType: types.join(",") }, includes: "certificates")
143
148
  end
144
149
 
145
150
  # Gets the main and additional cert types
@@ -173,6 +178,78 @@ module Match
173
178
  self.files = certs + keys + profiles
174
179
  end
175
180
 
181
+ def filter_by_cert
182
+ # Force will continue to revoke and delete all certificates and profiles
183
+ return if self.params[:force] || !UI.interactive?
184
+ return if self.certs.count < 2
185
+
186
+ # Print table showing certificates that can be revoked
187
+ puts("")
188
+ rows = self.certs.each_with_index.collect do |cert, i|
189
+ cert_expiration = cert.expiration_date.nil? ? "Unknown" : Time.parse(cert.expiration_date).strftime("%Y-%m-%d")
190
+ [i + 1, cert.name, cert.id, cert.class.to_s.split("::").last, cert_expiration]
191
+ end
192
+ puts(Terminal::Table.new({
193
+ title: "Certificates that can be revoked".green,
194
+ headings: ["Option", "Name", "ID", "Type", "Expires"],
195
+ rows: FastlaneCore::PrintTable.transform_output(rows)
196
+ }))
197
+ puts("")
198
+
199
+ UI.important("By default, all listed certificates and profiles will be nuked")
200
+ if UI.confirm("Do you want to only nuke specific certificates and their associated profiles?")
201
+ input_indexes = UI.input("Enter the \"Option\" number(s) from the table above? (comma-separated)").split(',')
202
+
203
+ # Get certificates from option indexes
204
+ self.certs = input_indexes.map do |index|
205
+ self.certs[index.to_i - 1]
206
+ end.compact
207
+
208
+ if self.certs.empty?
209
+ UI.user_error!("No certificates were selected based on option number(s) entered")
210
+ end
211
+
212
+ # Do profile selection logic
213
+ cert_ids = self.certs.map(&:id)
214
+ self.profiles = self.profiles.select do |profile|
215
+ profile_cert_ids = profile.certificates.map(&:id)
216
+ (cert_ids & profile_cert_ids).any?
217
+ end
218
+
219
+ # Do file selection logic
220
+ self.files = self.files.select do |f|
221
+ found = false
222
+
223
+ ext = File.extname(f)
224
+ filename = File.basename(f, ".*")
225
+
226
+ # Attempt to find cert based on filename
227
+ if ext == ".cer" || ext == ".p12"
228
+ found ||= self.certs.any? do |cert|
229
+ filename == cert.id.to_s
230
+ end
231
+ end
232
+
233
+ # Attempt to find profile matched on UUIDs in profile
234
+ if ext == ".mobileprovision" || ext == ".provisionprofile"
235
+ storage_uuid = FastlaneCore::ProvisioningProfile.uuid(f)
236
+
237
+ found ||= self.profiles.any? do |profile|
238
+ tmp_file = Tempfile.new
239
+ tmp_file.write(Base64.decode64(profile.profile_content))
240
+ tmp_file.close
241
+
242
+ # Compare profile uuid in storage to profile uuid on developer portal
243
+ portal_uuid = FastlaneCore::ProvisioningProfile.uuid(tmp_file.path)
244
+ storage_uuid == portal_uuid
245
+ end
246
+ end
247
+
248
+ found
249
+ end
250
+ end
251
+ end
252
+
176
253
  # Print tables to ask the user
177
254
  def print_tables
178
255
  puts("")
@@ -351,4 +428,5 @@ module Match
351
428
  end
352
429
  end
353
430
  end
431
+ # rubocop:disable Metrics/ClassLength
354
432
  end
@@ -70,6 +70,7 @@ module Match
70
70
  UI.error("for the user #{username}")
71
71
  UI.error("Make sure to use the same user and team every time you run 'match' for this")
72
72
  UI.error("Git repository. This might be caused by revoking the certificate on the Dev Portal")
73
+ UI.error("If missing certificate is a Developer ID Installer, you may need to auth with Apple ID instead of App Store API Key")
73
74
  UI.user_error!("To reset the certificates of your Apple account, you can use the `fastlane match nuke` feature, more information on https://docs.fastlane.tools/actions/match/")
74
75
  end
75
76
 
@@ -18,7 +18,13 @@ module PEM
18
18
 
19
19
  if existing_certificate
20
20
  remaining_days = (existing_certificate.expires - Time.now) / 60 / 60 / 24
21
- UI.message("Existing push notification profile for '#{existing_certificate.owner_name}' is valid for #{remaining_days.round} more days.")
21
+
22
+ display_platform = ''
23
+ unless PEM.config[:website_push]
24
+ display_platform = "#{PEM.config[:platform]} "
25
+ end
26
+
27
+ UI.message("Existing #{display_platform}push notification profile for '#{existing_certificate.owner_name}' is valid for #{remaining_days.round} more days.")
22
28
  if remaining_days > PEM.config[:active_days_limit]
23
29
  if PEM.config[:force]
24
30
  UI.success("You already have an existing push certificate, but a new one will be created since the --force option has been set.")
@@ -59,7 +65,7 @@ module PEM
59
65
 
60
66
  x509_certificate = cert.download
61
67
 
62
- filename_base = PEM.config[:pem_name] || "#{certificate_type}_#{PEM.config[:app_identifier]}"
68
+ filename_base = PEM.config[:pem_name] || "#{certificate_type}_#{PEM.config[:app_identifier]}_#{PEM.config[:platform]}"
63
69
  filename_base = File.basename(filename_base, ".pem") # strip off the .pem if it was provided.
64
70
 
65
71
  output_path = File.expand_path(PEM.config[:output_path])
@@ -86,12 +92,29 @@ module PEM
86
92
  end
87
93
 
88
94
  def certificate
89
- if PEM.config[:development]
90
- Spaceship.certificate.development_push
91
- elsif PEM.config[:website_push]
95
+ if PEM.config[:website_push]
92
96
  Spaceship.certificate.website_push
93
97
  else
94
- Spaceship.certificate.production_push
98
+ platform = PEM.config[:platform]
99
+ UI.user_error!('platform parameter is unspecified.') unless platform
100
+
101
+ case platform
102
+ when 'ios'
103
+ if PEM.config[:development]
104
+ Spaceship.certificate.development_push
105
+ else
106
+ Spaceship.certificate.production_push
107
+ end
108
+ when 'macos'
109
+ if PEM.config[:development]
110
+ Spaceship.certificate.mac_development_push
111
+ else
112
+ Spaceship.certificate.mac_production_push
113
+ end
114
+ else
115
+ UI.user_error!("Unsupported platform '#{platform}'. Supported platforms for development and production certificates are 'ios' & 'macos'")
116
+ end
117
+
95
118
  end
96
119
  end
97
120
 
@@ -1,5 +1,6 @@
1
1
  require 'fastlane_core/configuration/config_item'
2
2
  require 'credentials_manager/appfile_config'
3
+ require 'fastlane/helper/lane_helper'
3
4
 
4
5
  require_relative 'module'
5
6
 
@@ -10,6 +11,14 @@ module PEM
10
11
  user ||= CredentialsManager::AppfileConfig.try_fetch_value(:apple_id)
11
12
 
12
13
  [
14
+ FastlaneCore::ConfigItem.new(key: :platform,
15
+ description: "Set certificate's platform. Used for creation of production & development certificates. Supported platforms: ios, macos",
16
+ short_option: "-m",
17
+ env_name: "PEM_PLATFORM",
18
+ default_value: "ios",
19
+ verify_block: proc do |value|
20
+ UI.user_error!("The platform can only be ios or macos") unless ['ios', 'macos'].include?(value)
21
+ end),
13
22
  FastlaneCore::ConfigItem.new(key: :development,
14
23
  env_name: "PEM_DEVELOPMENT",
15
24
  description: "Renew the development push certificate instead of the production one",
@@ -38,7 +38,7 @@ module Pilot
38
38
  end
39
39
 
40
40
  transporter = transporter_for_selected_team(options)
41
- result = transporter.upload(package_path: package_path)
41
+ result = transporter.upload(package_path: package_path, asset_path: options[:ipa] || options[:pkg])
42
42
 
43
43
  unless result
44
44
  transporter_errors = transporter.displayable_errors
@@ -60,7 +60,7 @@ module Scan
60
60
  optional: true,
61
61
  is_string: true,
62
62
  env_name: "SCAN_DEVICE",
63
- description: "The name of the simulator type you want to run tests on (e.g. 'iPhone 6')",
63
+ description: "The name of the simulator type you want to run tests on (e.g. 'iPhone 6' or 'iPhone SE (2nd generation) (14.5)')",
64
64
  conflicting_options: [:devices],
65
65
  conflict_block: proc do |value|
66
66
  UI.user_error!("You can't use 'device' and 'devices' options in one run")
@@ -70,7 +70,7 @@ module Scan
70
70
  is_string: false,
71
71
  env_name: "SCAN_DEVICES",
72
72
  type: Array,
73
- description: "Array of devices to run the tests on (e.g. ['iPhone 6', 'iPad Air'])",
73
+ description: "Array of devices to run the tests on (e.g. ['iPhone 6', 'iPad Air', 'iPhone SE (2nd generation) (14.5)'])",
74
74
  conflicting_options: [:device],
75
75
  conflict_block: proc do |value|
76
76
  UI.user_error!("You can't use 'device' and 'devices' options in one run")
@@ -226,7 +226,7 @@ module Scan
226
226
  FileUtils.cp(xctestrun_file, output_path)
227
227
  UI.message("Successfully copied xctestrun file: #{output_path}")
228
228
  else
229
- UI.user_error!("Could not find .xctextrun file to copy")
229
+ UI.user_error!("Could not find .xctestrun file to copy")
230
230
  end
231
231
  end
232
232
 
@@ -123,7 +123,7 @@ module Snapshot
123
123
  # Simulator could stil be booting with Apple logo
124
124
  # Need to wait "some amount of time" until home screen shows
125
125
  boot_sleep = ENV["SNAPSHOT_SIMULATOR_WAIT_FOR_BOOT_TIMEOUT"].to_i || 10
126
- UI.message("Waiting #{boot_sleep} seconds for device to fully boot before overriding status bar... Set 'SNAPSHOT_SIMULATOR_WAIT_FOR_BOOT_TIMEOUT' environemnt variable to adjust timeout")
126
+ UI.message("Waiting #{boot_sleep} seconds for device to fully boot before overriding status bar... Set 'SNAPSHOT_SIMULATOR_WAIT_FOR_BOOT_TIMEOUT' environment variable to adjust timeout")
127
127
  sleep(boot_sleep) if boot_sleep > 0
128
128
 
129
129
  UI.message("Overriding Status Bar")
@@ -391,10 +391,11 @@ module Spaceship
391
391
 
392
392
  keychain_entry = CredentialsManager::AccountManager.new(user: user, password: password)
393
393
  user ||= keychain_entry.user
394
- password = keychain_entry.password
394
+ password = keychain_entry.password(ask_if_missing: !Spaceship::Globals.check_session)
395
395
  end
396
396
 
397
397
  if user.to_s.strip.empty? || password.to_s.strip.empty?
398
+ exit_with_session_state(user, false) if Spaceship::Globals.check_session
398
399
  raise NoUserCredentialsError.new, "No login data provided"
399
400
  end
400
401
 
@@ -413,21 +414,16 @@ module Spaceship
413
414
  end
414
415
  end
415
416
 
416
- # This method is used for both the Apple Dev Portal and App Store Connect
417
- # This will also handle 2 step verification and 2 factor authentication
417
+ # Check if we have a cached/valid session
418
418
  #
419
- # It is called in `send_login_request` of sub classes (which the method `login`, above, transferred over to via `do_login`)
420
- # rubocop:disable Metrics/PerceivedComplexity
421
- def send_shared_login_request(user, password)
422
- # Check if we have a cached/valid session
423
- #
424
- # Background:
425
- # December 4th 2017 Apple introduced a rate limit - which is of course fine by itself -
426
- # but unfortunately also rate limits successful logins. If you call multiple tools in a
427
- # lane (e.g. call match 5 times), this would lock you out of the account for a while.
428
- # By loading existing sessions and checking if they're valid, we're sending less login requests.
429
- # More context on why this change was necessary https://github.com/fastlane/fastlane/pull/11108
430
- #
419
+ # Background:
420
+ # December 4th 2017 Apple introduced a rate limit - which is of course fine by itself -
421
+ # but unfortunately also rate limits successful logins. If you call multiple tools in a
422
+ # lane (e.g. call match 5 times), this would lock you out of the account for a while.
423
+ # By loading existing sessions and checking if they're valid, we're sending less login requests.
424
+ # More context on why this change was necessary https://github.com/fastlane/fastlane/pull/11108
425
+ #
426
+ def has_valid_session
431
427
  # If there was a successful manual login before, we have a session on disk
432
428
  if load_session_from_file
433
429
  # Check if the session is still valid here
@@ -462,6 +458,23 @@ module Spaceship
462
458
  #
463
459
  # After this point, we sure have no valid session any more and have to create a new one
464
460
  #
461
+ return false
462
+ end
463
+
464
+ # This method is used for both the Apple Dev Portal and App Store Connect
465
+ # This will also handle 2 step verification and 2 factor authentication
466
+ #
467
+ # It is called in `send_login_request` of sub classes (which the method `login`, above, transferred over to via `do_login`)
468
+ # rubocop:disable Metrics/PerceivedComplexity
469
+ def send_shared_login_request(user, password)
470
+ # Check if the cache or FASTLANE_SESSION is still valid
471
+ has_valid_session = self.has_valid_session
472
+
473
+ # Exit if `--check_session` flag was passed
474
+ exit_with_session_state(user, has_valid_session) if Spaceship::Globals.check_session
475
+
476
+ # If the session is valid no need to attempt to generate a new one.
477
+ return true if has_valid_session
465
478
 
466
479
  data = {
467
480
  accountName: user,
@@ -566,6 +579,13 @@ module Spaceship
566
579
  return false
567
580
  end
568
581
 
582
+ # This method is used to log if the session is valid or not and then exit
583
+ # It is called when the `--check_session` flag is passed
584
+ def exit_with_session_state(user, has_valid_session)
585
+ puts("#{has_valid_session ? 'Valid' : 'No valid'} session found (#{user}). Exiting.")
586
+ exit(has_valid_session)
587
+ end
588
+
569
589
  def itc_service_key
570
590
  return @service_key if @service_key
571
591
 
@@ -41,7 +41,7 @@ module Spaceship
41
41
  c.syntax = 'fastlane spaceship spaceauth'
42
42
  c.description = 'Authentication helper for spaceship/fastlane to work with Apple 2-Step/2FA'
43
43
  c.option('--copy_to_clipboard', 'Whether the session string should be copied to clipboard. For more info see https://docs.fastlane.tools/best-practices/continuous-integration/#storing-a-manually-verified-session-using-spaceauth`')
44
-
44
+ c.option('--check_session', 'Check to see if there is a valid session (either in the cache or via FASTLANE_SESSION). Sets the exit code to 0 if the session is valid or 1 if not.') { Spaceship::Globals.check_session = true }
45
45
  c.action do |args, options|
46
46
  Spaceship::SpaceauthRunner.new(username: options.user, copy_to_clipboard: options.copy_to_clipboard).run
47
47
  end
@@ -19,6 +19,7 @@ module Spaceship
19
19
  attr_accessor :beta_build_metrics
20
20
  attr_accessor :beta_build_localizations
21
21
  attr_accessor :build_beta_detail
22
+ attr_accessor :build_bundles
22
23
  attr_accessor :pre_release_version
23
24
  attr_accessor :individual_testers
24
25
 
@@ -38,10 +39,11 @@ module Spaceship
38
39
  "betaBuildLocalizations" => "beta_build_localizations",
39
40
  "buildBetaDetail" => "build_beta_detail",
40
41
  "preReleaseVersion" => "pre_release_version",
41
- "individualTesters" => "individual_testers"
42
+ "individualTesters" => "individual_testers",
43
+ "buildBundles" => "build_bundles"
42
44
  })
43
45
 
44
- ESSENTIAL_INCLUDES = "app,buildBetaDetail,preReleaseVersion"
46
+ ESSENTIAL_INCLUDES = "app,buildBetaDetail,preReleaseVersion,buildBundles"
45
47
 
46
48
  module ProcessingState
47
49
  PROCESSING = "PROCESSING"
@@ -0,0 +1,59 @@
1
+ require_relative '../model'
2
+ module Spaceship
3
+ class ConnectAPI
4
+ class BuildBundle
5
+ include Spaceship::ConnectAPI::Model
6
+
7
+ attr_accessor :bundle_id
8
+ attr_accessor :bundle_type
9
+ attr_accessor :sdk_build
10
+ attr_accessor :platform_build
11
+ attr_accessor :file_name
12
+ attr_accessor :has_siri_kit
13
+ attr_accessor :has_on_demand_resources
14
+ attr_accessor :is_newsstand
15
+ attr_accessor :has_prerendered_icon
16
+ attr_accessor :uses_location_services
17
+ attr_accessor :is_ios_build_mac_app_store_compatible
18
+ attr_accessor :includes_symbols
19
+ attr_accessor :dsym_url
20
+ attr_accessor :supported_architectures
21
+ attr_accessor :required_capabilities
22
+ attr_accessor :device_protocols
23
+ attr_accessor :locales
24
+ attr_accessor :entitlements
25
+ attr_accessor :tracks_users
26
+
27
+ module BundleType
28
+ APP = "APP"
29
+ # APP_CLIP might be in here as well
30
+ end
31
+
32
+ attr_mapping({
33
+ "bundleId" => "bundle_id",
34
+ "bundleType" => "bundle_type",
35
+ "sdkBuild" => "sdk_build",
36
+ "platformBuild" => "platform_build",
37
+ "fileName" => "file_name",
38
+ "hasSirikit" => "has_siri_kit",
39
+ "hasOnDemandResources" => "has_on_demand_resources",
40
+ "isNewsstand" => "is_newsstand",
41
+ "hasPrerenderedIcon" => "has_prerendered_icon",
42
+ "usesLocationServices" => "uses_location_services",
43
+ "isIosBuildMacAppStoreCompatible" => "is_ios_build_mac_app_store_compatible",
44
+ "includesSymbols" => "includes_symbols",
45
+ "dSYMUrl" => "dsym_url",
46
+ "supportedArchitectures" => "supported_architectures",
47
+ "requiredCapabilities" => "required_capabilities",
48
+ "deviceProtocols" => "device_protocols",
49
+ "locales" => "locales",
50
+ "entitlements" => "entitlements",
51
+ "tracksUsers" => "tracks_users"
52
+ })
53
+
54
+ def self.type
55
+ return "buildBundles"
56
+ end
57
+ end
58
+ end
59
+ end
@@ -41,6 +41,9 @@ module Spaceship
41
41
  MAC_APP_DEVELOPMENT = "MAC_APP_DEVELOPMENT"
42
42
  DEVELOPER_ID_KEXT = "DEVELOPER_ID_KEXT"
43
43
  DEVELOPER_ID_APPLICATION = "DEVELOPER_ID_APPLICATION"
44
+
45
+ # As of 2021-11-09, this is only available with Apple ID auth
46
+ DEVELOPER_ID_INSTALLER = "DEVELOPER_ID_INSTALLER"
44
47
  end
45
48
 
46
49
  def self.type
@@ -64,6 +64,19 @@ module Spaceship
64
64
  yield(model)
65
65
  end
66
66
  end
67
+
68
+ def all_pages_each(&block)
69
+ to_models.each do |model|
70
+ yield(model)
71
+ end
72
+
73
+ resp = self
74
+ loop do
75
+ resp = resp.next_page
76
+ break if resp.nil?
77
+ resp.each(&block)
78
+ end
79
+ end
67
80
  end
68
81
  end
69
82
  end
@@ -32,6 +32,7 @@ require 'spaceship/connect_api/models/beta_tester_metric'
32
32
  require 'spaceship/connect_api/models/build'
33
33
  require 'spaceship/connect_api/models/build_delivery'
34
34
  require 'spaceship/connect_api/models/build_beta_detail'
35
+ require 'spaceship/connect_api/models/build_bundle'
35
36
  require 'spaceship/connect_api/models/custom_app_organization'
36
37
  require 'spaceship/connect_api/models/custom_app_user'
37
38
  require 'spaceship/connect_api/models/pre_release_version'