fastlane 2.155.1 → 2.157.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +72 -72
  3. data/deliver/lib/deliver.rb +1 -0
  4. data/deliver/lib/deliver/app_screenshot_iterator.rb +95 -0
  5. data/deliver/lib/deliver/detect_values.rb +4 -1
  6. data/deliver/lib/deliver/languages.rb +7 -0
  7. data/deliver/lib/deliver/loader.rb +4 -5
  8. data/deliver/lib/deliver/queue_worker.rb +64 -0
  9. data/deliver/lib/deliver/runner.rb +7 -5
  10. data/deliver/lib/deliver/upload_screenshots.rb +143 -128
  11. data/fastlane/lib/fastlane/actions/app_store_connect_api_key.rb +120 -0
  12. data/fastlane/lib/fastlane/actions/commit_version_bump.rb +1 -1
  13. data/fastlane/lib/fastlane/actions/docs/upload_to_play_store.md +2 -0
  14. data/fastlane/lib/fastlane/actions/docs/upload_to_testflight.md +17 -1
  15. data/fastlane/lib/fastlane/actions/set_changelog.rb +2 -2
  16. data/fastlane/lib/fastlane/actions/sonar.rb +5 -0
  17. data/fastlane/lib/fastlane/actions/spaceship_stats.rb +73 -0
  18. data/fastlane/lib/fastlane/actions/upload_to_testflight.rb +4 -0
  19. data/fastlane/lib/fastlane/version.rb +1 -1
  20. data/fastlane/swift/Deliverfile.swift +1 -1
  21. data/fastlane/swift/DeliverfileProtocol.swift +1 -1
  22. data/fastlane/swift/Fastlane.swift +68 -8
  23. data/fastlane/swift/Gymfile.swift +1 -1
  24. data/fastlane/swift/GymfileProtocol.swift +1 -1
  25. data/fastlane/swift/Matchfile.swift +1 -1
  26. data/fastlane/swift/MatchfileProtocol.swift +1 -1
  27. data/fastlane/swift/Precheckfile.swift +1 -1
  28. data/fastlane/swift/PrecheckfileProtocol.swift +1 -1
  29. data/fastlane/swift/Scanfile.swift +1 -1
  30. data/fastlane/swift/ScanfileProtocol.swift +1 -1
  31. data/fastlane/swift/Screengrabfile.swift +1 -1
  32. data/fastlane/swift/ScreengrabfileProtocol.swift +1 -1
  33. data/fastlane/swift/Snapshotfile.swift +1 -1
  34. data/fastlane/swift/SnapshotfileProtocol.swift +1 -1
  35. data/fastlane_core/lib/fastlane_core/itunes_transporter.rb +71 -42
  36. data/fastlane_core/lib/fastlane_core/project.rb +1 -0
  37. data/gym/lib/gym/error_handler.rb +1 -1
  38. data/gym/lib/gym/generators/build_command_generator.rb +0 -1
  39. data/pilot/lib/pilot/build_manager.rb +18 -4
  40. data/pilot/lib/pilot/manager.rb +15 -5
  41. data/pilot/lib/pilot/options.rb +16 -0
  42. data/produce/lib/produce/itunes_connect.rb +2 -2
  43. data/scan/lib/scan/test_command_generator.rb +3 -1
  44. data/screengrab/lib/screengrab/runner.rb +8 -7
  45. data/sigh/lib/sigh/runner.rb +9 -5
  46. data/snapshot/lib/snapshot/test_command_generator_base.rb +3 -1
  47. data/spaceship/lib/spaceship.rb +4 -0
  48. data/spaceship/lib/spaceship/client.rb +2 -0
  49. data/spaceship/lib/spaceship/connect_api.rb +0 -15
  50. data/spaceship/lib/spaceship/connect_api/api_client.rb +270 -0
  51. data/spaceship/lib/spaceship/connect_api/client.rb +139 -213
  52. data/spaceship/lib/spaceship/connect_api/models/profile.rb +3 -2
  53. data/spaceship/lib/spaceship/connect_api/provisioning/client.rb +8 -17
  54. data/spaceship/lib/spaceship/connect_api/provisioning/provisioning.rb +75 -64
  55. data/spaceship/lib/spaceship/connect_api/spaceship.rb +94 -0
  56. data/spaceship/lib/spaceship/connect_api/testflight/client.rb +8 -17
  57. data/spaceship/lib/spaceship/connect_api/testflight/testflight.rb +288 -277
  58. data/spaceship/lib/spaceship/connect_api/token.rb +46 -5
  59. data/spaceship/lib/spaceship/connect_api/token_refresh_middleware.rb +24 -0
  60. data/spaceship/lib/spaceship/connect_api/tunes/client.rb +8 -17
  61. data/spaceship/lib/spaceship/connect_api/tunes/tunes.rb +717 -706
  62. data/spaceship/lib/spaceship/connect_api/users/client.rb +8 -17
  63. data/spaceship/lib/spaceship/connect_api/users/users.rb +28 -17
  64. data/spaceship/lib/spaceship/stats_middleware.rb +65 -0
  65. metadata +25 -18
  66. data/sigh/lib/sigh/.runner.rb.swp +0 -0
  67. data/spaceship/lib/spaceship/connect_api/models/.device.rb.swp +0 -0
@@ -320,6 +320,7 @@ module FastlaneCore
320
320
  proj << "-scheme #{options[:scheme].shellescape}" if options[:scheme]
321
321
  proj << "-project #{options[:project].shellescape}" if options[:project]
322
322
  proj << "-configuration #{options[:configuration].shellescape}" if options[:configuration]
323
+ proj << "-derivedDataPath #{options[:derived_data_path].shellescape}" if options[:derived_data_path]
323
324
  proj << "-xcconfig #{options[:xcconfig].shellescape}" if options[:xcconfig]
324
325
 
325
326
  if FastlaneCore::Helper.xcode_at_least?('11.0') && options[:cloned_source_packages_path]
@@ -143,7 +143,7 @@ module Gym
143
143
  # `xcodebuild` doesn't properly mark lines as failure reason or important information
144
144
  # so we assume that the last few lines show the error message that's relevant
145
145
  # (at least that's what was correct during testing)
146
- log_content = File.read(log_path).split("\n")[-5..-1]
146
+ log_content = File.read(log_path).split("\n").last(5)
147
147
  log_content.each do |row|
148
148
  UI.command_output(row)
149
149
  end
@@ -38,7 +38,6 @@ module Gym
38
38
  options << "-toolchain '#{config[:toolchain]}'" if config[:toolchain]
39
39
  options << "-destination '#{config[:destination]}'" if config[:destination]
40
40
  options << "-archivePath #{archive_path.shellescape}" unless config[:skip_archive]
41
- options << "-derivedDataPath '#{config[:derived_data_path]}'" if config[:derived_data_path]
42
41
  options << "-resultBundlePath '#{result_bundle_path}'" if config[:result_bundle]
43
42
  options << config[:xcargs] if config[:xcargs]
44
43
  options << "OTHER_SWIFT_FLAGS=\"-Xfrontend -debug-time-function-bodies\"" if config[:analyze_build_time]
@@ -343,22 +343,36 @@ module Pilot
343
343
  builds_to_expire.each(&:expire!)
344
344
  end
345
345
 
346
+ # If App Store Connect API token, use token.
346
347
  # If itc_provider was explicitly specified, use it.
347
348
  # If there are multiple teams, infer the provider from the selected team name.
348
349
  # If there are fewer than two teams, don't infer the provider.
349
350
  def transporter_for_selected_team(options)
351
+ # Use JWT auth
352
+ unless api_token.nil?
353
+ api_token.refresh! if api_token.expired?
354
+ return FastlaneCore::ItunesTransporter.new(nil, nil, false, nil, api_token.text)
355
+ end
356
+
357
+ # Otherwise use username and password
358
+ tunes_client = Spaceship::ConnectAPI.client ? Spaceship::ConnectAPI.client.tunes_client : nil
359
+
350
360
  generic_transporter = FastlaneCore::ItunesTransporter.new(options[:username], nil, false, options[:itc_provider])
351
- return generic_transporter if options[:itc_provider] || Spaceship::Tunes.client.nil?
352
- return generic_transporter unless Spaceship::Tunes.client.teams.count > 1
361
+ return generic_transporter if options[:itc_provider] || tunes_client.nil?
362
+ return generic_transporter unless tunes_client.teams.count > 1
353
363
 
354
364
  begin
355
- team = Spaceship::Tunes.client.teams.find { |t| t['contentProvider']['contentProviderId'].to_s == Spaceship::Tunes.client.team_id }
365
+ team = tunes_client.teams.find { |t| t['contentProvider']['contentProviderId'].to_s == tunes_client.team_id }
356
366
  name = team['contentProvider']['name']
367
+ STDERR.puts("name: #{name}")
368
+ STDERR.puts("id: #{generic_transporter.provider_ids}")
357
369
  provider_id = generic_transporter.provider_ids[name]
370
+ STDERR.puts("provider_id: #{provider_id}")
358
371
  UI.verbose("Inferred provider id #{provider_id} for team #{name}.")
359
372
  return FastlaneCore::ItunesTransporter.new(options[:username], nil, false, provider_id)
360
373
  rescue => ex
361
- UI.verbose("Couldn't infer a provider short name for team with id #{Spaceship::Tunes.client.team_id} automatically: #{ex}. Proceeding without provider short name.")
374
+ STDERR.puts(ex.to_s)
375
+ UI.verbose("Couldn't infer a provider short name for team with id #{tunes_client.team_id} automatically: #{ex}. Proceeding without provider short name.")
362
376
  return generic_transporter
363
377
  end
364
378
  end
@@ -17,12 +17,22 @@ module Pilot
17
17
  end
18
18
 
19
19
  def login
20
- config[:username] ||= CredentialsManager::AppfileConfig.try_fetch_value(:apple_id)
20
+ if api_token
21
+ UI.message("Creating authorization token for App Store Connect API")
22
+ Spaceship::ConnectAPI.token = api_token
23
+ else
24
+ config[:username] ||= CredentialsManager::AppfileConfig.try_fetch_value(:apple_id)
21
25
 
22
- UI.message("Login to App Store Connect (#{config[:username]})")
23
- Spaceship::Tunes.login(config[:username])
24
- Spaceship::Tunes.select_team(team_id: config[:team_id], team_name: config[:team_name])
25
- UI.message("Login successful")
26
+ UI.message("Login to App Store Connect (#{config[:username]})")
27
+ Spaceship::ConnectAPI.login(config[:username], team_id: config[:team_id], team_name: config[:team_name])
28
+ UI.message("Login successful")
29
+ end
30
+ end
31
+
32
+ def api_token
33
+ @api_token ||= Spaceship::ConnectAPI::Token.create(config[:api_key]) if config[:api_key]
34
+ @api_token ||= Spaceship::ConnectAPI::Token.from_json_file(config[:api_key_path]) if config[:api_key_path]
35
+ return @api_token
26
36
  end
27
37
 
28
38
  # The app object we're currently using
@@ -10,6 +10,22 @@ module Pilot
10
10
  user ||= CredentialsManager::AppfileConfig.try_fetch_value(:apple_id)
11
11
 
12
12
  [
13
+ FastlaneCore::ConfigItem.new(key: :api_key_path,
14
+ env_name: "PILOT_API_KEY_PATH",
15
+ description: "Path to your App Store Connect API key JSON file",
16
+ optional: true,
17
+ conflicting_options: [:username],
18
+ verify_block: proc do |value|
19
+ UI.user_error!("Couldn't find API key JSON file at path '#{value}'") unless File.exist?(value)
20
+ end),
21
+ FastlaneCore::ConfigItem.new(key: :api_key,
22
+ env_name: "PILOT_API_KEY",
23
+ description: "Path to your App Store Connect API key JSON file",
24
+ type: Hash,
25
+ optional: true,
26
+ sensitive: true,
27
+ conflicting_options: [:api_key_path, :username]),
28
+
13
29
  # app upload info
14
30
  FastlaneCore::ConfigItem.new(key: :username,
15
31
  short_option: "-u",
@@ -9,8 +9,8 @@ module Produce
9
9
  @full_bundle_identifier = app_identifier
10
10
  @full_bundle_identifier.gsub!('*', Produce.config[:bundle_identifier_suffix].to_s) if wildcard_bundle?
11
11
 
12
- Spaceship::Tunes.login(Produce.config[:username], nil)
13
- Spaceship::Tunes.client.select_team
12
+ Spaceship::ConnectAPI.login(Produce.config[:username], nil)
13
+ Spaceship::ConnectAPI.client.select_team
14
14
 
15
15
  create_new_app
16
16
  end
@@ -35,7 +35,9 @@ module Scan
35
35
  options << "-sdk '#{config[:sdk]}'" if config[:sdk]
36
36
  options << destination # generated in `detect_values`
37
37
  options << "-toolchain '#{config[:toolchain]}'" if config[:toolchain]
38
- options << "-derivedDataPath '#{config[:derived_data_path]}'" if config[:derived_data_path]
38
+ if config[:derived_data_path] && !options.include?("-derivedDataPath #{config[:derived_data_path].shellescape}")
39
+ options << "-derivedDataPath #{config[:derived_data_path].shellescape}"
40
+ end
39
41
  options << "-resultBundlePath '#{result_bundle_path}'" if config[:result_bundle]
40
42
  if FastlaneCore::Helper.xcode_at_least?(10)
41
43
  options << "-parallel-testing-worker-count #{config[:concurrent_workers]}" if config[:concurrent_workers]
@@ -64,9 +64,10 @@ module Screengrab
64
64
  # Root is needed to access device paths at /data
65
65
  if @config[:use_adb_root]
66
66
  run_adb_command("-s #{device_serial} root", print_all: false, print_command: true)
67
+ run_adb_command("-s #{device_serial} wait-for-device", print_all: false, print_command: true)
67
68
  end
68
69
 
69
- clear_device_previous_screenshots(device_serial, device_screenshots_paths)
70
+ clear_device_previous_screenshots(@config[:app_package_name], device_serial, device_screenshots_paths)
70
71
 
71
72
  app_apk_path ||= select_app_apk(discovered_apk_paths)
72
73
  tests_apk_path ||= select_tests_apk(discovered_apk_paths)
@@ -157,12 +158,12 @@ module Screengrab
157
158
  end.flatten
158
159
  end
159
160
 
160
- def clear_device_previous_screenshots(device_serial, device_screenshots_paths)
161
+ def clear_device_previous_screenshots(app_package_name, device_serial, device_screenshots_paths)
161
162
  UI.message('Cleaning screenshots on device')
162
163
 
163
164
  device_screenshots_paths.each do |device_path|
164
- if_device_path_exists(device_serial, device_path) do |path|
165
- run_adb_command("-s #{device_serial} shell rm -rf #{path}",
165
+ if_device_path_exists(app_package_name, device_serial, device_path) do |path|
166
+ run_adb_command("-s #{device_serial} shell run-as #{app_package_name} rm -rf #{path}",
166
167
  print_all: true,
167
168
  print_command: true)
168
169
  end
@@ -296,7 +297,7 @@ module Screengrab
296
297
 
297
298
  Dir.mktmpdir do |tempdir|
298
299
  device_screenshots_paths.each do |device_path|
299
- if_device_path_exists(device_serial, device_path) do |path|
300
+ if_device_path_exists(@config[:app_package_name], device_serial, device_path) do |path|
300
301
  next unless path.include?(locale)
301
302
  run_adb_command("-s #{device_serial} pull #{path} #{tempdir}",
302
303
  print_all: false,
@@ -361,8 +362,8 @@ module Screengrab
361
362
 
362
363
  # Some device commands fail if executed against a device path that does not exist, so this helper method
363
364
  # provides a way to conditionally execute a block only if the provided path exists on the device.
364
- def if_device_path_exists(device_serial, device_path)
365
- return if run_adb_command("-s #{device_serial} shell ls #{device_path}",
365
+ def if_device_path_exists(app_package_name, device_serial, device_path)
366
+ return if run_adb_command("-s #{device_serial} shell run-as #{app_package_name} ls #{device_path}",
366
367
  print_all: false,
367
368
  print_command: false).include?('No such file')
368
369
 
@@ -18,8 +18,8 @@ module Sigh
18
18
  title: "Summary for sigh #{Fastlane::VERSION}")
19
19
 
20
20
  UI.message("Starting login with user '#{Sigh.config[:username]}'")
21
- Spaceship.login(Sigh.config[:username], nil)
22
- Spaceship.select_team
21
+ Spaceship::ConnectAPI.login(Sigh.config[:username], nil)
22
+ Spaceship::ConnectAPI.select_team
23
23
  UI.message("Successfully logged in")
24
24
 
25
25
  profiles = [] if Sigh.config[:skip_fetch_profiles]
@@ -60,12 +60,12 @@ module Sigh
60
60
  case Sigh.config[:platform]
61
61
  when "ios"
62
62
  @profile_type = Spaceship::ConnectAPI::Profile::ProfileType::IOS_APP_STORE
63
- @profile_type = Spaceship::ConnectAPI::Profile::ProfileType::IOS_APP_INHOUSE if Spaceship.client.in_house?
63
+ @profile_type = Spaceship::ConnectAPI::Profile::ProfileType::IOS_APP_INHOUSE if Spaceship::ConnectAPI.client.in_house?
64
64
  @profile_type = Spaceship::ConnectAPI::Profile::ProfileType::IOS_APP_ADHOC if Sigh.config[:adhoc]
65
65
  @profile_type = Spaceship::ConnectAPI::Profile::ProfileType::IOS_APP_DEVELOPMENT if Sigh.config[:development]
66
66
  when "tvos"
67
67
  @profile_type = Spaceship::ConnectAPI::Profile::ProfileType::TVOS_APP_STORE
68
- @profile_type = Spaceship::ConnectAPI::Profile::ProfileType::TVOS_APP_INHOUSE if Spaceship.client.in_house?
68
+ @profile_type = Spaceship::ConnectAPI::Profile::ProfileType::TVOS_APP_INHOUSE if Spaceship::ConnectAPI.client.in_house?
69
69
  @profile_type = Spaceship::ConnectAPI::Profile::ProfileType::TVOS_APP_ADHOC if Sigh.config[:adhoc]
70
70
  @profile_type = Spaceship::ConnectAPI::Profile::ProfileType::TVOS_APP_DEVELOPMENT if Sigh.config[:development]
71
71
  when "macos"
@@ -172,7 +172,8 @@ module Sigh
172
172
  profile_type: profile_type,
173
173
  bundle_id_id: bundle_id.id,
174
174
  certificate_ids: certificates_to_use.map(&:id),
175
- device_ids: devices_to_use.map(&:id)
175
+ device_ids: devices_to_use.map(&:id),
176
+ template_name: Sigh.config[:template_name]
176
177
  )
177
178
 
178
179
  profile
@@ -228,6 +229,9 @@ module Sigh
228
229
  end
229
230
 
230
231
  def devices_to_use
232
+ # Only use devices if development or adhoc
233
+ return [] if !Sigh.config[:development] && !Sigh.config[:adhoc]
234
+
231
235
  device_class = case Sigh.config[:platform].to_s
232
236
  when 'ios'
233
237
  [
@@ -24,7 +24,9 @@ module Snapshot
24
24
  options = []
25
25
  options += project_path_array
26
26
  options << "-sdk '#{config[:sdk]}'" if config[:sdk]
27
- options << "-derivedDataPath '#{derived_data_path}'"
27
+ if derived_data_path && !options.include?("-derivedDataPath #{derived_data_path.shellescape}")
28
+ options << "-derivedDataPath #{derived_data_path.shellescape}"
29
+ end
28
30
  options << "-resultBundlePath '#{result_bundle_path}'" if result_bundle_path
29
31
  if FastlaneCore::Helper.xcode_at_least?(11)
30
32
  options << "-testPlan '#{config[:testplan]}'" if config[:testplan]
@@ -4,6 +4,9 @@ require_relative 'spaceship/client'
4
4
  require_relative 'spaceship/provider'
5
5
  require_relative 'spaceship/launcher'
6
6
 
7
+ # Middleware
8
+ require_relative 'spaceship/stats_middleware'
9
+
7
10
  # Dev Portal
8
11
  require_relative 'spaceship/portal/portal'
9
12
  require_relative 'spaceship/portal/spaceship'
@@ -13,6 +16,7 @@ require_relative 'spaceship/tunes/tunes'
13
16
  require_relative 'spaceship/tunes/spaceship'
14
17
  require_relative 'spaceship/test_flight'
15
18
  require_relative 'spaceship/connect_api'
19
+ require_relative 'spaceship/connect_api/spaceship'
16
20
  require_relative 'spaceship/spaceauth_runner'
17
21
 
18
22
  require_relative 'spaceship/module'
@@ -16,6 +16,7 @@ require_relative 'errors'
16
16
  require_relative 'tunes/errors'
17
17
  require_relative 'globals'
18
18
  require_relative 'provider'
19
+ require_relative 'stats_middleware'
19
20
 
20
21
  Faraday::Utils.default_params_encoder = Faraday::FlatParamsEncoder
21
22
 
@@ -209,6 +210,7 @@ module Spaceship
209
210
  c.response(:plist, content_type: /\bplist$/)
210
211
  c.use(:cookie_jar, jar: @cookie)
211
212
  c.use(FaradayMiddleware::RelsMiddleware)
213
+ c.use(Spaceship::StatsMiddleware)
212
214
  c.adapter(Faraday.default_adapter)
213
215
 
214
216
  if ENV['SPACESHIP_DEBUG']
@@ -56,21 +56,6 @@ require 'spaceship/connect_api/models/territory'
56
56
 
57
57
  module Spaceship
58
58
  class ConnectAPI
59
- extend Spaceship::ConnectAPI::Provisioning
60
- extend Spaceship::ConnectAPI::TestFlight
61
- extend Spaceship::ConnectAPI::Users
62
- extend Spaceship::ConnectAPI::Tunes
63
-
64
- @token = nil
65
-
66
- class << self
67
- attr_writer(:token)
68
- end
69
-
70
- class << self
71
- attr_reader :token
72
- end
73
-
74
59
  # Defined in the App Store Connect API docs:
75
60
  # https://developer.apple.com/documentation/appstoreconnectapi/platform
76
61
  #
@@ -0,0 +1,270 @@
1
+
2
+ require_relative '../client'
3
+ require_relative './response'
4
+ require_relative '../client'
5
+ require_relative './response'
6
+ require_relative './token_refresh_middleware'
7
+
8
+ require_relative '../stats_middleware'
9
+
10
+ module Spaceship
11
+ class ConnectAPI
12
+ class APIClient < Spaceship::Client
13
+ attr_accessor :token
14
+
15
+ #####################################################
16
+ # @!group Client Init
17
+ #####################################################
18
+
19
+ # Instantiates a client with cookie session or a JWT token.
20
+ def initialize(cookie: nil, current_team_id: nil, token: nil, another_client: nil)
21
+ params_count = [cookie, token, another_client].compact.size
22
+ if params_count != 1
23
+ raise "Must initialize with one of :cookie, :token, or :another_client"
24
+ end
25
+
26
+ if token.nil?
27
+ if another_client.nil?
28
+ super(cookie: cookie, current_team_id: current_team_id, timeout: 1200)
29
+ return
30
+ end
31
+ super(cookie: another_client.instance_variable_get(:@cookie), current_team_id: another_client.team_id)
32
+ else
33
+ options = {
34
+ request: {
35
+ timeout: (ENV["SPACESHIP_TIMEOUT"] || 300).to_i,
36
+ open_timeout: (ENV["SPACESHIP_TIMEOUT"] || 300).to_i
37
+ }
38
+ }
39
+ @token = token
40
+ @current_team_id = current_team_id
41
+
42
+ @client = Faraday.new(hostname, options) do |c|
43
+ c.response(:json, content_type: /\bjson$/)
44
+ c.response(:plist, content_type: /\bplist$/)
45
+ c.use(FaradayMiddleware::RelsMiddleware)
46
+ c.use(Spaceship::StatsMiddleware)
47
+ c.use(Spaceship::TokenRefreshMiddleware, token)
48
+ c.adapter(Faraday.default_adapter)
49
+
50
+ if ENV['SPACESHIP_DEBUG']
51
+ # for debugging only
52
+ # This enables tracking of networking requests using Charles Web Proxy
53
+ c.proxy = "https://127.0.0.1:8888"
54
+ c.ssl[:verify_mode] = OpenSSL::SSL::VERIFY_NONE
55
+ elsif ENV["SPACESHIP_PROXY"]
56
+ c.proxy = ENV["SPACESHIP_PROXY"]
57
+ c.ssl[:verify_mode] = OpenSSL::SSL::VERIFY_NONE if ENV["SPACESHIP_PROXY_SSL_VERIFY_NONE"]
58
+ end
59
+
60
+ if ENV["DEBUG"]
61
+ puts("To run spaceship through a local proxy, use SPACESHIP_DEBUG")
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ # Instance level hostname only used when creating
68
+ # App Store Connect API Farady client.
69
+ # Forwarding to class level if using web session.
70
+ def hostname
71
+ if @token
72
+ return "https://api.appstoreconnect.apple.com/v1/"
73
+ end
74
+ return self.class.hostname
75
+ end
76
+
77
+ def self.hostname
78
+ # Implemented in subclass
79
+ not_implemented(__method__)
80
+ end
81
+
82
+ #
83
+ # Helpers
84
+ #
85
+
86
+ def web_session?
87
+ return @token.nil?
88
+ end
89
+
90
+ def build_params(filter: nil, includes: nil, limit: nil, sort: nil, cursor: nil)
91
+ params = {}
92
+
93
+ filter = filter.delete_if { |k, v| v.nil? } if filter
94
+
95
+ params[:filter] = filter if filter && !filter.empty?
96
+ params[:include] = includes if includes
97
+ params[:limit] = limit if limit
98
+ params[:sort] = sort if sort
99
+ params[:cursor] = cursor if cursor
100
+
101
+ return params
102
+ end
103
+
104
+ def get(url_or_path, params = nil)
105
+ response = with_asc_retry do
106
+ request(:get) do |req|
107
+ req.url(url_or_path)
108
+ req.options.params_encoder = Faraday::NestedParamsEncoder
109
+ req.params = params if params
110
+ req.headers['Content-Type'] = 'application/json'
111
+ end
112
+ end
113
+ handle_response(response)
114
+ end
115
+
116
+ def post(url_or_path, body, tries: 5)
117
+ response = with_asc_retry(tries) do
118
+ request(:post) do |req|
119
+ req.url(url_or_path)
120
+ req.body = body.to_json
121
+ req.headers['Content-Type'] = 'application/json'
122
+ end
123
+ end
124
+ handle_response(response)
125
+ end
126
+
127
+ def patch(url_or_path, body)
128
+ response = with_asc_retry do
129
+ request(:patch) do |req|
130
+ req.url(url_or_path)
131
+ req.body = body.to_json
132
+ req.headers['Content-Type'] = 'application/json'
133
+ end
134
+ end
135
+ handle_response(response)
136
+ end
137
+
138
+ def delete(url_or_path, params = nil, body = nil)
139
+ response = with_asc_retry do
140
+ request(:delete) do |req|
141
+ req.url(url_or_path)
142
+ req.options.params_encoder = Faraday::NestedParamsEncoder if params
143
+ req.params = params if params
144
+ req.body = body.to_json if body
145
+ req.headers['Content-Type'] = 'application/json' if body
146
+ end
147
+ end
148
+ handle_response(response)
149
+ end
150
+
151
+ protected
152
+
153
+ def with_asc_retry(tries = 5, &_block)
154
+ tries = 1 if Object.const_defined?("SpecHelper")
155
+ response = yield
156
+
157
+ status = response.status if response
158
+
159
+ if [500, 504].include?(status)
160
+ msg = "Timeout received! Retrying after 3 seconds (remaining: #{tries})..."
161
+ raise msg
162
+ end
163
+
164
+ return response
165
+ rescue => error
166
+ tries -= 1
167
+ puts(error) if Spaceship::Globals.verbose?
168
+ if tries.zero?
169
+ return response
170
+ else
171
+ retry
172
+ end
173
+ end
174
+
175
+ def handle_response(response)
176
+ if (200...300).cover?(response.status) && (response.body.nil? || response.body.empty?)
177
+ return
178
+ end
179
+
180
+ raise InternalServerError, "Server error got #{response.status}" if (500...600).cover?(response.status)
181
+
182
+ unless response.body.kind_of?(Hash)
183
+ raise UnexpectedResponse, response.body
184
+ end
185
+
186
+ raise UnexpectedResponse, response.body['error'] if response.body['error']
187
+
188
+ raise UnexpectedResponse, handle_errors(response) if response.body['errors']
189
+
190
+ raise UnexpectedResponse, "Temporary App Store Connect error: #{response.body}" if response.body['statusCode'] == 'ERROR'
191
+
192
+ store_csrf_tokens(response)
193
+
194
+ return Spaceship::ConnectAPI::Response.new(body: response.body, status: response.status, client: self)
195
+ end
196
+
197
+ def handle_errors(response)
198
+ # Example error format
199
+ # {
200
+ # "errors":[
201
+ # {
202
+ # "id":"cbfd8674-4802-4857-bfe8-444e1ea36e32",
203
+ # "status":"409",
204
+ # "code":"STATE_ERROR",
205
+ # "title":"The request cannot be fulfilled because of the state of another resource.",
206
+ # "detail":"Submit for review errors found.",
207
+ # "meta":{
208
+ # "associatedErrors":{
209
+ # "/v1/appScreenshots/":[
210
+ # {
211
+ # "id":"23d1734f-b81f-411a-98e4-6d3e763d54ed",
212
+ # "status":"409",
213
+ # "code":"STATE_ERROR.SCREENSHOT_REQUIRED.APP_WATCH_SERIES_4",
214
+ # "title":"App screenshot missing (APP_WATCH_SERIES_4)."
215
+ # },
216
+ # {
217
+ # "id":"db993030-0a93-48e9-9fd7-7e5676633431",
218
+ # "status":"409",
219
+ # "code":"STATE_ERROR.SCREENSHOT_REQUIRED.APP_WATCH_SERIES_4",
220
+ # "title":"App screenshot missing (APP_WATCH_SERIES_4)."
221
+ # }
222
+ # ],
223
+ # "/v1/builds/d710b6fa-5235-4fe4-b791-2b80d6818db0":[
224
+ # {
225
+ # "id":"e421fe6f-0e3b-464b-89dc-ba437e7bb77d",
226
+ # "status":"409",
227
+ # "code":"ENTITY_ERROR.ATTRIBUTE.REQUIRED",
228
+ # "title":"The provided entity is missing a required attribute",
229
+ # "detail":"You must provide a value for the attribute 'usesNonExemptEncryption' with this request",
230
+ # "source":{
231
+ # "pointer":"/data/attributes/usesNonExemptEncryption"
232
+ # }
233
+ # }
234
+ # ]
235
+ # }
236
+ # }
237
+ # }
238
+ # ]
239
+ # }
240
+
241
+ return response.body['errors'].map do |error|
242
+ messages = [[error['title'], error['detail']].compact.join(" - ")]
243
+
244
+ meta = error["meta"] || {}
245
+ associated_errors = meta["associatedErrors"] || {}
246
+
247
+ messages + associated_errors.values.flatten.map do |associated_error|
248
+ [[associated_error["title"], associated_error["detail"]].compact.join(" - ")]
249
+ end
250
+ end.flatten.join("\n")
251
+ end
252
+
253
+ private
254
+
255
+ def local_variable_get(binding, name)
256
+ if binding.respond_to?(:local_variable_get)
257
+ binding.local_variable_get(name)
258
+ else
259
+ binding.eval(name.to_s)
260
+ end
261
+ end
262
+
263
+ def provider_id
264
+ return team_id if self.provider.nil?
265
+ self.provider.provider_id
266
+ end
267
+ end
268
+ end
269
+ # rubocop:enable Metrics/ClassLength
270
+ end