fastlane 2.142.0 → 2.146.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +80 -80
  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/fastlane/actions/automatic_code_signing.rb +7 -1
  8. data/fastlane/lib/fastlane/actions/clean_build_artifacts.rb +3 -0
  9. data/fastlane/lib/fastlane/actions/crashlytics.rb +14 -2
  10. data/fastlane/lib/fastlane/actions/create_pull_request.rb +7 -1
  11. data/fastlane/lib/fastlane/actions/docs/capture_ios_screenshots.md +10 -4
  12. data/fastlane/lib/fastlane/actions/docs/frame_screenshots.md +22 -6
  13. data/fastlane/lib/fastlane/actions/docs/sync_code_signing.md +23 -7
  14. data/fastlane/lib/fastlane/actions/frame_screenshots.rb +2 -1
  15. data/fastlane/lib/fastlane/actions/get_version_number.rb +1 -1
  16. data/fastlane/lib/fastlane/actions/pod_lib_lint.rb +7 -1
  17. data/fastlane/lib/fastlane/actions/s3.rb +3 -289
  18. data/fastlane/lib/fastlane/actions/setup_ci.rb +1 -1
  19. data/fastlane/lib/fastlane/actions/setup_jenkins.rb +11 -2
  20. data/fastlane/lib/fastlane/actions/slather.rb +1 -1
  21. data/fastlane/lib/fastlane/actions/swiftlint.rb +28 -7
  22. data/fastlane/lib/fastlane/actions/update_code_signing_settings.rb +203 -0
  23. data/fastlane/lib/fastlane/actions/upload_symbols_to_crashlytics.rb +1 -1
  24. data/fastlane/lib/fastlane/actions/verify_build.rb +1 -1
  25. data/fastlane/lib/fastlane/helper/s3_client_helper.rb +61 -0
  26. data/fastlane/lib/fastlane/version.rb +1 -1
  27. data/fastlane/swift/Deliverfile.swift +1 -1
  28. data/fastlane/swift/Fastlane.swift +147 -8
  29. data/fastlane/swift/Gymfile.swift +1 -1
  30. data/fastlane/swift/Matchfile.swift +1 -1
  31. data/fastlane/swift/MatchfileProtocol.swift +17 -1
  32. data/fastlane/swift/Precheckfile.swift +1 -1
  33. data/fastlane/swift/Scanfile.swift +1 -1
  34. data/fastlane/swift/ScanfileProtocol.swift +5 -1
  35. data/fastlane/swift/Screengrabfile.swift +1 -1
  36. data/fastlane/swift/Snapshotfile.swift +1 -1
  37. data/fastlane_core/lib/fastlane_core/configuration/config_item.rb +9 -0
  38. data/fastlane_core/lib/fastlane_core/device_manager.rb +3 -3
  39. data/fastlane_core/lib/fastlane_core/ipa_file_analyser.rb +1 -0
  40. data/fastlane_core/lib/fastlane_core/provisioning_profile.rb +15 -2
  41. data/frameit/lib/frameit/commands_generator.rb +25 -0
  42. data/frameit/lib/frameit/config_parser.rb +31 -9
  43. data/frameit/lib/frameit/device.rb +90 -0
  44. data/frameit/lib/frameit/device_types.rb +121 -5
  45. data/frameit/lib/frameit/editor.rb +29 -41
  46. data/frameit/lib/frameit/offsets.rb +8 -1
  47. data/frameit/lib/frameit/options.rb +81 -54
  48. data/frameit/lib/frameit/runner.rb +17 -7
  49. data/frameit/lib/frameit/screenshot.rb +39 -47
  50. data/frameit/lib/frameit/template_finder.rb +15 -12
  51. data/gym/lib/gym/generators/package_command_generator.rb +4 -0
  52. data/gym/lib/gym/generators/package_command_generator_xcode7.rb +5 -0
  53. data/gym/lib/gym/runner.rb +14 -0
  54. data/match/lib/match/change_password.rb +1 -1
  55. data/match/lib/match/encryption.rb +4 -0
  56. data/match/lib/match/importer.rb +37 -20
  57. data/match/lib/match/module.rb +1 -1
  58. data/match/lib/match/nuke.rb +5 -1
  59. data/match/lib/match/options.rb +18 -0
  60. data/match/lib/match/runner.rb +4 -0
  61. data/match/lib/match/setup.rb +1 -1
  62. data/match/lib/match/storage.rb +4 -0
  63. data/match/lib/match/storage/s3_storage.rb +167 -0
  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 +5 -0
  70. data/scan/lib/scan/test_command_generator.rb +5 -1
  71. data/screengrab/lib/screengrab/runner.rb +12 -4
  72. data/snapshot/lib/snapshot/reports_generator.rb +4 -0
  73. data/spaceship/lib/spaceship/connect_api/models/app.rb +11 -0
  74. data/spaceship/lib/spaceship/connect_api/models/build.rb +1 -2
  75. data/spaceship/lib/spaceship/connect_api/models/certificate.rb +2 -0
  76. data/spaceship/lib/spaceship/connect_api/testflight/testflight.rb +23 -0
  77. data/spaceship/lib/spaceship/portal/app_service.rb +2 -2
  78. data/spaceship/lib/spaceship/portal/portal_client.rb +13 -0
  79. data/spaceship/lib/spaceship/tunes/app_version.rb +6 -1
  80. data/spaceship/lib/spaceship/tunes/application.rb +2 -1
  81. data/spaceship/lib/spaceship/tunes/tunes_client.rb +2 -2
  82. data/spaceship/lib/spaceship/two_step_or_factor_client.rb +52 -16
  83. data/supply/lib/supply/client.rb +4 -4
  84. data/supply/lib/supply/setup.rb +5 -3
  85. metadata +34 -17
  86. data/gym/lib/gym/.code_signing_mapping.rb.swp +0 -0
@@ -118,9 +118,17 @@ module Pilot
118
118
 
119
119
  # Get latest uploaded build if no build specified
120
120
  if build.nil?
121
- UI.important("No build specified - fetching latest build")
121
+ app_version = config[:app_version]
122
+ build_number = config[:build_number]
123
+ if build_number.nil?
124
+ if app_version.nil?
125
+ UI.important("No build specified - fetching latest build")
126
+ else
127
+ UI.important("No build specified - fetching latest build for version #{app_version}")
128
+ end
129
+ end
122
130
  platform = Spaceship::ConnectAPI::Platform.map(fetch_app_platform)
123
- build ||= Spaceship::ConnectAPI::Build.all(app_id: app.id, sort: "-uploadedDate", platform: platform, limit: 1).first
131
+ build ||= Spaceship::ConnectAPI::Build.all(app_id: app.id, version: app_version, build_number: build_number, sort: "-uploadedDate", platform: platform, limit: 1).first
124
132
  end
125
133
 
126
134
  # Verify the build has all the includes that we need
@@ -207,8 +215,11 @@ module Pilot
207
215
  end
208
216
 
209
217
  def update_beta_app_meta(options, build)
210
- # Setting account required wth AppStore Connect API
211
- update_review_detail(build, { demo_account_required: options[:demo_account_required] })
218
+ # If demo_account_required is a parameter, it should added into beta_app_review_info
219
+ unless options[:demo_account_required].nil?
220
+ options[:beta_app_review_info] = {} if options[:beta_app_review_info].nil?
221
+ options[:beta_app_review_info][:demo_account_required] = options[:demo_account_required]
222
+ end
212
223
 
213
224
  if should_update_beta_app_review_info(options)
214
225
  update_review_detail(build, options[:beta_app_review_info])
@@ -162,6 +162,14 @@ module Pilot
162
162
  env_name: "PILOT_NOTIFY_EXTERNAL_TESTERS",
163
163
  description: "Should notify external testers?",
164
164
  default_value: true),
165
+ FastlaneCore::ConfigItem.new(key: :app_version,
166
+ env_name: "PILOT_APP_VERSION",
167
+ description: "The version number of the application build to distribute. If the version number is not specified, then the most recent build uploaded to TestFlight will be distributed. If specified, the most recent build for the version number will be distributed",
168
+ optional: true),
169
+ FastlaneCore::ConfigItem.new(key: :build_number,
170
+ env_name: "PILOT_BUILD_NUMBER",
171
+ description: "The build number of the application build to distribute. If the build number is not specified, the most recent build is distributed",
172
+ optional: true),
165
173
 
166
174
  # testers
167
175
  FastlaneCore::ConfigItem.new(key: :first_name,
@@ -59,7 +59,7 @@ module Produce
59
59
  app = Spaceship.app.create!(bundle_id: app_identifier,
60
60
  name: app_name,
61
61
  enable_services: enable_services,
62
- mac: Produce.config[:platform] == "osx")
62
+ mac: platform == "osx")
63
63
 
64
64
  if app.name != Produce.config[:app_name]
65
65
  UI.important("Your app name includes non-ASCII characters, which are not supported by the Apple Developer Portal.")
@@ -122,8 +122,17 @@ module Produce
122
122
 
123
123
  private
124
124
 
125
+ def platform
126
+ # This was added to support creation of multiple platforms
127
+ # Produce::ItunesConnect can take an array of platforms to create for App Store Connect
128
+ # but the Developer Center is now platform agnostic so we choose any platform here
129
+ #
130
+ # Platform won't be needed at all in the future when this is change over to use Spaceship::ConnectAPI
131
+ (Produce.config[:platforms] || []).first || Produce.config[:platform]
132
+ end
133
+
125
134
  def app_exists?
126
- Spaceship.app.find(app_identifier, mac: Produce.config[:platform] == "osx") != nil
135
+ Spaceship.app.find(app_identifier, mac: platform == "osx") != nil
127
136
  end
128
137
 
129
138
  def login
@@ -23,6 +23,8 @@ module Produce
23
23
  else
24
24
  UI.success("Creating new app '#{Produce.config[:app_name]}' on App Store Connect")
25
25
 
26
+ platforms = Produce.config[:platforms] || [Produce.config[:platform]]
27
+
26
28
  Produce.config[:bundle_identifier_suffix] = '' unless wildcard_bundle?
27
29
  generated_app = Spaceship::Tunes::Application.create!(name: Produce.config[:app_name],
28
30
  primary_language: language,
@@ -30,7 +32,7 @@ module Produce
30
32
  bundle_id: app_identifier,
31
33
  bundle_id_suffix: Produce.config[:bundle_identifier_suffix],
32
34
  company_name: Produce.config[:company_name],
33
- platform: Produce.config[:platform],
35
+ platforms: platforms,
34
36
  itunes_connect_users: Produce.config[:itc_users])
35
37
 
36
38
  UI.crash!("Something went wrong when creating the new app on iTC") if generated_app["adamId"].to_s.empty?
@@ -51,16 +53,22 @@ module Produce
51
53
  UI.crash!("Something went wrong when creating the new app - it's not listed in the App's list") unless application
52
54
 
53
55
  UI.message("Ensuring version number")
54
- application.ensure_version!(Produce.config[:app_version], platform: Produce.config[:platform]) if Produce.config[:app_version]
56
+ platforms.each do |platform|
57
+ application.ensure_version!(Produce.config[:app_version], platform: platform) if Produce.config[:app_version]
58
+ end
55
59
 
56
60
  UI.success("Successfully created new app '#{Produce.config[:app_name]}' on App Store Connect with ID #{application.apple_id}")
57
61
  end
58
62
 
59
- return Spaceship::Tunes::Application.find(@full_bundle_identifier, mac: Produce.config[:platform] == "osx").apple_id
63
+ return Spaceship::Tunes::Application.find(@full_bundle_identifier, mac: platform == "osx").apple_id
60
64
  end
61
65
 
62
66
  private
63
67
 
68
+ def platform
69
+ (Produce.config[:platforms] || []).first || Produce.config[:platform]
70
+ end
71
+
64
72
  def fetch_application
65
73
  Spaceship::Tunes::Application.find(@full_bundle_identifier)
66
74
  end
@@ -50,11 +50,23 @@ module Produce
50
50
  short_option: "-j",
51
51
  env_name: "PRODUCE_PLATFORM",
52
52
  description: "The platform to use (optional)",
53
+ conflicting_options: [:platforms],
53
54
  optional: true,
54
55
  default_value: "ios",
55
56
  verify_block: proc do |value|
56
57
  UI.user_error!("The platform can only be ios or osx") unless %('ios', 'osx').include?(value)
57
58
  end),
59
+ FastlaneCore::ConfigItem.new(key: :platforms,
60
+ short_option: "-J",
61
+ env_name: "PRODUCE_PLATFORMS",
62
+ description: "The platforms to use (optional)",
63
+ conflicting_options: [:platform],
64
+ optional: true,
65
+ type: Array,
66
+ verify_block: proc do |values|
67
+ types = %w(ios osx)
68
+ UI.user_error!("The platform can only be #{types}") unless (values - types).empty?
69
+ end),
58
70
  FastlaneCore::ConfigItem.new(key: :language,
59
71
  short_option: "-m",
60
72
  env_name: "PRODUCE_LANGUAGE",
@@ -124,6 +124,11 @@ module Scan
124
124
  end),
125
125
 
126
126
  # other test options
127
+ FastlaneCore::ConfigItem.new(key: :testplan,
128
+ env_name: "SCAN_TESTPLAN",
129
+ description: "The testplan associated with the scheme that should be used for testing",
130
+ is_string: true,
131
+ optional: true),
127
132
  FastlaneCore::ConfigItem.new(key: :xctestrun,
128
133
  short_option: "-X",
129
134
  env_name: "SCAN_XCTESTRUN",
@@ -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
 
@@ -151,7 +154,8 @@ module Scan
151
154
 
152
155
  def result_bundle_path
153
156
  unless Scan.cache[:result_bundle_path]
154
- path = File.join(Scan.config[:output_directory], Scan.config[:scheme]) + ".test_result"
157
+ ext = FastlaneCore::Helper.xcode_version.to_i >= 11 ? '.xcresult' : '.test_result'
158
+ path = File.join(Scan.config[:output_directory], Scan.config[:scheme]) + ext
155
159
  if File.directory?(path)
156
160
  FileUtils.remove_dir(path)
157
161
  end
@@ -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)
@@ -143,10 +143,17 @@ module Screengrab
143
143
 
144
144
  def determine_internal_screenshots_paths(app_package_name, locales)
145
145
  locale_paths = locales.map do |locale|
146
- "/data/user/0/#{app_package_name}/files/#{app_package_name}/screengrab/#{locale}/images/screenshots"
147
- end
146
+ [
147
+ "/data/user/0/#{app_package_name}/files/#{app_package_name}/screengrab/#{locale}/images/screenshots",
148
+
149
+ # https://github.com/fastlane/fastlane/issues/15653#issuecomment-578541663
150
+ "/data/data/#{app_package_name}/files/#{app_package_name}/screengrab/#{locale}/images/screenshots"
151
+ ]
152
+ end.flatten
148
153
 
149
- return ["/data/data/#{app_package_name}/app_screengrab"] + locale_paths
154
+ return ["/data/data/#{app_package_name}/app_screengrab"] +
155
+ ["/data/data/#{app_package_name}/screengrab"] +
156
+ locale_paths
150
157
  end
151
158
 
152
159
  def clear_device_previous_screenshots(device_serial, device_screenshots_paths)
@@ -289,6 +296,7 @@ module Screengrab
289
296
  Dir.mktmpdir do |tempdir|
290
297
  device_screenshots_paths.each do |device_path|
291
298
  if_device_path_exists(device_serial, device_path) do |path|
299
+ next unless path.include?(locale)
292
300
  run_adb_command("-s #{device_serial} pull #{path} #{tempdir}",
293
301
  print_all: false,
294
302
  print_command: true)
@@ -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)',
@@ -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
@@ -79,8 +79,7 @@ module Spaceship
79
79
  end
80
80
 
81
81
  def ready_for_internal_testing?
82
- raise "No build_beta_detail included" unless build_beta_detail
83
- return build_beta_detail.ready_for_internal_testing?
82
+ return build_beta_detail.nil? == false && build_beta_detail.ready_for_internal_testing?
84
83
  end
85
84
 
86
85
  def ready_for_beta_submission?
@@ -22,6 +22,8 @@ module Spaceship
22
22
  })
23
23
 
24
24
  module CertificateType
25
+ DEVELOPMENT = "DEVELOPMENT"
26
+ DISTRIBUTION = "DISTRIBUTION"
25
27
  IOS_DEVELOPMENT = "IOS_DEVELOPMENT"
26
28
  IOS_DISTRIBUTION = "IOS_DISTRIBUTION"
27
29
  MAC_APP_DISTRIBUTION = "MAC_APP_DISTRIBUTION"
@@ -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
 
@@ -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
@@ -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