fastlane 2.108.0 → 2.109.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +76 -76
  3. data/deliver/lib/deliver/app_screenshot.rb +22 -1
  4. data/fastlane/lib/fastlane/action_collector.rb +1 -22
  5. data/fastlane/lib/fastlane/actions/appetize.rb +20 -3
  6. data/fastlane/lib/fastlane/actions/docs/build_ios_app.md +12 -9
  7. data/fastlane/lib/fastlane/actions/pod_lib_lint.rb +25 -16
  8. data/fastlane/lib/fastlane/actions/pod_push.rb +1 -1
  9. data/fastlane/lib/fastlane/actions/puts.rb +1 -1
  10. data/fastlane/lib/fastlane/actions/set_github_release.rb +1 -0
  11. data/fastlane/lib/fastlane/actions/testfairy.rb +11 -5
  12. data/fastlane/lib/fastlane/fast_file.rb +9 -6
  13. data/fastlane/lib/fastlane/helper/adb_helper.rb +3 -3
  14. data/fastlane/lib/fastlane/helper/crashlytics_helper.rb +5 -5
  15. data/fastlane/lib/fastlane/version.rb +1 -1
  16. data/fastlane/swift/Deliverfile.swift +1 -1
  17. data/fastlane/swift/Fastlane.swift +16 -6
  18. data/fastlane/swift/Gymfile.swift +1 -1
  19. data/fastlane/swift/Matchfile.swift +1 -1
  20. data/fastlane/swift/Precheckfile.swift +1 -1
  21. data/fastlane/swift/Scanfile.swift +1 -1
  22. data/fastlane/swift/Screengrabfile.swift +1 -1
  23. data/fastlane/swift/Snapshotfile.swift +1 -1
  24. data/fastlane_core/lib/fastlane_core.rb +0 -1
  25. data/fastlane_core/lib/fastlane_core/configuration/configuration.rb +2 -2
  26. data/fastlane_core/lib/fastlane_core/project.rb +9 -11
  27. data/match/lib/match/encryption.rb +33 -9
  28. data/match/lib/match/runner.rb +15 -3
  29. data/match/lib/match/spaceship_ensure.rb +2 -5
  30. data/match/lib/match/storage.rb +29 -7
  31. data/match/lib/match/storage/git_storage.rb +30 -48
  32. data/match/lib/match/storage/interface.rb +39 -1
  33. data/precheck/lib/precheck/rules/other_platforms_rule.rb +4 -1
  34. data/scan/lib/scan/error_handler.rb +8 -1
  35. data/sigh/lib/assets/resign.sh +39 -63
  36. data/snapshot/lib/snapshot/reports_generator.rb +1 -0
  37. data/spaceship/lib/spaceship/client.rb +174 -58
  38. data/spaceship/lib/spaceship/commands_generator.rb +1 -0
  39. data/spaceship/lib/spaceship/du/du_client.rb +18 -12
  40. data/spaceship/lib/spaceship/spaceauth_runner.rb +1 -1
  41. data/spaceship/lib/spaceship/tunes/app_submission.rb +2 -1
  42. data/spaceship/lib/spaceship/tunes/device_type.rb +1 -1
  43. data/spaceship/lib/spaceship/tunes/tunes_client.rb +5 -2
  44. data/spaceship/lib/spaceship/{two_step_client.rb → two_step_or_factor_client.rb} +65 -96
  45. data/supply/lib/supply/options.rb +17 -3
  46. data/supply/lib/supply/uploader.rb +12 -5
  47. metadata +17 -19
  48. data/deliver/lib/deliver/.app_screenshot.rb.swp +0 -0
  49. data/fastlane_core/lib/fastlane_core/tool_collector.rb +0 -304
@@ -92,6 +92,7 @@ module Snapshot
92
92
  'iPad Pro (9.7-inch)' => 'iPad Pro (9.7-inch)',
93
93
  'iPad Pro (9.7 inch)' => 'iPad Pro (9.7-inch)', # iOS 10.3.1 simulator
94
94
  'iPad Pro (10.5-inch)' => 'iPad Pro (10.5-inch)',
95
+ 'iPad Pro (11-inch)' => 'iPad Pro (11-inch)',
95
96
  'iPad Pro (12.9-inch) (2nd generation)' => 'iPad Pro (12.9-inch) (2nd generation)',
96
97
  'iPad Pro (12.9-inch)' => 'iPad Pro (12.9-inch)',
97
98
  'iPad Pro (12.9 inch)' => 'iPad Pro (12.9-inch)', # iOS 10.3.1 simulator
@@ -4,6 +4,7 @@ require 'faraday_middleware'
4
4
  require 'logger'
5
5
  require 'tmpdir'
6
6
  require 'cgi'
7
+ require 'tempfile'
7
8
 
8
9
  require 'fastlane/version'
9
10
  require_relative 'babosa_fix'
@@ -53,32 +54,14 @@ module Spaceship
53
54
  GatewayTimeoutError = Spaceship::GatewayTimeoutError
54
55
  InternalServerError = Spaceship::InternalServerError
55
56
 
56
- # Authenticates with Apple's web services. This method has to be called once
57
- # to generate a valid session. The session will automatically be used from then
58
- # on.
59
- #
60
- # This method will automatically use the username from the Appfile (if available)
61
- # and fetch the password from the Keychain (if available)
62
- #
63
- # @param user (String) (optional): The username (usually the email address)
64
- # @param password (String) (optional): The password
65
- #
66
- # @raise InvalidUserCredentialsError: raised if authentication failed
67
- #
68
- # @return (Spaceship::Client) The client the login method was called for
69
- def self.login(user = nil, password = nil)
70
- instance = self.new
71
- if instance.login(user, password)
72
- instance
73
- else
74
- raise InvalidUserCredentialsError.new, "Invalid User Credentials"
75
- end
76
- end
77
-
78
57
  def self.hostname
79
58
  raise "You must implement self.hostname"
80
59
  end
81
60
 
61
+ #####################################################
62
+ # @!group Teams + User
63
+ #####################################################
64
+
82
65
  # @return (Array) A list of all available teams
83
66
  def teams
84
67
  user_details_data['associatedAccounts'].sort_by do |team|
@@ -198,6 +181,10 @@ module Spaceship
198
181
  (team_information || {})['name']
199
182
  end
200
183
 
184
+ #####################################################
185
+ # @!group Client Init
186
+ #####################################################
187
+
201
188
  # Instantiates a client but with a cookie derived from another client.
202
189
  #
203
190
  # HACK: since the `@cookie` is not exposed, we use this hacky way of sharing the instance.
@@ -238,6 +225,10 @@ module Spaceship
238
225
  end
239
226
  end
240
227
 
228
+ #####################################################
229
+ # @!group Request Logger
230
+ #####################################################
231
+
241
232
  # The logger in which all requests are logged
242
233
  # /tmp/spaceship[time]_[pid].log by default
243
234
  def logger
@@ -251,13 +242,18 @@ module Spaceship
251
242
  end
252
243
 
253
244
  @logger.formatter = proc do |severity, datetime, progname, msg|
254
- "[#{datetime.strftime('%H:%M:%S')}]: #{msg}\n"
245
+ severity = format('%-5.5s', severity)
246
+ "#{severity} [#{datetime.strftime('%H:%M:%S')}]: #{msg}\n"
255
247
  end
256
248
  end
257
249
 
258
250
  @logger
259
251
  end
260
252
 
253
+ #####################################################
254
+ # @!group Session Cookie
255
+ #####################################################
256
+
261
257
  ##
262
258
  # Return the session cookie.
263
259
  #
@@ -331,6 +327,28 @@ module Spaceship
331
327
  # @!group Login and Team Selection
332
328
  #####################################################
333
329
 
330
+ # Authenticates with Apple's web services. This method has to be called once
331
+ # to generate a valid session. The session will automatically be used from then
332
+ # on.
333
+ #
334
+ # This method will automatically use the username from the Appfile (if available)
335
+ # and fetch the password from the Keychain (if available)
336
+ #
337
+ # @param user (String) (optional): The username (usually the email address)
338
+ # @param password (String) (optional): The password
339
+ #
340
+ # @raise InvalidUserCredentialsError: raised if authentication failed
341
+ #
342
+ # @return (Spaceship::Client) The client the login method was called for
343
+ def self.login(user = nil, password = nil)
344
+ instance = self.new
345
+ if instance.login(user, password)
346
+ instance
347
+ else
348
+ raise InvalidUserCredentialsError.new, "Invalid User Credentials"
349
+ end
350
+ end
351
+
334
352
  # Authenticates with Apple's web services. This method has to be called once
335
353
  # to generate a valid session. The session will automatically be used from then
336
354
  # on.
@@ -348,6 +366,8 @@ module Spaceship
348
366
  if user.to_s.empty? || password.to_s.empty?
349
367
  require 'credentials_manager/account_manager'
350
368
 
369
+ puts("Reading keychain entry, because either user or password were empty") if Spaceship::Globals.verbose?
370
+
351
371
  keychain_entry = CredentialsManager::AccountManager.new(user: user, password: password)
352
372
  user ||= keychain_entry.user
353
373
  password = keychain_entry.password
@@ -360,7 +380,7 @@ module Spaceship
360
380
  self.user = user
361
381
  @password = password
362
382
  begin
363
- do_login(user, password)
383
+ do_login(user, password) # calls `send_login_request` in sub class (which then will redirect back here to `send_shared_login_request`, below)
364
384
  rescue InvalidUserCredentialsError => ex
365
385
  raise ex unless keychain_entry
366
386
 
@@ -374,20 +394,19 @@ module Spaceship
374
394
 
375
395
  # This method is used for both the Apple Dev Portal and App Store Connect
376
396
  # This will also handle 2 step verification
397
+ #
398
+ # It is called in `send_login_request` of sub classes (which the method `login`, above, transferred over to via `do_login`)
377
399
  def send_shared_login_request(user, password)
378
- # Check if we have a cached/valid session here
379
- # Fixes
380
- # - https://github.com/fastlane/fastlane/issues/10812
381
- # - https://github.com/fastlane/fastlane/issues/10793
400
+ # Check if we have a cached/valid session
382
401
  #
383
- # Before 4th December 2017 we didn't load existing session from the disk
384
- # but changed it, because Apple introduced a rate limit, which is fine by itself
385
- # but unfortunately it also rate limits successful logins, meaning if you call multiple
386
- # tools in a lane (e.g. call match 5 times), this would mean it locks you out of the account
387
- # for a while.
388
- # By loading existing sessions and checking if they're valid, we're sending less login requests
402
+ # Background:
403
+ # December 4th 2017 Apple introduced a rate limit - which is of course fine by itself -
404
+ # but unfortunately also rate limits successful logins. If you call multiple tools in a
405
+ # lane (e.g. call match 5 times), this would lock you out of the account for a while.
406
+ # By loading existing sessions and checking if they're valid, we're sending less login requests.
389
407
  # More context on why this change was necessary https://github.com/fastlane/fastlane/pull/11108
390
408
  #
409
+ # If there was a successful manual login before, we have a session on disk
391
410
  if load_session_from_file
392
411
  # Check if the session is still valid here
393
412
  begin
@@ -403,12 +422,24 @@ module Spaceship
403
422
  # which is common, as the session automatically invalidates after x hours (we don't know x)
404
423
  # In this case we don't actually care about the exact exception, and why it was failing
405
424
  # because either way, we'll have to do a fresh login, where we do the actual error handling
425
+ puts("Available session is not valid any more. Continuing with normal login.")
406
426
  end
407
427
  end
408
-
409
- # If this is a CI, the user can pass the session via environment variable
410
- # This is used for 2FA related sessions
411
- load_session_from_env
428
+ #
429
+ # The user can pass the session via environment variable (Mainly used in CI environments)
430
+ if load_session_from_env
431
+ # see above
432
+ begin
433
+ # see above
434
+ return true if fetch_olympus_session
435
+ rescue
436
+ puts("Session loaded from environment variable is not valid. Continuing with normal login.")
437
+ # see above
438
+ end
439
+ end
440
+ #
441
+ # After this point, we sure have no valid session any more and have to create a new one
442
+ #
412
443
 
413
444
  data = {
414
445
  accountName: user,
@@ -458,9 +489,9 @@ module Spaceship
458
489
  fetch_olympus_session
459
490
  return response
460
491
  when 409
461
- # 2 factor is enabled for this account, first handle that
492
+ # 2 step/factor is enabled for this account, first handle that
493
+ handle_two_step_or_factor(response)
462
494
  # and then get the olympus session
463
- handle_two_step(response)
464
495
  fetch_olympus_session
465
496
  return true
466
497
  else
@@ -525,6 +556,44 @@ module Spaceship
525
556
  raise AppleTimeoutError.new, "Could not receive latest API key from App Store Connect, this might be a server issue."
526
557
  end
527
558
 
559
+ #####################################################
560
+ # @!group Session
561
+ #####################################################
562
+
563
+ def load_session_from_file
564
+ if File.exist?(persistent_cookie_path)
565
+ puts("Loading session from '#{persistent_cookie_path}'") if Spaceship::Globals.verbose?
566
+ @cookie.load(persistent_cookie_path)
567
+ return true
568
+ end
569
+ return false
570
+ end
571
+
572
+ def load_session_from_env
573
+ return if self.class.spaceship_session_env.to_s.length == 0
574
+ puts("Loading session from environment variable") if Spaceship::Globals.verbose?
575
+
576
+ file = Tempfile.new('cookie.yml')
577
+ file.write(self.class.spaceship_session_env.gsub("\\n", "\n"))
578
+ file.close
579
+
580
+ begin
581
+ @cookie.load(file.path)
582
+ rescue => ex
583
+ puts("Error loading session from environment")
584
+ puts("Make sure to pass the session in a valid format")
585
+ raise ex
586
+ ensure
587
+ file.unlink
588
+ end
589
+ end
590
+
591
+ # Fetch the session cookie from the environment
592
+ # (if exists)
593
+ def self.spaceship_session_env
594
+ ENV["FASTLANE_SESSION"] || ENV["SPACESHIP_SESSION"]
595
+ end
596
+
528
597
  #####################################################
529
598
  # @!group Helpers
530
599
  #####################################################
@@ -533,21 +602,35 @@ module Spaceship
533
602
  return yield
534
603
  rescue \
535
604
  Faraday::Error::ConnectionFailed,
536
- Faraday::Error::TimeoutError,
537
- Faraday::ParsingError, # <h2>Internal Server Error</h2> with content type json
605
+ Faraday::Error::TimeoutError, # New Faraday version: Faraday::TimeoutError => ex
538
606
  AppleTimeoutError,
539
- GatewayTimeoutError,
540
- InternalServerError => ex # New Faraday version: Faraday::TimeoutError => ex
607
+ GatewayTimeoutError => ex
541
608
  tries -= 1
542
609
  unless tries.zero?
543
- logger.warn("Timeout received: '#{ex.message}'. Retrying after 3 seconds (remaining: #{tries})...")
610
+ msg = "Timeout received: '#{ex.class}', '#{ex.message}'. Retrying after 3 seconds (remaining: #{tries})..."
611
+ puts(msg) if Spaceship::Globals.verbose?
612
+ logger.warn(msg)
613
+
614
+ sleep(3) unless Object.const_defined?("SpecHelper")
615
+ retry
616
+ end
617
+ raise ex # re-raise the exception
618
+ rescue \
619
+ Faraday::ParsingError, # <h2>Internal Server Error</h2> with content type json
620
+ InternalServerError => ex
621
+ tries -= 1
622
+ unless tries.zero?
623
+ msg = "Internal Server Error received: '#{ex.class}', '#{ex.message}'. Retrying after 3 seconds (remaining: #{tries})..."
624
+ puts(msg) if Spaceship::Globals.verbose?
625
+ logger.warn(msg)
626
+
544
627
  sleep(3) unless Object.const_defined?("SpecHelper")
545
628
  retry
546
629
  end
547
630
  raise ex # re-raise the exception
548
631
  rescue UnauthorizedAccessError => ex
549
632
  if @loggedin && !(tries -= 1).zero?
550
- msg = "Auth error received: '#{ex.message}'. Login in again then retrying after 3 seconds (remaining: #{tries})..."
633
+ msg = "Auth error received: '#{ex.class}', '#{ex.message}'. Login in again then retrying after 3 seconds (remaining: #{tries})..."
551
634
  puts(msg) if Spaceship::Globals.verbose?
552
635
  logger.warn(msg)
553
636
 
@@ -572,7 +655,7 @@ module Spaceship
572
655
  headers['User-Agent'] = USER_AGENT
573
656
 
574
657
  # Before encoding the parameters, log them
575
- log_request(method, url_or_path, params)
658
+ log_request(method, url_or_path, params, headers, &block)
576
659
 
577
660
  # form-encode the params only if there are params, and the block is not supplied.
578
661
  # this is so that certain requests can be made using the block for more control
@@ -586,8 +669,6 @@ module Spaceship
586
669
  send_request(method, url_or_path, params, headers, &block)
587
670
  end
588
671
 
589
- log_response(method, url_or_path, response)
590
-
591
672
  return response
592
673
  end
593
674
 
@@ -602,9 +683,12 @@ module Spaceship
602
683
 
603
684
  content = expected_key ? response.body[expected_key] : response.body
604
685
  end
686
+
687
+ # if content (filled with whole body or just expected_key) is missing
605
688
  if content.nil?
606
689
  detect_most_common_errors_and_raise_exceptions(response.body) if response.body
607
690
  raise UnexpectedResponse, response.body
691
+ # else if it is a hash and `resultString` includes `NotAllowed`
608
692
  elsif content.kind_of?(Hash) && (content["resultString"] || "").include?("NotAllowed")
609
693
  # example content when doing a Developer Portal action with not enough permission
610
694
  # => {"responseId"=>"e5013d83-c5cb-4ba0-bb62-734a8d56007f",
@@ -616,7 +700,7 @@ module Spaceship
616
700
  # "userLocale"=>"en_US",
617
701
  # "requestUrl"=>"https://developer.apple.com/services-account/QH65B2/account/ios/certificate/downloadCertificateContent.action",
618
702
  # "httpCode"=>200}
619
- raise_insuffient_permission_error!(additional_error_string: content["userString"])
703
+ raise_insufficient_permission_error!(additional_error_string: content["userString"])
620
704
  else
621
705
  store_csrf_tokens(response)
622
706
  content
@@ -626,11 +710,11 @@ module Spaceship
626
710
  def detect_most_common_errors_and_raise_exceptions(body)
627
711
  # Check if the failure is due to missing permissions (App Store Connect)
628
712
  if body["messages"] && body["messages"]["error"].include?("Forbidden")
629
- raise_insuffient_permission_error!
713
+ raise_insufficient_permission_error!
630
714
  elsif body["messages"] && body["messages"]["error"].include?("insufficient privileges")
631
715
  # Passing a specific `caller_location` here to make sure we return the correct method
632
716
  # With the default location the error would say that `parse_response` is the caller
633
- raise_insuffient_permission_error!(caller_location: 3)
717
+ raise_insufficient_permission_error!(caller_location: 3)
634
718
  elsif body.to_s.include?("Internal Server Error - Read")
635
719
  raise InternalServerError, "Received an internal server error from App Store Connect / Developer Portal, please try again later"
636
720
  elsif body.to_s.include?("Gateway Timeout - In read")
@@ -641,7 +725,7 @@ module Spaceship
641
725
  end
642
726
 
643
727
  # This also gets called from subclasses
644
- def raise_insuffient_permission_error!(additional_error_string: nil, caller_location: 2)
728
+ def raise_insufficient_permission_error!(additional_error_string: nil, caller_location: 2)
645
729
  # get the method name of the request that failed
646
730
  # `block in` is used very often for requests when surrounded for paging or retrying blocks
647
731
  # The ! is part of some methods when they modify or delete a resource, so we don't want to show it
@@ -679,19 +763,48 @@ module Spaceship
679
763
  end
680
764
  end
681
765
 
682
- def log_request(method, url, params)
766
+ def log_request(method, url, params, headers = nil, &block)
767
+ url ||= extract_key_from_block('url', &block)
768
+ body = extract_key_from_block('body', &block)
769
+ if body
770
+ begin
771
+ body = JSON.parse(body)
772
+ body['password'] = '***' if body.kind_of?(Hash) && body.key?("password")
773
+ rescue JSON::ParserError
774
+ # no json, no password
775
+ end
776
+ end
683
777
  params_to_log = Hash(params).dup # to also work with nil
684
778
  params_to_log.delete(:accountPassword) # Dev Portal
685
779
  params_to_log.delete(:theAccountPW) # iTC
686
780
  params_to_log = params_to_log.collect do |key, value|
687
781
  "{#{key}: #{value}}"
688
782
  end
689
- logger.info(">> #{method.upcase}: #{url} #{params_to_log.join(', ')}")
783
+ logger.info(">> #{method.upcase} #{url}: #{body.to_json} #{params_to_log.join(', ')}")
690
784
  end
691
785
 
692
- def log_response(method, url, response)
786
+ def log_response(method, url, response, headers = nil, &block)
787
+ url ||= extract_key_from_block('url', &block)
693
788
  body = response.body.kind_of?(String) ? response.body.force_encoding(Encoding::UTF_8) : response.body
694
- logger.debug("<< #{method.upcase}: #{url}: #{body}")
789
+ logger.debug("<< #{method.upcase} #{url}: #{response.status} #{body}")
790
+ end
791
+
792
+ def extract_key_from_block(key, &block)
793
+ if block_given?
794
+ obj = Object.new
795
+ class << obj
796
+ attr_accessor :body, :headers, :params, :url
797
+ # rubocop: disable Style/TrivialAccessors
798
+ # the block calls `url` (not `url=`) so need to define `url` method
799
+ def url(url)
800
+ @url = url
801
+ end
802
+ # rubocop: enable Style/TrivialAccessors
803
+ end
804
+ obj.headers = {}
805
+ yield(obj)
806
+ obj.instance_variable_get("@#{key}")
807
+ end
695
808
  end
696
809
 
697
810
  # Actually sends the request to the remote server
@@ -699,6 +812,8 @@ module Spaceship
699
812
  def send_request(method, url_or_path, params, headers, &block)
700
813
  with_retry do
701
814
  response = @client.send(method, url_or_path, params, headers, &block)
815
+ log_response(method, url_or_path, response, headers, &block)
816
+
702
817
  resp_hash = response.to_hash
703
818
  if resp_hash[:status] == 401
704
819
  msg = "Auth lost"
@@ -709,6 +824,7 @@ module Spaceship
709
824
  if response.body.to_s.include?("<title>302 Found</title>")
710
825
  raise AppleTimeoutError.new, "Apple 302 detected - this might be temporary server error, check https://developer.apple.com/system-status/ to see if there is a known downtime"
711
826
  end
827
+
712
828
  return response
713
829
  end
714
830
  end
@@ -738,4 +854,4 @@ module Spaceship
738
854
  # rubocop:enable Metrics/ClassLength
739
855
  end
740
856
 
741
- require 'spaceship/two_step_client'
857
+ require 'spaceship/two_step_or_factor_client'
@@ -24,6 +24,7 @@ module Spaceship
24
24
  program :help_formatter, :compact
25
25
 
26
26
  global_option('-u', '--user USERNAME', 'Specify the Apple ID you want to log in with')
27
+ global_option('--verbose') { FastlaneCore::Globals.verbose = true }
27
28
 
28
29
  command :playground do |c|
29
30
  c.syntax = 'fastlane spaceship playground'
@@ -101,17 +101,19 @@ module Spaceship
101
101
  def picture_type_map
102
102
  # rubocop:enable Layout/ExtraSpacing
103
103
  {
104
- watch: "MZPFT.SortedN27ScreenShot",
105
- watchSeries4: "MZPFT.SortedN131ScreenShot",
106
104
  ipad: "MZPFT.SortedTabletScreenShot",
107
- ipadPro: "MZPFT.SortedJ99ScreenShot",
108
105
  ipad105: "MZPFT.SortedJ207ScreenShot",
106
+ ipadPro: "MZPFT.SortedJ99ScreenShot",
107
+ ipadPro11: "MZPFT.SortedJ317ScreenShot",
108
+ ipadPro129: "MZPFT.SortedJ320ScreenShot",
109
+ iphone35: "MZPFT.SortedScreenShot",
110
+ iphone4: "MZPFT.SortedN41ScreenShot",
109
111
  iphone6: "MZPFT.SortedN61ScreenShot",
110
112
  iphone6Plus: "MZPFT.SortedN56ScreenShot",
111
113
  iphone58: "MZPFT.SortedD22ScreenShot",
112
114
  iphone65: "MZPFT.SortedD33ScreenShot",
113
- iphone4: "MZPFT.SortedN41ScreenShot",
114
- iphone35: "MZPFT.SortedScreenShot",
115
+ watch: "MZPFT.SortedN27ScreenShot",
116
+ watchSeries4: "MZPFT.SortedN131ScreenShot",
115
117
  appleTV: "MZPFT.SortedATVScreenShot",
116
118
  desktop: "MZPFT.SortedDesktopScreenShot"
117
119
  }
@@ -121,13 +123,15 @@ module Spaceship
121
123
  # rubocop:enable Layout/ExtraSpacing
122
124
  {
123
125
  ipad: "MZPFT.SortedTabletMessagesScreenShot",
124
- ipadPro: "MZPFT.SortedJ99MessagesScreenShot",
125
126
  ipad105: "MZPFT.SortedJ207MessagesScreenShot",
127
+ ipadPro: "MZPFT.SortedJ99MessagesScreenShot",
128
+ ipadPro11: "MZPFT.SortedJ317MessagesScreenShot",
129
+ ipadPro129: "MZPFT.SortedJ320MessagesScreenShot",
130
+ iphone4: "MZPFT.SortedN41MessagesScreenShot",
126
131
  iphone6: "MZPFT.SortedN61MessagesScreenShot",
127
132
  iphone6Plus: "MZPFT.SortedN56MessagesScreenShot",
128
133
  iphone58: "MZPFT.SortedD22MessagesScreenShot",
129
- iphone65: "MZPFT.SortedD33MessagesScreenShot",
130
- iphone4: "MZPFT.SortedN41MessagesScreenShot"
134
+ iphone65: "MZPFT.SortedD33MessagesScreenShot"
131
135
  }
132
136
  end
133
137
 
@@ -137,15 +141,17 @@ module Spaceship
137
141
  watch: [[312, 390]],
138
142
  watchSeries4: [[368, 448]],
139
143
  ipad: [[1024, 748], [1024, 768], [2048, 1496], [2048, 1536], [768, 1004], [768, 1024], [1536, 2008], [1536, 2048]],
140
- ipadPro: [[2048, 2732], [2732, 2048]],
141
144
  ipad105: [[1668, 2224], [2224, 1668]],
145
+ ipadPro: [[2048, 2732], [2732, 2048]],
146
+ ipadPro11: [[1668, 2388], [2388, 1668]],
147
+ ipadPro129: [[2048, 2732], [2732, 2048]],
148
+ iphone35: [[640, 960], [640, 920], [960, 600], [960, 640]],
149
+ iphone4: [[640, 1096], [640, 1136], [1136, 600], [1136, 640]],
142
150
  iphone6: [[750, 1334], [1334, 750]],
143
151
  iphone6Plus: [[1242, 2208], [2208, 1242]],
144
152
  iphone58: [[1125, 2436], [2436, 1125]],
145
153
  iphone65: [[1242, 2688], [2688, 1242]],
146
- iphone4: [[640, 1096], [640, 1136], [1136, 600], [1136, 640]],
147
- iphone35: [[640, 960], [640, 920], [960, 600], [960, 640]],
148
- appleTV: [[1920, 1080]],
154
+ appleTV: [[1920, 1080], [3840, 2160]],
149
155
  desktop: [[1280, 800], [1440, 900], [2560, 1600], [2880, 1800]]
150
156
  }
151
157
  end