fastlane 2.143.0 → 2.147.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (101) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +82 -82
  3. data/credentials_manager/lib/credentials_manager/appfile_config.rb +4 -0
  4. data/deliver/lib/deliver/app_screenshot.rb +1 -0
  5. data/deliver/lib/deliver/options.rb +30 -1
  6. data/deliver/lib/deliver/setup.rb +4 -4
  7. data/fastlane/lib/assets/custom_action_template.rb +6 -6
  8. data/fastlane/lib/fastlane/actions/automatic_code_signing.rb +7 -1
  9. data/fastlane/lib/fastlane/actions/clean_build_artifacts.rb +3 -0
  10. data/fastlane/lib/fastlane/actions/cocoapods.rb +1 -1
  11. data/fastlane/lib/fastlane/actions/crashlytics.rb +14 -2
  12. data/fastlane/lib/fastlane/actions/create_pull_request.rb +7 -1
  13. data/fastlane/lib/fastlane/actions/docs/capture_ios_screenshots.md +13 -5
  14. data/fastlane/lib/fastlane/actions/docs/frame_screenshots.md +1 -1
  15. data/fastlane/lib/fastlane/actions/docs/sync_code_signing.md +3 -3
  16. data/fastlane/lib/fastlane/actions/get_version_number.rb +1 -1
  17. data/fastlane/lib/fastlane/actions/git_branch.rb +1 -1
  18. data/fastlane/lib/fastlane/actions/pod_lib_lint.rb +7 -1
  19. data/fastlane/lib/fastlane/actions/set_pod_key.rb +3 -3
  20. data/fastlane/lib/fastlane/actions/setup_ci.rb +1 -1
  21. data/fastlane/lib/fastlane/actions/setup_jenkins.rb +11 -2
  22. data/fastlane/lib/fastlane/actions/slather.rb +1 -1
  23. data/fastlane/lib/fastlane/actions/swiftlint.rb +28 -7
  24. data/fastlane/lib/fastlane/actions/testfairy.rb +18 -3
  25. data/fastlane/lib/fastlane/actions/update_code_signing_settings.rb +203 -0
  26. data/fastlane/lib/fastlane/actions/upload_symbols_to_crashlytics.rb +1 -1
  27. data/fastlane/lib/fastlane/actions/verify_build.rb +1 -1
  28. data/fastlane/lib/fastlane/actions/verify_xcode.rb +7 -0
  29. data/fastlane/lib/fastlane/commands_generator.rb +4 -1
  30. data/fastlane/lib/fastlane/helper/lane_helper.rb +13 -0
  31. data/fastlane/lib/fastlane/helper/s3_client_helper.rb +14 -9
  32. data/fastlane/lib/fastlane/version.rb +1 -1
  33. data/fastlane/swift/Deliverfile.swift +1 -1
  34. data/fastlane/swift/Fastlane.swift +138 -23
  35. data/fastlane/swift/Gymfile.swift +1 -1
  36. data/fastlane/swift/Matchfile.swift +1 -1
  37. data/fastlane/swift/MatchfileProtocol.swift +6 -2
  38. data/fastlane/swift/Precheckfile.swift +1 -1
  39. data/fastlane/swift/Scanfile.swift +1 -1
  40. data/fastlane/swift/ScanfileProtocol.swift +10 -2
  41. data/fastlane/swift/Screengrabfile.swift +1 -1
  42. data/fastlane/swift/Snapshotfile.swift +1 -1
  43. data/fastlane/swift/SnapshotfileProtocol.swift +9 -1
  44. data/fastlane_core/lib/fastlane_core/configuration/config_item.rb +9 -0
  45. data/fastlane_core/lib/fastlane_core/device_manager.rb +3 -3
  46. data/fastlane_core/lib/fastlane_core/helper.rb +17 -0
  47. data/fastlane_core/lib/fastlane_core/keychain_importer.rb +46 -2
  48. data/fastlane_core/lib/fastlane_core/provisioning_profile.rb +15 -2
  49. data/frameit/lib/frameit/device_types.rb +10 -0
  50. data/frameit/lib/frameit/editor.rb +1 -1
  51. data/frameit/lib/frameit/options.rb +5 -2
  52. data/frameit/lib/frameit/runner.rb +5 -0
  53. data/frameit/lib/frameit/screenshot.rb +5 -0
  54. data/gym/lib/gym/generators/package_command_generator.rb +4 -0
  55. data/gym/lib/gym/generators/package_command_generator_xcode7.rb +5 -0
  56. data/gym/lib/gym/runner.rb +14 -0
  57. data/match/lib/match/change_password.rb +1 -18
  58. data/match/lib/match/encryption/openssl.rb +1 -1
  59. data/match/lib/match/generator.rb +5 -1
  60. data/match/lib/match/importer.rb +35 -18
  61. data/match/lib/match/options.rb +6 -1
  62. data/match/lib/match/storage/s3_storage.rb +10 -5
  63. data/match/lib/match/utils.rb +1 -1
  64. data/pilot/lib/pilot/build_manager.rb +15 -4
  65. data/pilot/lib/pilot/options.rb +8 -0
  66. data/produce/lib/produce/developer_center.rb +11 -2
  67. data/produce/lib/produce/itunes_connect.rb +11 -3
  68. data/produce/lib/produce/options.rb +12 -0
  69. data/scan/lib/scan/options.rb +11 -1
  70. data/scan/lib/scan/runner.rb +2 -0
  71. data/scan/lib/scan/test_command_generator.rb +4 -1
  72. data/screengrab/lib/screengrab/runner.rb +1 -1
  73. data/snapshot/lib/assets/SnapfileTemplate +3 -0
  74. data/snapshot/lib/snapshot/options.rb +10 -0
  75. data/snapshot/lib/snapshot/reports_generator.rb +4 -0
  76. data/snapshot/lib/snapshot/simulator_launchers/launcher_configuration.rb +2 -0
  77. data/snapshot/lib/snapshot/simulator_launchers/simulator_launcher.rb +4 -0
  78. data/snapshot/lib/snapshot/simulator_launchers/simulator_launcher_base.rb +21 -0
  79. data/snapshot/lib/snapshot/test_command_generator_base.rb +3 -0
  80. data/spaceship/lib/spaceship/base.rb +1 -1
  81. data/spaceship/lib/spaceship/connect_api/model.rb +6 -0
  82. data/spaceship/lib/spaceship/connect_api/models/app.rb +11 -0
  83. data/spaceship/lib/spaceship/connect_api/testflight/testflight.rb +23 -0
  84. data/spaceship/lib/spaceship/portal/app_service.rb +2 -2
  85. data/spaceship/lib/spaceship/portal/portal_client.rb +13 -0
  86. data/spaceship/lib/spaceship/tunes/app_version.rb +6 -1
  87. data/spaceship/lib/spaceship/tunes/application.rb +2 -1
  88. data/spaceship/lib/spaceship/tunes/iap.rb +15 -0
  89. data/spaceship/lib/spaceship/tunes/tunes_client.rb +16 -2
  90. data/spaceship/lib/spaceship/two_step_or_factor_client.rb +52 -16
  91. data/supply/lib/supply/.client.rb.swp +0 -0
  92. data/supply/lib/supply/client.rb +4 -4
  93. data/supply/lib/supply/setup.rb +5 -3
  94. metadata +40 -32
  95. data/fastlane/lib/fastlane/actions/.hockey.rb.swp +0 -0
  96. data/fastlane/lib/fastlane/actions/.slack.rb.swp +0 -0
  97. data/fastlane/lib/fastlane/actions/.update_project_provisioning.rb.swp +0 -0
  98. data/fastlane/swift/FastlaneSwiftRunner/FastlaneSwiftRunner.xcodeproj/project.xcworkspace/xcuserdata/josh.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  99. data/pilot/lib/pilot/.manager.rb.swp +0 -0
  100. data/spaceship/lib/spaceship/connect_api/.DS_Store +0 -0
  101. data/spaceship/lib/spaceship/portal/.certificate.rb.swp +0 -0
@@ -148,6 +148,8 @@ module Scan
148
148
  end
149
149
 
150
150
  def test_results
151
+ return if Scan.config[:disable_xcpretty]
152
+
151
153
  temp_junit_report = Scan.cache[:temp_junit_report]
152
154
  return File.read(temp_junit_report) if temp_junit_report && File.file?(temp_junit_report)
153
155
 
@@ -45,6 +45,9 @@ module Scan
45
45
  options << "-enableCodeCoverage #{config[:code_coverage] ? 'YES' : 'NO'}" unless config[:code_coverage].nil?
46
46
  options << "-enableAddressSanitizer #{config[:address_sanitizer] ? 'YES' : 'NO'}" unless config[:address_sanitizer].nil?
47
47
  options << "-enableThreadSanitizer #{config[:thread_sanitizer] ? 'YES' : 'NO'}" unless config[:thread_sanitizer].nil?
48
+ if FastlaneCore::Helper.xcode_at_least?(11)
49
+ options << "-testPlan '#{config[:testplan]}'" if config[:testplan]
50
+ end
48
51
  options << "-xctestrun '#{config[:xctestrun]}'" if config[:xctestrun]
49
52
  options << config[:xcargs] if config[:xcargs]
50
53
 
@@ -82,7 +85,7 @@ module Scan
82
85
  def pipe
83
86
  pipe = ["| tee '#{xcodebuild_log_path}'"]
84
87
 
85
- if Scan.config[:output_style] == 'raw'
88
+ if Scan.config[:disable_xcpretty] || Scan.config[:output_style] == 'raw'
86
89
  return pipe
87
90
  end
88
91
 
@@ -63,7 +63,7 @@ module Screengrab
63
63
 
64
64
  # Root is needed to access device paths at /data
65
65
  if @config[:use_adb_root]
66
- run_adb_command("root", print_all: false, print_command: true)
66
+ run_adb_command("-s #{device_serial} root", print_all: false, print_command: true)
67
67
  end
68
68
 
69
69
  clear_device_previous_screenshots(device_serial, device_screenshots_paths)
@@ -27,6 +27,9 @@
27
27
  # remove the '#' to clear all previously generated screenshots before creating new ones
28
28
  # clear_previous_screenshots(true)
29
29
 
30
+ # Remove the '#' to set the status bar to 9:41 AM, and show full battery and reception.
31
+ # override_status_bar(true)
32
+
30
33
  # Arguments to pass to the app on launch. See https://docs.fastlane.tools/actions/snapshot/#launch-arguments
31
34
  # launch_arguments(["-favColor red"])
32
35
 
@@ -111,6 +111,11 @@ module Snapshot
111
111
  description: "Enabling this option will automatically erase the simulator before running the application",
112
112
  default_value: false,
113
113
  is_string: false),
114
+ FastlaneCore::ConfigItem.new(key: :override_status_bar,
115
+ env_name: 'SNAPSHOT_OVERRIDE_STATUS_BAR',
116
+ description: "Enabling this option wil automatically override the status bar to show 9:41 AM, full battery, and full reception",
117
+ default_value: false,
118
+ is_string: false),
114
119
  FastlaneCore::ConfigItem.new(key: :localize_simulator,
115
120
  env_name: 'SNAPSHOT_LOCALIZE_SIMULATOR',
116
121
  description: "Enabling this option will configure the Simulator's system language",
@@ -232,6 +237,11 @@ module Snapshot
232
237
  env_name: "SNAPSHOT_CLONED_SOURCE_PACKAGES_PATH",
233
238
  description: "Sets a custom path for Swift Package Manager dependencies",
234
239
  type: String,
240
+ optional: true),
241
+ FastlaneCore::ConfigItem.new(key: :testplan,
242
+ env_name: "SNAPSHOT_TESTPLAN",
243
+ description: "The testplan associated with the scheme that should be used for testing",
244
+ is_string: true,
235
245
  optional: true)
236
246
  ]
237
247
  end
@@ -97,13 +97,17 @@ module Snapshot
97
97
  'iPhone SE' => "iPhone SE",
98
98
  'iPhone 4s' => "iPhone 4s (3.5-Inch)",
99
99
  'iPad 2' => 'iPad 2',
100
+ 'iPad Air (3rd generation)' => 'iPad Air (3rd generation)',
100
101
  'iPad Air 2' => 'iPad Air 2',
101
102
  'iPad Air' => 'iPad Air',
102
103
  'iPad (5th generation)' => 'iPad (5th generation)',
104
+ 'iPad (7th generation)' => 'iPad (7th generation)',
103
105
  'iPad Pro (9.7-inch)' => 'iPad Pro (9.7-inch)',
104
106
  'iPad Pro (9.7 inch)' => 'iPad Pro (9.7-inch)', # iOS 10.3.1 simulator
105
107
  'iPad Pro (10.5-inch)' => 'iPad Pro (10.5-inch)',
108
+ 'iPad Pro (11-inch) (2nd generation)' => 'iPad Pro (11-inch) (2nd generation)',
106
109
  'iPad Pro (11-inch)' => 'iPad Pro (11-inch)',
110
+ 'iPad Pro (12.9-inch) (4th generation)' => 'iPad Pro (12.9-inch) (4th generation)',
107
111
  'iPad Pro (12.9-inch) (3rd generation)' => 'iPad Pro (12.9-inch) (3rd generation)',
108
112
  'iPad Pro (12.9-inch) (2nd generation)' => 'iPad Pro (12.9-inch) (2nd generation)',
109
113
  'iPad Pro (12.9-inch)' => 'iPad Pro (12.9-inch)',
@@ -12,6 +12,7 @@ module Snapshot
12
12
  attr_accessor :reinstall_app
13
13
  attr_accessor :app_identifier
14
14
  attr_accessor :disable_slide_to_type
15
+ attr_accessor :override_status_bar
15
16
 
16
17
  # xcode 8
17
18
  attr_accessor :number_of_retries
@@ -43,6 +44,7 @@ module Snapshot
43
44
  @output_directory = snapshot_config[:output_directory]
44
45
  @concurrent_simulators = snapshot_config[:concurrent_simulators]
45
46
  @disable_slide_to_type = snapshot_config[:disable_slide_to_type]
47
+ @override_status_bar = snapshot_config[:override_status_bar]
46
48
 
47
49
  launch_arguments = Array(snapshot_config[:launch_arguments])
48
50
  # if more than 1 set of arguments, use a tuple with an index
@@ -89,10 +89,14 @@ module Snapshot
89
89
  log_path: xcodebuild_log_path(language: language, locale: locale)
90
90
  )
91
91
 
92
+ devices.each { |device_type| override_status_bar(device_type) } if launcher_config.override_status_bar
93
+
92
94
  UI.important("Running snapshot on: #{devices.join(', ')}")
93
95
 
94
96
  execute(command: command, language: language, locale: locale, launch_args: launch_arguments, devices: devices)
95
97
 
98
+ devices.each { |device_type| clear_status_bar(device_type) } if launcher_config.override_status_bar
99
+
96
100
  return copy_screenshots(language: language, locale: locale, launch_args: launch_arguments)
97
101
  end
98
102
 
@@ -1,4 +1,5 @@
1
1
  require 'plist'
2
+ require 'time'
2
3
 
3
4
  require_relative '../module'
4
5
  require_relative '../test_command_generator'
@@ -102,6 +103,26 @@ module Snapshot
102
103
  end
103
104
  end
104
105
 
106
+ def override_status_bar(device_type)
107
+ device_udid = TestCommandGenerator.device_udid(device_type)
108
+
109
+ UI.message("Launch Simulator #{device_type}")
110
+ Helper.backticks("xcrun instruments -w #{device_udid} &> /dev/null")
111
+
112
+ UI.message("Overriding Status Bar")
113
+
114
+ # The time needs to be passed as ISO8601 so the simulator formats it correctly
115
+ time = Time.new(2007, 1, 9, 9, 41, 0)
116
+ Helper.backticks("xcrun simctl status_bar #{device_udid} override --time #{time.iso8601} --dataNetwork wifi --wifiMode active --wifiBars 3 --cellularMode active --cellularBars 4 --batteryState charged --batteryLevel 100 &> /dev/null")
117
+ end
118
+
119
+ def clear_status_bar(device_type)
120
+ device_udid = TestCommandGenerator.device_udid(device_type)
121
+
122
+ UI.message("Clearing Status Bar Override")
123
+ Helper.backticks("xcrun simctl status_bar #{device_udid} clear &> /dev/null")
124
+ end
125
+
105
126
  def uninstall_app(device_type)
106
127
  launcher_config.app_identifier ||= UI.input("App Identifier: ")
107
128
  device_udid = TestCommandGenerator.device_udid(device_type)
@@ -26,6 +26,9 @@ module Snapshot
26
26
  options << "-sdk '#{config[:sdk]}'" if config[:sdk]
27
27
  options << "-derivedDataPath '#{derived_data_path}'"
28
28
  options << "-resultBundlePath '#{result_bundle_path}'" if result_bundle_path
29
+ if FastlaneCore::Helper.xcode_at_least?(11)
30
+ options << "-testPlan '#{config[:testplan]}'" if config[:testplan]
31
+ end
29
32
  options << config[:xcargs] if config[:xcargs]
30
33
  return options
31
34
  end
@@ -295,7 +295,7 @@ module Spaceship
295
295
  def inspect_value
296
296
  self.attributes.map do |k|
297
297
  v = self.send(k).inspect
298
- v.gsub!("\n", "\n\t") # to align nested elements
298
+ v = v.gsub("\n", "\n\t") # to align nested elements
299
299
 
300
300
  "\t#{k}=#{v}"
301
301
  end.join(", \n")
@@ -49,6 +49,12 @@ module Spaceship
49
49
  alias_method(key_writer, writer)
50
50
  end
51
51
  end
52
+
53
+ def to_json(*options)
54
+ instance_variables.map do |var|
55
+ [var.to_s[1..-1], instance_variable_get(var)]
56
+ end.to_h.to_json(*options)
57
+ end
52
58
  end
53
59
 
54
60
  module Models
@@ -103,6 +103,17 @@ module Spaceship
103
103
  resps = Spaceship::ConnectAPI.get_beta_groups(filter: filter, includes: includes, limit: limit, sort: sort).all_pages
104
104
  return resps.flat_map(&:to_models)
105
105
  end
106
+
107
+ def create_beta_group(group_name: nil, public_link_enabled: false, public_link_limit: 10_000, public_link_limit_enabled: false)
108
+ resps = Spaceship::ConnectAPI.create_beta_group(
109
+ app_id: id,
110
+ group_name: group_name,
111
+ public_link_enabled: public_link_enabled,
112
+ public_link_limit: public_link_limit,
113
+ public_link_limit_enabled: public_link_limit_enabled
114
+ ).all_pages
115
+ return resps.flat_map(&:to_models).first
116
+ end
106
117
  end
107
118
  end
108
119
  end
@@ -181,6 +181,29 @@ module Spaceship
181
181
  Client.instance.post("builds/#{build_id}/relationships/betaGroups", body)
182
182
  end
183
183
 
184
+ def create_beta_group(app_id: nil, group_name: nil, public_link_enabled: false, public_link_limit: 10_000, public_link_limit_enabled: false)
185
+ body = {
186
+ data: {
187
+ attributes: {
188
+ name: group_name,
189
+ publicLinkEnabled: public_link_enabled,
190
+ publicLinkLimit: public_link_limit,
191
+ publicLinkLimitEnabled: public_link_limit_enabled
192
+ },
193
+ relationships: {
194
+ app: {
195
+ data: {
196
+ id: app_id,
197
+ type: "apps"
198
+ }
199
+ }
200
+ },
201
+ type: "betaGroups"
202
+ }
203
+ }
204
+ Client.instance.post("betaGroups", body)
205
+ end
206
+
184
207
  #
185
208
  # betaTesters
186
209
  #
@@ -59,11 +59,11 @@ module Spaceship
59
59
  NetworkExtension = AppService.new_service("NWEXT04537")
60
60
  NFCTagReading = AppService.new_service("NFCTRMAY17")
61
61
  PersonalVPN = AppService.new_service("V66P55NK2I")
62
- Passbook = AppService.new_service("pass")
62
+ Passbook = AppService.new_service("passbook")
63
63
  PushNotification = AppService.new_service("push")
64
64
  SiriKit = AppService.new_service("SI015DKUHP")
65
65
  VPNConfiguration = AppService.new_service("V66P55NK2I")
66
- Wallet = AppService.new_service("pass")
66
+ Wallet = AppService.new_service("passbook")
67
67
  WirelessAccessory = AppService.new_service("WC421J6T7P")
68
68
 
69
69
  constants.each do |c|
@@ -100,6 +100,19 @@ module Spaceship
100
100
  end
101
101
  private :platform_slug
102
102
 
103
+ def list_pending_agreements(language: "en")
104
+ r = request(:post) do |req|
105
+ req.url("account/listPendingAgreements")
106
+ req.body = {
107
+ teamId: team_id,
108
+ languageIsoCode: language
109
+ }.to_json
110
+ req.headers['Content-Type'] = 'application/json'
111
+ end
112
+
113
+ return parse_response(r)
114
+ end
115
+
103
116
  #####################################################
104
117
  # @!group Apps
105
118
  #####################################################
@@ -767,14 +767,19 @@ module Spaceship
767
767
  end
768
768
 
769
769
  def setup_screenshots
770
- # Enable Scaling for all screen sizes that don't have at least one screenshot
770
+ # Enable Scaling for all screen sizes that don't have at least one screenshot or at least one trailer (app_preview)
771
771
  # We automatically disable scaling once we upload at least one screenshot
772
772
  language_details = raw_data_details.each do |current_language|
773
773
  language_details = (current_language["displayFamilies"] || {})["value"]
774
774
  (language_details || []).each do |device_language_details|
775
+ # Do not enable scaling if a screenshot already exists
775
776
  next if device_language_details["screenshots"].nil?
776
777
  next if device_language_details["screenshots"]["value"].count > 0
777
778
 
779
+ # Do not enable scaling if a trailer already exists
780
+ next if device_language_details["trailers"].nil?
781
+ next if device_language_details["trailers"]["value"].count > 0
782
+
778
783
  # The current row includes screenshots for all device types
779
784
  # so we need to enable scaling for both iOS and watchOS apps
780
785
  device_language_details["scaled"]["value"] = true if device_language_details["scaled"]
@@ -91,7 +91,7 @@ module Spaceship
91
91
  # @param platform (String): Platform one of (ios,osx)
92
92
  # should it be an ios or an osx app
93
93
 
94
- def create!(name: nil, primary_language: nil, version: nil, sku: nil, bundle_id: nil, bundle_id_suffix: nil, company_name: nil, platform: nil, itunes_connect_users: nil)
94
+ def create!(name: nil, primary_language: nil, version: nil, sku: nil, bundle_id: nil, bundle_id_suffix: nil, company_name: nil, platform: nil, platforms: nil, itunes_connect_users: nil)
95
95
  puts("The `version` parameter is deprecated. Use `ensure_version!` method instead") if version
96
96
  client.create_application!(name: name,
97
97
  primary_language: primary_language,
@@ -100,6 +100,7 @@ module Spaceship
100
100
  bundle_id_suffix: bundle_id_suffix,
101
101
  company_name: company_name,
102
102
  platform: platform,
103
+ platforms: platforms,
103
104
  itunes_connect_users: itunes_connect_users)
104
105
  end
105
106
 
@@ -132,6 +132,21 @@ module Spaceship
132
132
  return nil
133
133
  end
134
134
 
135
+ # generate app-specific shared secret (or regenerate if exists)
136
+ def generate_shared_secret
137
+ client.generate_shared_secret(app_id: self.application.apple_id)
138
+ end
139
+
140
+ # retrieve app-specific shared secret
141
+ # @param create (Boolean) Create new shared secret if does not exist
142
+ def get_shared_secret(create: false)
143
+ secret = client.get_shared_secret(app_id: self.application.apple_id)
144
+ if create && secret.nil?
145
+ secret = generate_shared_secret
146
+ end
147
+ secret
148
+ end
149
+
135
150
  private
136
151
 
137
152
  def find_product_with_retries(product_id, max_tries)
@@ -286,7 +286,7 @@ module Spaceship
286
286
  # @param sku (String): A unique ID for your app that is not visible on the App Store.
287
287
  # @param bundle_id (String): The bundle ID must match the one you used in Xcode. It
288
288
  # can't be changed after you submit your first build.
289
- def create_application!(name: nil, primary_language: nil, version: nil, sku: nil, bundle_id: nil, bundle_id_suffix: nil, company_name: nil, platform: nil, itunes_connect_users: nil)
289
+ def create_application!(name: nil, primary_language: nil, version: nil, sku: nil, bundle_id: nil, bundle_id_suffix: nil, company_name: nil, platform: nil, platforms: nil, itunes_connect_users: nil)
290
290
  puts("The `version` parameter is deprecated. Use `Spaceship::Tunes::Application.ensure_version!` method instead") if version
291
291
 
292
292
  # First, we need to fetch the data from Apple, which we then modify with the user's values
@@ -307,7 +307,7 @@ module Spaceship
307
307
  data['enabledPlatformsForCreation'] = { value: [platform] }
308
308
 
309
309
  data['initialPlatform'] = platform
310
- data['enabledPlatformsForCreation'] = { value: [platform] }
310
+ data['enabledPlatformsForCreation'] = { value: platforms || [platform] }
311
311
 
312
312
  unless itunes_connect_users.nil?
313
313
  data['iTunesConnectUsers']['grantedAllUsers'] = false
@@ -1422,6 +1422,20 @@ module Spaceship
1422
1422
  handle_itc_response(r.body)
1423
1423
  end
1424
1424
 
1425
+ # Retrieves app-specific shared secret key
1426
+ def get_shared_secret(app_id: nil)
1427
+ r = request(:get, "ra/apps/#{app_id}/iaps/appSharedSecret")
1428
+ data = parse_response(r, 'data')
1429
+ data['sharedSecret']
1430
+ end
1431
+
1432
+ # Generates app-specific shared secret key
1433
+ def generate_shared_secret(app_id: nil)
1434
+ r = request(:post, "ra/apps/#{app_id}/iaps/appSharedSecret")
1435
+ data = parse_response(r, 'data')
1436
+ data['sharedSecret']
1437
+ end
1438
+
1425
1439
  #####################################################
1426
1440
  # @!group Sandbox Testers
1427
1441
  #####################################################
@@ -131,18 +131,33 @@ module Spaceship
131
131
  puts("Environment variable `SPACESHIP_2FA_SMS_DEFAULT_PHONE_NUMBER` is set, automatically requesting 2FA token via SMS to that number")
132
132
  puts("SPACESHIP_2FA_SMS_DEFAULT_PHONE_NUMBER = #{env_2fa_sms_default_phone_number}")
133
133
  puts("")
134
+
134
135
  phone_number = env_2fa_sms_default_phone_number
135
136
  phone_id = phone_id_from_number(response.body["trustedPhoneNumbers"], phone_number)
137
+ # don't request sms if no trusted devices and env default is the only trusted number,
138
+ # code was automatically sent
139
+ should_request_code = !sms_automatically_sent(response)
140
+ code_type = 'phone'
141
+ body = request_two_factor_code_from_phone(phone_id, phone_number, code_length, should_request_code)
142
+ elsif sms_automatically_sent(response) # sms fallback, code was automatically sent
143
+ fallback_number = response.body["trustedPhoneNumbers"].first
144
+ phone_number = fallback_number["numberWithDialCode"]
145
+ phone_id = fallback_number["id"]
146
+
147
+ code_type = 'phone'
148
+ body = request_two_factor_code_from_phone(phone_id, phone_number, code_length, false)
149
+ elsif sms_fallback(response) # sms fallback but code wasn't sent bec > 1 phone number
136
150
  code_type = 'phone'
137
- body = request_two_factor_code_from_phone(phone_id, phone_number, code_length)
151
+ body = request_two_factor_code_from_phone_choose(response.body["trustedPhoneNumbers"], code_length)
138
152
  else
139
153
  puts("(Input `sms` to escape this prompt and select a trusted phone number to send the code as a text message)")
140
154
  puts("")
141
155
  puts("(You can also set the environment variable `SPACESHIP_2FA_SMS_DEFAULT_PHONE_NUMBER` to automate this)")
142
- puts("(Read more at: https://github.com/fastlane/fastlane/blob/master/spaceship/docs/Authentication.md#auto-select-sms-via-spaceship-2fa-sms-default-phone-number)")
156
+ puts("(Read more at: https://github.com/fastlane/fastlane/blob/master/spaceship/docs/Authentication.md#auto-select-sms-via-spaceship_2fa_sms_default_phone_number)")
143
157
  puts("")
144
- code_type = 'trusteddevice'
158
+
145
159
  code = ask_for_2fa_code("Please enter the #{code_length} digit code:")
160
+ code_type = 'trusteddevice'
146
161
  body = { "securityCode" => { "code" => code.to_s } }.to_json
147
162
 
148
163
  # User exited by entering `sms` and wants to choose phone number for SMS
@@ -191,11 +206,30 @@ module Spaceship
191
206
  return true
192
207
  end
193
208
 
209
+ # For reference in case auth behavior changes:
210
+ # The "noTrustedDevices" field is only present
211
+ # in the response for `GET /appleauth/auth`
212
+
213
+ # Account is not signed into any devices that can display a verification code
214
+ def sms_fallback(response)
215
+ response.body["noTrustedDevices"]
216
+ end
217
+
218
+ # see `sms_fallback` + account has only one trusted number for receiving an sms
219
+ def sms_automatically_sent(response)
220
+ (response.body["trustedPhoneNumbers"] || []).count == 1 && sms_fallback(response)
221
+ end
222
+
194
223
  # extracted into its own method for testing
195
224
  def ask_for_2fa_code(text)
196
225
  ask(text)
197
226
  end
198
227
 
228
+ # extracted into its own method for testing
229
+ def choose_phone_number(opts)
230
+ choose(*opts)
231
+ end
232
+
199
233
  def phone_id_from_number(phone_numbers, phone_number)
200
234
  characters_to_remove_from_phone_numbers = ' \-()"'
201
235
 
@@ -247,27 +281,29 @@ If it is, please open an issue at https://github.com/fastlane/fastlane/issues/ne
247
281
  available = phone_numbers.collect do |current|
248
282
  current['numberWithDialCode']
249
283
  end
250
- chosen = choose(*available)
284
+ chosen = choose_phone_number(available)
251
285
  phone_id = phone_id_from_masked_number(phone_numbers, chosen)
252
286
 
253
287
  request_two_factor_code_from_phone(phone_id, chosen, code_length)
254
288
  end
255
289
 
256
290
  # this is used in two places: after choosing a phone number and when a phone number is set via ENV var
257
- def request_two_factor_code_from_phone(phone_id, phone_number, code_length)
258
- # Request code
259
- r = request(:put) do |req|
260
- req.url("https://idmsa.apple.com/appleauth/auth/verify/phone")
261
- req.headers['Content-Type'] = 'application/json'
262
- req.body = { "phoneNumber" => { "id" => phone_id }, "mode" => "sms" }.to_json
263
- update_request_headers(req)
264
- end
291
+ def request_two_factor_code_from_phone(phone_id, phone_number, code_length, should_request_code = true)
292
+ if should_request_code
293
+ # Request code
294
+ r = request(:put) do |req|
295
+ req.url("https://idmsa.apple.com/appleauth/auth/verify/phone")
296
+ req.headers['Content-Type'] = 'application/json'
297
+ req.body = { "phoneNumber" => { "id" => phone_id }, "mode" => "sms" }.to_json
298
+ update_request_headers(req)
299
+ end
265
300
 
266
- # we use `Spaceship::TunesClient.new.handle_itc_response`
267
- # since this might be from the Dev Portal, but for 2 step
268
- Spaceship::TunesClient.new.handle_itc_response(r.body)
301
+ # we use `Spaceship::TunesClient.new.handle_itc_response`
302
+ # since this might be from the Dev Portal, but for 2 step
303
+ Spaceship::TunesClient.new.handle_itc_response(r.body)
269
304
 
270
- puts("Successfully requested text message to #{phone_number}")
305
+ puts("Successfully requested text message to #{phone_number}")
306
+ end
271
307
 
272
308
  code = ask_for_2fa_code("Please enter the #{code_length} digit code you received at #{phone_number}:")
273
309