fastlane 2.209.0 → 2.210.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +88 -88
  3. data/deliver/lib/deliver/runner.rb +14 -7
  4. data/fastlane/lib/assets/AppfileTemplate +1 -1
  5. data/fastlane/lib/assets/AppfileTemplate.swift +1 -1
  6. data/fastlane/lib/fastlane/actions/run_tests.rb +1 -1
  7. data/fastlane/lib/fastlane/version.rb +1 -1
  8. data/fastlane/swift/Appfile.swift +1 -1
  9. data/fastlane/swift/Deliverfile.swift +1 -1
  10. data/fastlane/swift/DeliverfileProtocol.swift +1 -1
  11. data/fastlane/swift/Fastlane.swift +3 -3
  12. data/fastlane/swift/Gymfile.swift +1 -1
  13. data/fastlane/swift/GymfileProtocol.swift +1 -1
  14. data/fastlane/swift/Matchfile.swift +1 -1
  15. data/fastlane/swift/MatchfileProtocol.swift +1 -1
  16. data/fastlane/swift/Precheckfile.swift +1 -1
  17. data/fastlane/swift/PrecheckfileProtocol.swift +1 -1
  18. data/fastlane/swift/Scanfile.swift +1 -1
  19. data/fastlane/swift/ScanfileProtocol.swift +1 -1
  20. data/fastlane/swift/Screengrabfile.swift +1 -1
  21. data/fastlane/swift/ScreengrabfileProtocol.swift +1 -1
  22. data/fastlane/swift/Snapshotfile.swift +1 -1
  23. data/fastlane/swift/SnapshotfileProtocol.swift +1 -1
  24. data/fastlane/swift/formatting/Brewfile.lock.json +16 -16
  25. data/fastlane_core/lib/fastlane_core/cert_checker.rb +66 -10
  26. data/fastlane_core/lib/fastlane_core/itunes_transporter.rb +246 -20
  27. data/pilot/lib/pilot/build_manager.rb +11 -4
  28. data/spaceship/lib/spaceship/connect_api/token.rb +5 -2
  29. data/spaceship/lib/spaceship/two_step_or_factor_client.rb +1 -1
  30. metadata +22 -30
  31. data/fastlane/lib/.DS_Store +0 -0
  32. data/fastlane/lib/fastlane/.DS_Store +0 -0
  33. data/fastlane/lib/fastlane/actions/.DS_Store +0 -0
  34. data/fastlane/lib/fastlane/actions/.supply.rb.swp +0 -0
  35. data/fastlane/swift/FastlaneSwiftRunner/FastlaneSwiftRunner.xcodeproj/project.xcworkspace/xcuserdata/joshholtz.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  36. data/spaceship/lib/spaceship/connect_api/models/.review_submission.rb.swp +0 -0
  37. data/spaceship/lib/spaceship/connect_api/testflight/.testflight.rb.swp +0 -0
  38. data/trainer/lib/.DS_Store +0 -0
@@ -3,6 +3,40 @@ require 'openssl'
3
3
 
4
4
  require_relative 'helper'
5
5
 
6
+ # WWDR Intermediate Certificates in https://www.apple.com/certificateauthority/
7
+ WWDRCA_CERTIFICATES = [
8
+ {
9
+ alias: 'G1',
10
+ sha256: 'ce057691d730f89ca25e916f7335f4c8a15713dcd273a658c024023f8eb809c2',
11
+ url: 'https://developer.apple.com/certificationauthority/AppleWWDRCA.cer'
12
+ },
13
+ {
14
+ alias: 'G2',
15
+ sha256: '9ed4b3b88c6a339cf1387895bda9ca6ea31a6b5ce9edf7511845923b0c8ac94c',
16
+ url: 'https://www.apple.com/certificateauthority/AppleWWDRCAG2.cer'
17
+ },
18
+ {
19
+ alias: 'G3',
20
+ sha256: 'dcf21878c77f4198e4b4614f03d696d89c66c66008d4244e1b99161aac91601f',
21
+ url: 'https://www.apple.com/certificateauthority/AppleWWDRCAG3.cer'
22
+ },
23
+ {
24
+ alias: 'G4',
25
+ sha256: 'ea4757885538dd8cb59ff4556f676087d83c85e70902c122e42c0808b5bce14c',
26
+ url: 'https://www.apple.com/certificateauthority/AppleWWDRCAG4.cer'
27
+ },
28
+ {
29
+ alias: 'G5',
30
+ sha256: '53fd008278e5a595fe1e908ae9c5e5675f26243264a5a6438c023e3ce2870760',
31
+ url: 'https://www.apple.com/certificateauthority/AppleWWDRCAG5.cer'
32
+ },
33
+ {
34
+ alias: 'G6',
35
+ sha256: 'bdd4ed6e74691f0c2bfd01be0296197af1379e0418e2d300efa9c3bef642ca30',
36
+ url: 'https://www.apple.com/certificateauthority/AppleWWDRCAG6.cer'
37
+ }
38
+ ]
39
+
6
40
  module FastlaneCore
7
41
  # This class checks if a specific certificate is installed on the current mac
8
42
  class CertChecker
@@ -22,7 +56,7 @@ module FastlaneCore
22
56
  end
23
57
 
24
58
  def self.installed_identies(in_keychain: nil)
25
- install_wwdr_certificate unless wwdr_certificate_installed?
59
+ install_missing_wwdr_certificates
26
60
 
27
61
  available = list_available_identities(in_keychain: in_keychain)
28
62
  # Match for this text against word boundaries to avoid edge cases around multiples of 10 identities!
@@ -81,19 +115,41 @@ module FastlaneCore
81
115
  `#{commands.join(' ')}`
82
116
  end
83
117
 
84
- def self.wwdr_certificate_installed?
85
- certificate_name = "Apple Worldwide Developer Relations Certification Authority"
86
- certificate_hash = "SHA-256 hash: BDD4ED6E74691F0C2BFD01BE0296197AF1379E0418E2D300EFA9C3BEF642CA30"
118
+ def self.installed_wwdr_certificates
119
+ certificate_name = "Apple Worldwide Developer Relations"
120
+
121
+ # Find all installed WWDRCA certificates
122
+ installed_certs = []
123
+ Helper.backticks("security find-certificate -a -c '#{certificate_name}' -p #{wwdr_keychain.shellescape}")
124
+ .lines
125
+ .each do |line|
126
+ if line.start_with?('-----BEGIN CERTIFICATE-----')
127
+ installed_certs << line
128
+ else
129
+ installed_certs.last << line
130
+ end
131
+ end
87
132
 
88
- keychain = wwdr_keychain
89
- response = Helper.backticks("security find-certificate -a -c '#{certificate_name}' -Z #{keychain.shellescape} | grep ^SHA-256", print: FastlaneCore::Globals.verbose?)
133
+ # Get the alias (see `WWDRCA_CERTIFICATES`) of the installed WWDRCA certificates
134
+ installed_certs
135
+ .map do |pem|
136
+ sha256 = Digest::SHA256.hexdigest(OpenSSL::X509::Certificate.new(pem).to_der)
137
+ WWDRCA_CERTIFICATES.find { |c| c[:sha256].casecmp?(sha256) }&.fetch(:alias)
138
+ end
139
+ .compact
140
+ end
90
141
 
91
- certs = response.split("\n")
92
- certs.include?(certificate_hash)
142
+ def self.install_missing_wwdr_certificates
143
+ # Install all Worldwide Developer Relations Intermediate Certificates listed here: https://www.apple.com/certificateauthority/
144
+ missing = WWDRCA_CERTIFICATES.map { |c| c[:alias] } - installed_wwdr_certificates
145
+ missing.each do |cert_alias|
146
+ install_wwdr_certificate(cert_alias)
147
+ end
148
+ missing.count
93
149
  end
94
150
 
95
- def self.install_wwdr_certificate
96
- url = 'https://www.apple.com/certificateauthority/AppleWWDRCAG6.cer'
151
+ def self.install_wwdr_certificate(cert_alias)
152
+ url = WWDRCA_CERTIFICATES.find { |c| c[:alias] == cert_alias }.fetch(:url)
97
153
  file = Tempfile.new(File.basename(url))
98
154
  filename = file.path
99
155
  keychain = wwdr_keychain
@@ -28,10 +28,29 @@ module FastlaneCore
28
28
  OUTPUT_REGEX = />\s+(.+)/
29
29
  RETURN_VALUE_REGEX = />\sDBG-X:\sReturning\s+(\d+)/
30
30
 
31
+ # Matches a line in the iTMSTransporter provider table: "12 Initech Systems Inc LG89CQY559"
32
+ ITMS_PROVIDER_REGEX = /^\d+\s{2,}.+\s{2,}[^\s]+$/
33
+
31
34
  SKIP_ERRORS = ["ERROR: An exception has occurred: Scheduling automatic restart in 1 minute"]
32
35
 
33
36
  private_constant :ERROR_REGEX, :WARNING_REGEX, :OUTPUT_REGEX, :RETURN_VALUE_REGEX, :SKIP_ERRORS
34
37
 
38
+ def build_download_command(username, password, apple_id, destination = "/tmp", provider_short_name = "", jwt = nil)
39
+ not_implemented(__method__)
40
+ end
41
+
42
+ def build_provider_ids_command(username, password, jwt = nil, api_key = nil)
43
+ not_implemented(__method__)
44
+ end
45
+
46
+ def build_upload_command(username, password, source = "/tmp", provider_short_name = "", jwt = nil, platform = nil, api_key = nil)
47
+ not_implemented(__method__)
48
+ end
49
+
50
+ def build_verify_command(username, password, source = "/tmp", provider_short_name = "", jwt = nil)
51
+ not_implemented(__method__)
52
+ end
53
+
35
54
  def execute(command, hide_output)
36
55
  if Helper.test?
37
56
  yield(nil) if block_given?
@@ -100,8 +119,18 @@ module FastlaneCore
100
119
  @errors.map { |error| "[Transporter Error Output]: #{error}" }.join("\n").gsub!(/"/, "")
101
120
  end
102
121
 
122
+ def parse_provider_info(lines)
123
+ lines.map { |line| itms_provider_pair(line) }.compact.to_h
124
+ end
125
+
103
126
  private
104
127
 
128
+ def itms_provider_pair(line)
129
+ line = line.strip
130
+ return nil unless line =~ ITMS_PROVIDER_REGEX
131
+ line.split(/\s{2,}/).drop(1)
132
+ end
133
+
105
134
  def parse_line(line, hide_output)
106
135
  # Taken from https://github.com/sshaw/itunes_store_transporter/blob/master/lib/itunes/store/transporter/output_parser.rb
107
136
 
@@ -180,9 +209,166 @@ module FastlaneCore
180
209
  end
181
210
  end
182
211
 
212
+ # Generates commands and executes the altool.
213
+ class AltoolTransporterExecutor < TransporterExecutor
214
+ ERROR_REGEX = /\*\*\* Error:\s+(.+)/
215
+
216
+ private_constant :ERROR_REGEX
217
+
218
+ def execute(command, hide_output)
219
+ if Helper.test?
220
+ yield(nil) if block_given?
221
+ return command
222
+ end
223
+
224
+ @errors = []
225
+ @all_lines = []
226
+
227
+ if hide_output
228
+ # Show a one time message instead
229
+ UI.success("Waiting for App Store Connect transporter to be finished.")
230
+ UI.success("Application Loader progress... this might take a few minutes...")
231
+ end
232
+
233
+ begin
234
+ exit_status = FastlaneCore::FastlanePty.spawn(command) do |command_stdout, command_stdin, pid|
235
+ command_stdout.each do |line|
236
+ @all_lines << line
237
+ parse_line(line, hide_output) # this is where the parsing happens
238
+ end
239
+ end
240
+ rescue => ex
241
+ # FastlanePty adds exit_status on to StandardError so every error will have a status code
242
+ exit_status = ex.exit_status
243
+ @errors << ex.to_s
244
+ end
245
+
246
+ @errors << "The call to the altool completed with a non-zero exit status: #{exit_status}. This indicates a failure." unless exit_status.zero?
247
+
248
+ unless @errors.empty? || @all_lines.empty?
249
+ # Print the last lines that appear after the last error from the logs
250
+ # If error text is not detected, it will be 20 lines
251
+ # This is key for non-verbose mode
252
+
253
+ # The format of altool's result with error is like below
254
+ # > *** Error: Error uploading '...'.
255
+ # > *** Error: ...
256
+ # > {
257
+ # > NSLocalizedDescription = "...",
258
+ # > ...
259
+ # > }
260
+ # So this line tries to find the line which has "*** Error:" prefix from bottom of log
261
+ error_line_index = @all_lines.rindex { |line| ERROR_REGEX.match?(line) }
262
+
263
+ @all_lines[(error_line_index || -20)..-1].each do |line|
264
+ UI.important("[altool] #{line}")
265
+ end
266
+ UI.message("Application Loader output above ^")
267
+ @errors.each { |error| UI.error(error) }
268
+ end
269
+
270
+ yield(@all_lines) if block_given?
271
+ exit_status.zero?
272
+ end
273
+
274
+ def build_upload_command(username, password, source = "/tmp", provider_short_name = "", jwt = nil, platform = nil, api_key = nil)
275
+ use_api_key = !api_key.nil?
276
+ [
277
+ ("API_PRIVATE_KEYS_DIR=#{api_key[:key_dir]}" if use_api_key),
278
+ "xcrun altool",
279
+ "--upload-app",
280
+ ("-u #{username.shellescape}" unless use_api_key),
281
+ ("-p #{password.shellescape}" unless use_api_key),
282
+ ("--apiKey #{api_key[:key_id]}" if use_api_key),
283
+ ("--apiIssuer #{api_key[:issuer_id]}" if use_api_key),
284
+ ("--asc-provider #{provider_short_name}" unless use_api_key || provider_short_name.to_s.empty?),
285
+ platform_option(platform),
286
+ file_upload_option(source),
287
+ additional_upload_parameters,
288
+ "-k 100000"
289
+ ].compact.join(' ')
290
+ end
291
+
292
+ def build_provider_ids_command(username, password, jwt = nil, api_key = nil)
293
+ use_api_key = !api_key.nil?
294
+ [
295
+ ("API_PRIVATE_KEYS_DIR=#{api_key[:key_dir]}" if use_api_key),
296
+ "xcrun altool",
297
+ "--list-providers",
298
+ ("-u #{username.shellescape}" unless use_api_key),
299
+ ("-p #{password.shellescape}" unless use_api_key),
300
+ ("--apiKey #{api_key[:key_id]}" if use_api_key),
301
+ ("--apiIssuer #{api_key[:issuer_id]}" if use_api_key),
302
+ "--output-format json"
303
+ ].compact.join(' ')
304
+ end
305
+
306
+ def build_download_command(username, password, apple_id, destination = "/tmp", provider_short_name = "", jwt = nil)
307
+ raise "This feature has not been implemented yet with altool for Xcode 14"
308
+ end
309
+
310
+ def build_verify_command(username, password, source = "/tmp", provider_short_name = "", jwt = nil)
311
+ raise "This feature has not been implemented yet with altool for Xcode 14"
312
+ end
313
+
314
+ def additional_upload_parameters
315
+ env_deliver_additional_params = ENV["DELIVER_ALTOOL_ADDITIONAL_UPLOAD_PARAMETERS"]
316
+ return nil if env_deliver_additional_params.to_s.strip.empty?
317
+
318
+ env_deliver_additional_params.to_s.strip
319
+ end
320
+
321
+ def handle_error(password)
322
+ UI.error("Could not download/upload from App Store Connect!")
323
+ end
324
+
325
+ def displayable_errors
326
+ @errors.map { |error| "[Application Loader Error Output]: #{error}" }.join("\n")
327
+ end
328
+
329
+ def parse_provider_info(lines)
330
+ # This tries parsing the provider id from altool output to detect provider list
331
+ provider_info = {}
332
+ json_body = lines[-2] # altool outputs result in second line from last
333
+ return provider_info if json_body.nil?
334
+ providers = JSON.parse(json_body)["providers"]
335
+ return provider_info if providers.nil?
336
+ providers.each do |provider|
337
+ provider_info[provider["ProviderName"]] = provider["ProviderShortname"]
338
+ end
339
+ provider_info
340
+ end
341
+
342
+ private
343
+
344
+ def file_upload_option(source)
345
+ "-f #{source.shellescape}"
346
+ end
347
+
348
+ def platform_option(platform)
349
+ "-t #{platform == 'osx' ? 'macos' : platform}"
350
+ end
351
+
352
+ def parse_line(line, hide_output)
353
+ output_done = false
354
+
355
+ if line =~ ERROR_REGEX
356
+ @errors << $1
357
+ output_done = true
358
+ end
359
+
360
+ unless hide_output
361
+ # General logging for debug purposes
362
+ unless output_done
363
+ UI.verbose("[altool]: #{line}")
364
+ end
365
+ end
366
+ end
367
+ end
368
+
183
369
  # Generates commands and executes the iTMSTransporter through the shell script it provides by the same name
184
370
  class ShellScriptTransporterExecutor < TransporterExecutor
185
- def build_upload_command(username, password, source = "/tmp", provider_short_name = "", jwt = nil)
371
+ def build_upload_command(username, password, source = "/tmp", provider_short_name = "", jwt = nil, platform = nil, api_key = nil)
186
372
  use_jwt = !jwt.to_s.empty?
187
373
  [
188
374
  '"' + Helper.transporter_path + '"',
@@ -212,7 +398,7 @@ module FastlaneCore
212
398
  ].compact.join(' ')
213
399
  end
214
400
 
215
- def build_provider_ids_command(username, password, jwt = nil)
401
+ def build_provider_ids_command(username, password, jwt = nil, api_key = nil)
216
402
  use_jwt = !jwt.to_s.empty?
217
403
  [
218
404
  '"' + Helper.transporter_path + '"',
@@ -278,7 +464,7 @@ module FastlaneCore
278
464
  # Generates commands and executes the iTMSTransporter by invoking its Java app directly, to avoid the crazy parameter
279
465
  # escaping problems in its accompanying shell script.
280
466
  class JavaTransporterExecutor < TransporterExecutor
281
- def build_upload_command(username, password, source = "/tmp", provider_short_name = "", jwt = nil)
467
+ def build_upload_command(username, password, source = "/tmp", provider_short_name = "", jwt = nil, platform = nil, api_key = nil)
282
468
  use_jwt = !jwt.to_s.empty?
283
469
  if !Helper.user_defined_itms_path? && Helper.mac? && Helper.xcode_at_least?(11)
284
470
  [
@@ -392,7 +578,7 @@ module FastlaneCore
392
578
  end
393
579
  end
394
580
 
395
- def build_provider_ids_command(username, password, jwt = nil)
581
+ def build_provider_ids_command(username, password, jwt = nil, api_key = nil)
396
582
  use_jwt = !jwt.to_s.empty?
397
583
  if !Helper.user_defined_itms_path? && Helper.mac? && Helper.xcode_at_least?(11)
398
584
  [
@@ -451,8 +637,6 @@ module FastlaneCore
451
637
  end
452
638
 
453
639
  class ItunesTransporter
454
- # Matches a line in the provider table: "12 Initech Systems Inc LG89CQY559"
455
- PROVIDER_REGEX = /^\d+\s{2,}.+\s{2,}[^\s]+$/
456
640
  TWO_STEP_HOST_PREFIX = "deliver.appspecific"
457
641
 
458
642
  # This will be called from the Deliverfile, and disables the logging of the transporter output
@@ -476,7 +660,7 @@ module FastlaneCore
476
660
  # see: https://github.com/fastlane/fastlane/issues/1524#issuecomment-196370628
477
661
  # for more information about how to use the iTMSTransporter to list your provider
478
662
  # short names
479
- def initialize(user = nil, password = nil, use_shell_script = false, provider_short_name = nil, jwt = nil)
663
+ def initialize(user = nil, password = nil, use_shell_script = false, provider_short_name = nil, jwt = nil, upload: false, api_key: nil)
480
664
  # Xcode 6.x doesn't have the same iTMSTransporter Java setup as later Xcode versions, so
481
665
  # we can't default to using the newer direct Java invocation strategy for those versions.
482
666
  use_shell_script ||= Helper.is_mac? && Helper.xcode_version.start_with?('6.')
@@ -489,8 +673,16 @@ module FastlaneCore
489
673
  end
490
674
 
491
675
  @jwt = jwt
676
+ @api_key = api_key
677
+
678
+ if should_use_altool?(upload, use_shell_script)
679
+ UI.verbose("Using altool as transporter.")
680
+ @transporter_executor = AltoolTransporterExecutor.new
681
+ else
682
+ UI.verbose("Using iTMSTransporter as transporter.")
683
+ @transporter_executor = use_shell_script ? ShellScriptTransporterExecutor.new : JavaTransporterExecutor.new
684
+ end
492
685
 
493
- @transporter_executor = use_shell_script ? ShellScriptTransporterExecutor.new : JavaTransporterExecutor.new
494
686
  @provider_short_name = provider_short_name
495
687
  end
496
688
 
@@ -539,7 +731,7 @@ module FastlaneCore
539
731
  # @return (Bool) True if everything worked fine
540
732
  # @raise [Deliver::TransporterTransferError] when something went wrong
541
733
  # when transferring
542
- def upload(app_id = nil, dir = nil, package_path: nil, asset_path: nil)
734
+ def upload(app_id = nil, dir = nil, package_path: nil, asset_path: nil, platform: nil)
543
735
  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?
544
736
 
545
737
  # Transport can upload .ipa, .dmg, and .pkg files directly with -assetFile
@@ -569,14 +761,25 @@ module FastlaneCore
569
761
  password_placeholder = @jwt.nil? ? 'YourPassword' : nil
570
762
  jwt_placeholder = @jwt.nil? ? nil : 'YourJWT'
571
763
 
572
- command = @transporter_executor.build_upload_command(@user, @password, actual_dir, @provider_short_name, @jwt)
573
- UI.verbose(@transporter_executor.build_upload_command(@user, password_placeholder, actual_dir, @provider_short_name, jwt_placeholder))
764
+ # Handle AppStore Connect API
765
+ use_api_key = !@api_key.nil?
766
+ api_key_placeholder = use_api_key ? { key_id: "YourKeyID", issuer_id: "YourIssuerID", key_dir: "YourTmpP8KeyDir" } : nil
767
+
768
+ api_key = nil
769
+ api_key = api_key_with_p8_file_path(@api_key) if use_api_key
770
+
771
+ command = @transporter_executor.build_upload_command(@user, @password, actual_dir, @provider_short_name, @jwt, platform, api_key)
772
+ UI.verbose(@transporter_executor.build_upload_command(@user, password_placeholder, actual_dir, @provider_short_name, jwt_placeholder, platform, api_key_placeholder))
574
773
 
575
774
  begin
576
775
  result = @transporter_executor.execute(command, ItunesTransporter.hide_transporter_output?)
577
776
  rescue TransporterRequiresApplicationSpecificPasswordError => ex
578
777
  handle_two_step_failure(ex)
579
778
  return upload(app_id, dir, package_path: package_path, asset_path: asset_path)
779
+ ensure
780
+ if use_api_key
781
+ FileUtils.rm_rf(api_key[:key_dir]) unless api_key.nil?
782
+ end
580
783
  end
581
784
 
582
785
  if result
@@ -638,8 +841,15 @@ module FastlaneCore
638
841
  password_placeholder = @jwt.nil? ? 'YourPassword' : nil
639
842
  jwt_placeholder = @jwt.nil? ? nil : 'YourJWT'
640
843
 
641
- command = @transporter_executor.build_provider_ids_command(@user, @password, @jwt)
642
- UI.verbose(@transporter_executor.build_provider_ids_command(@user, password_placeholder, jwt_placeholder))
844
+ # Handle AppStore Connect API
845
+ use_api_key = !@api_key.nil?
846
+ api_key_placeholder = use_api_key ? { key_id: "YourKeyID", issuer_id: "YourIssuerID", key_dir: "YourTmpP8KeyDir" } : nil
847
+
848
+ api_key = nil
849
+ api_key = api_key_with_p8_file_path(@api_key) if use_api_key
850
+
851
+ command = @transporter_executor.build_provider_ids_command(@user, @password, @jwt, api_key)
852
+ UI.verbose(@transporter_executor.build_provider_ids_command(@user, password_placeholder, jwt_placeholder, api_key_placeholder))
643
853
 
644
854
  lines = []
645
855
  begin
@@ -648,15 +858,37 @@ module FastlaneCore
648
858
  rescue TransporterRequiresApplicationSpecificPasswordError => ex
649
859
  handle_two_step_failure(ex)
650
860
  return provider_ids
861
+ ensure
862
+ if use_api_key
863
+ FileUtils.rm_rf(api_key[:key_dir]) unless api_key.nil?
864
+ end
651
865
  end
652
866
 
653
- lines.map { |line| provider_pair(line) }.compact.to_h
867
+ @transporter_executor.parse_provider_info(lines)
654
868
  end
655
869
 
656
870
  private
657
871
 
658
872
  TWO_FACTOR_ENV_VARIABLE = "FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD"
659
873
 
874
+ # Create .p8 file from api_key and provide api key info which contains .p8 file path
875
+ def api_key_with_p8_file_path(original_api_key)
876
+ api_key = original_api_key.clone
877
+ api_key[:key_dir] = Dir.mktmpdir("deliver-")
878
+ # Specified p8 needs to be generated to call altool
879
+ File.open(File.join(api_key[:key_dir], "AuthKey_#{api_key[:key_id]}.p8"), "wb") do |p8|
880
+ key_content = api_key[:is_key_content_base64] ? Base64.decode64(api_key[:key]) : api_key[:key]
881
+ p8.write(key_content)
882
+ end
883
+ api_key
884
+ end
885
+
886
+ # Returns whether altool should be used or ItunesTransporter should be used
887
+ def should_use_altool?(upload, use_shell_script)
888
+ # Xcode 14 no longer supports iTMSTransporter. Use altool instead
889
+ !use_shell_script && upload && !Helper.user_defined_itms_path? && Helper.mac? && Helper.xcode_at_least?(14)
890
+ end
891
+
660
892
  # Returns the password to be used with the transporter
661
893
  def load_password_for_transporter
662
894
  # 3 different sources for the password
@@ -714,11 +946,5 @@ module FastlaneCore
714
946
  def handle_error(password)
715
947
  @transporter_executor.handle_error(password)
716
948
  end
717
-
718
- def provider_pair(line)
719
- line = line.strip
720
- return nil unless line =~ PROVIDER_REGEX
721
- line.split(/\s{2,}/).drop(1)
722
- end
723
949
  end
724
950
  end
@@ -47,7 +47,7 @@ module Pilot
47
47
  end
48
48
 
49
49
  transporter = transporter_for_selected_team(options)
50
- result = transporter.upload(package_path: package_path, asset_path: asset_path)
50
+ result = transporter.upload(package_path: package_path, asset_path: asset_path, platform: platform)
51
51
 
52
52
  unless result
53
53
  transporter_errors = transporter.displayable_errors
@@ -389,15 +389,22 @@ module Pilot
389
389
  def transporter_for_selected_team(options)
390
390
  # Use JWT auth
391
391
  api_token = Spaceship::ConnectAPI.token
392
+ api_key = if options[:api_key].nil? && !api_token.nil?
393
+ # Load api key info if user set api_key_path, not api_key
394
+ { key_id: api_token.key_id, issuer_id: api_token.issuer_id, key: api_token.key_raw, is_key_content_base64: api_token.is_key_content_base64 }
395
+ elsif !options[:api_key].nil?
396
+ options[:api_key].transform_keys(&:to_sym)
397
+ end
398
+
392
399
  unless api_token.nil?
393
400
  api_token.refresh! if api_token.expired?
394
- return FastlaneCore::ItunesTransporter.new(nil, nil, false, nil, api_token.text)
401
+ return FastlaneCore::ItunesTransporter.new(nil, nil, false, nil, api_token.text, upload: true, api_key: api_key)
395
402
  end
396
403
 
397
404
  # Otherwise use username and password
398
405
  tunes_client = Spaceship::ConnectAPI.client ? Spaceship::ConnectAPI.client.tunes_client : nil
399
406
 
400
- generic_transporter = FastlaneCore::ItunesTransporter.new(options[:username], nil, false, options[:itc_provider])
407
+ generic_transporter = FastlaneCore::ItunesTransporter.new(options[:username], nil, false, options[:itc_provider], upload: true, api_key: api_key)
401
408
  return generic_transporter if options[:itc_provider] || tunes_client.nil?
402
409
  return generic_transporter unless tunes_client.teams.count > 1
403
410
 
@@ -406,7 +413,7 @@ module Pilot
406
413
  name = team['name']
407
414
  provider_id = generic_transporter.provider_ids[name]
408
415
  UI.verbose("Inferred provider id #{provider_id} for team #{name}.")
409
- return FastlaneCore::ItunesTransporter.new(options[:username], nil, false, provider_id)
416
+ return FastlaneCore::ItunesTransporter.new(options[:username], nil, false, provider_id, upload: true, api_key: api_key)
410
417
  rescue => ex
411
418
  STDERR.puts(ex.to_s)
412
419
  UI.verbose("Couldn't infer a provider short name for team with id #{tunes_client.team_id} automatically: #{ex}. Proceeding without provider short name.")
@@ -22,6 +22,7 @@ module Spaceship
22
22
  attr_reader :expiration
23
23
 
24
24
  attr_reader :key_raw
25
+ attr_reader :is_key_content_base64
25
26
 
26
27
  # Temporary attribute not needed to create the JWT text
27
28
  # There is no way to determine if the team associated with this
@@ -71,17 +72,19 @@ module Spaceship
71
72
  key: OpenSSL::PKey::EC.new(key),
72
73
  key_raw: key,
73
74
  duration: duration,
74
- in_house: in_house
75
+ in_house: in_house,
76
+ is_key_content_base64: is_key_content_base64
75
77
  )
76
78
  end
77
79
 
78
- def initialize(key_id: nil, issuer_id: nil, key: nil, key_raw: nil, duration: nil, in_house: nil)
80
+ def initialize(key_id: nil, issuer_id: nil, key: nil, key_raw: nil, duration: nil, in_house: nil, is_key_content_base64: nil)
79
81
  @key_id = key_id
80
82
  @key = key
81
83
  @key_raw = key_raw
82
84
  @issuer_id = issuer_id
83
85
  @duration = duration
84
86
  @in_house = in_house
87
+ @is_key_content_base64 = is_key_content_base64
85
88
 
86
89
  @duration ||= DEFAULT_TOKEN_DURATION
87
90
  @duration = @duration.to_i if @duration
@@ -164,7 +164,7 @@ module Spaceship
164
164
  body = { "securityCode" => { "code" => code.to_s } }.to_json
165
165
 
166
166
  # User exited by entering `sms` and wants to choose phone number for SMS
167
- if code == 'sms'
167
+ if code.casecmp?("sms")
168
168
  code_type = 'phone'
169
169
  body = request_two_factor_code_from_phone_choose(response.body["trustedPhoneNumbers"], code_length)
170
170
  end