fastlane 2.156.1 → 2.157.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +80 -80
  3. data/deliver/lib/deliver.rb +1 -0
  4. data/deliver/lib/deliver/app_screenshot_iterator.rb +26 -29
  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/runner.rb +7 -5
  9. data/deliver/lib/deliver/upload_screenshots.rb +34 -17
  10. data/fastlane/lib/fastlane/actions/app_store_connect_api_key.rb +120 -0
  11. data/fastlane/lib/fastlane/actions/commit_version_bump.rb +1 -1
  12. data/fastlane/lib/fastlane/actions/docs/upload_to_play_store.md +2 -0
  13. data/fastlane/lib/fastlane/actions/docs/upload_to_testflight.md +17 -1
  14. data/fastlane/lib/fastlane/actions/set_changelog.rb +2 -2
  15. data/fastlane/lib/fastlane/actions/sonar.rb +5 -0
  16. data/fastlane/lib/fastlane/actions/spaceship_stats.rb +73 -0
  17. data/fastlane/lib/fastlane/actions/upload_to_testflight.rb +4 -0
  18. data/fastlane/lib/fastlane/version.rb +1 -1
  19. data/fastlane/swift/Deliverfile.swift +1 -1
  20. data/fastlane/swift/DeliverfileProtocol.swift +1 -1
  21. data/fastlane/swift/Fastlane.swift +68 -8
  22. data/fastlane/swift/Gymfile.swift +1 -1
  23. data/fastlane/swift/GymfileProtocol.swift +1 -1
  24. data/fastlane/swift/Matchfile.swift +1 -1
  25. data/fastlane/swift/MatchfileProtocol.swift +1 -1
  26. data/fastlane/swift/Precheckfile.swift +1 -1
  27. data/fastlane/swift/PrecheckfileProtocol.swift +1 -1
  28. data/fastlane/swift/Scanfile.swift +1 -1
  29. data/fastlane/swift/ScanfileProtocol.swift +1 -1
  30. data/fastlane/swift/Screengrabfile.swift +1 -1
  31. data/fastlane/swift/ScreengrabfileProtocol.swift +1 -1
  32. data/fastlane/swift/Snapshotfile.swift +1 -1
  33. data/fastlane/swift/SnapshotfileProtocol.swift +1 -1
  34. data/fastlane_core/lib/fastlane_core/itunes_transporter.rb +71 -42
  35. data/gym/lib/gym/error_handler.rb +1 -1
  36. data/pilot/lib/pilot/build_manager.rb +18 -4
  37. data/pilot/lib/pilot/manager.rb +15 -5
  38. data/pilot/lib/pilot/options.rb +16 -0
  39. data/produce/lib/produce/itunes_connect.rb +2 -2
  40. data/screengrab/lib/screengrab/runner.rb +1 -0
  41. data/sigh/lib/sigh/runner.rb +4 -4
  42. data/spaceship/lib/spaceship.rb +4 -0
  43. data/spaceship/lib/spaceship/client.rb +2 -0
  44. data/spaceship/lib/spaceship/connect_api.rb +0 -15
  45. data/spaceship/lib/spaceship/connect_api/api_client.rb +270 -0
  46. data/spaceship/lib/spaceship/connect_api/client.rb +139 -213
  47. data/spaceship/lib/spaceship/connect_api/provisioning/client.rb +8 -17
  48. data/spaceship/lib/spaceship/connect_api/provisioning/provisioning.rb +75 -64
  49. data/spaceship/lib/spaceship/connect_api/spaceship.rb +94 -0
  50. data/spaceship/lib/spaceship/connect_api/testflight/client.rb +8 -17
  51. data/spaceship/lib/spaceship/connect_api/testflight/testflight.rb +288 -277
  52. data/spaceship/lib/spaceship/connect_api/token.rb +46 -5
  53. data/spaceship/lib/spaceship/connect_api/token_refresh_middleware.rb +24 -0
  54. data/spaceship/lib/spaceship/connect_api/tunes/client.rb +8 -17
  55. data/spaceship/lib/spaceship/connect_api/tunes/tunes.rb +717 -706
  56. data/spaceship/lib/spaceship/connect_api/users/client.rb +8 -17
  57. data/spaceship/lib/spaceship/connect_api/users/users.rb +28 -17
  58. data/spaceship/lib/spaceship/stats_middleware.rb +65 -0
  59. metadata +25 -19
  60. data/spaceship/lib/spaceship/connect_api/.client.rb.swp +0 -0
@@ -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
@@ -1,245 +1,171 @@
1
- require_relative '../client'
2
- require_relative './response'
1
+ require_relative './token'
2
+ require_relative './provisioning/provisioning'
3
+ require_relative './testflight/testflight'
4
+ require_relative './tunes/tunes'
5
+ require_relative './users/users'
3
6
 
4
7
  module Spaceship
5
8
  class ConnectAPI
6
- class Client < Spaceship::Client
9
+ class Client
7
10
  attr_accessor :token
11
+ attr_accessor :tunes_client
12
+ attr_accessor :portal_client
8
13
 
9
- #####################################################
10
- # @!group Client Init
11
- #####################################################
12
-
13
- # Instantiates a client with cookie session or a JWT token.
14
- def initialize(cookie: nil, current_team_id: nil, token: nil)
15
- if token.nil?
16
- super(cookie: cookie, current_team_id: current_team_id, timeout: 1200)
17
- else
18
- options = {
19
- request: {
20
- timeout: (ENV["SPACESHIP_TIMEOUT"] || 300).to_i,
21
- open_timeout: (ENV["SPACESHIP_TIMEOUT"] || 300).to_i
22
- }
23
- }
24
- @token = token
25
- @current_team_id = current_team_id
26
-
27
- hostname = "https://api.appstoreconnect.apple.com/v1/"
28
-
29
- @client = Faraday.new(hostname, options) do |c|
30
- c.response(:json, content_type: /\bjson$/)
31
- c.response(:plist, content_type: /\bplist$/)
32
- c.use(FaradayMiddleware::RelsMiddleware)
33
- c.adapter(Faraday.default_adapter)
34
- c.headers["Authorization"] = "Bearer #{token.text}"
35
-
36
- if ENV['SPACESHIP_DEBUG']
37
- # for debugging only
38
- # This enables tracking of networking requests using Charles Web Proxy
39
- c.proxy = "https://127.0.0.1:8888"
40
- c.ssl[:verify_mode] = OpenSSL::SSL::VERIFY_NONE
41
- elsif ENV["SPACESHIP_PROXY"]
42
- c.proxy = ENV["SPACESHIP_PROXY"]
43
- c.ssl[:verify_mode] = OpenSSL::SSL::VERIFY_NONE if ENV["SPACESHIP_PROXY_SSL_VERIFY_NONE"]
44
- end
45
-
46
- if ENV["DEBUG"]
47
- puts("To run spaceship through a local proxy, use SPACESHIP_DEBUG")
48
- end
49
- end
50
- end
51
- end
52
-
53
- def self.hostname
54
- return nil
55
- end
56
-
14
+ # Initializes client with Apple's App Store Connect JWT auth key.
57
15
  #
58
- # Helpers
16
+ # This method will automatically use the key id, issuer id, and filepath from environment
17
+ # variables if not given.
59
18
  #
60
-
61
- def web_session?
62
- return @token.nil?
63
- end
64
-
65
- def build_params(filter: nil, includes: nil, limit: nil, sort: nil, cursor: nil)
66
- params = {}
67
-
68
- filter = filter.delete_if { |k, v| v.nil? } if filter
69
-
70
- params[:filter] = filter if filter && !filter.empty?
71
- params[:include] = includes if includes
72
- params[:limit] = limit if limit
73
- params[:sort] = sort if sort
74
- params[:cursor] = cursor if cursor
75
-
76
- return params
19
+ # All three parameters are needed to authenticate.
20
+ #
21
+ # @param key_id (String) (optional): The key id
22
+ # @param issuer_id (String) (optional): The issuer id
23
+ # @param filepath (String) (optional): The filepath
24
+ #
25
+ # @raise InvalidUserCredentialsError: raised if authentication failed
26
+ #
27
+ # @return (Spaceship::ConnectAPI::Client) The client the login method was called for
28
+ def self.auth(key_id: nil, issuer_id: nil, filepath: nil)
29
+ token = Spaceship::ConnectAPI::Token.create(key_id: key_id, issuer_id: issuer_id, filepath: filepath)
30
+ return ConnectAPI::Client.new(token: token)
77
31
  end
78
32
 
79
- def get(url_or_path, params = nil)
80
- response = with_asc_retry do
81
- request(:get) do |req|
82
- req.url(url_or_path)
83
- req.options.params_encoder = Faraday::NestedParamsEncoder
84
- req.params = params if params
85
- req.headers['Content-Type'] = 'application/json'
86
- end
33
+ # Authenticates with Apple's web services. This method has to be called once
34
+ # to generate a valid session.
35
+ #
36
+ # This method will automatically use the username from the Appfile (if available)
37
+ # and fetch the password from the Keychain (if available)
38
+ #
39
+ # @param user (String) (optional): The username (usually the email address)
40
+ # @param password (String) (optional): The password
41
+ # @param team_id (String) (optional): The team id
42
+ # @param team_name (String) (optional): The team name
43
+ #
44
+ # @raise InvalidUserCredentialsError: raised if authentication failed
45
+ #
46
+ # @return (Spaceship::ConnectAPI::Client) The client the login method was called for
47
+ def self.login(user = nil, password = nil, team_id: nil, team_name: nil)
48
+ tunes_client = TunesClient.login(user, password)
49
+ portal_client = PortalClient.login(user, password)
50
+
51
+ # The clients will automatically select the first team if none is given
52
+ if !team_id.nil? || !team_name.nil?
53
+ tunes_client.select_team(team_id: team_id, team_name: team_name)
54
+ portal_client.select_team(team_id: team_id, team_name: team_name)
87
55
  end
88
- handle_response(response)
89
- end
90
56
 
91
- def post(url_or_path, body, tries: 5)
92
- response = with_asc_retry(tries) do
93
- request(:post) do |req|
94
- req.url(url_or_path)
95
- req.body = body.to_json
96
- req.headers['Content-Type'] = 'application/json'
97
- end
98
- end
99
- handle_response(response)
57
+ return ConnectAPI::Client.new(tunes_client: tunes_client, portal_client: portal_client)
100
58
  end
101
59
 
102
- def patch(url_or_path, body)
103
- response = with_asc_retry do
104
- request(:patch) do |req|
105
- req.url(url_or_path)
106
- req.body = body.to_json
107
- req.headers['Content-Type'] = 'application/json'
108
- end
109
- end
110
- handle_response(response)
60
+ def initialize(cookie: nil, current_team_id: nil, token: nil, tunes_client: nil, portal_client: nil)
61
+ @token = token
62
+
63
+ # If using web session...
64
+ # Spaceship::Tunes is needed for TestFlight::API, Tunes::API, and Users::API
65
+ # Spaceship::Portal is needed for Provisioning::API
66
+ @tunes_client = tunes_client
67
+ @portal_client = portal_client
68
+
69
+ # Extending this instance to add API endpoints from these modules
70
+ # Each of these modules adds a new setter method for an instance
71
+ # of an ConnectAPI::APIClient
72
+ # These get set in set_indvidual_clients
73
+ self.extend(Spaceship::ConnectAPI::TestFlight::API)
74
+ self.extend(Spaceship::ConnectAPI::Tunes::API)
75
+ self.extend(Spaceship::ConnectAPI::Provisioning::API)
76
+ self.extend(Spaceship::ConnectAPI::Users::API)
77
+
78
+ set_indvidual_clients(
79
+ cookie: cookie,
80
+ current_team_id: current_team_id,
81
+ token: token,
82
+ tunes_client: @tunes_client,
83
+ portal_client: @portal_client
84
+ )
111
85
  end
112
86
 
113
- def delete(url_or_path, params = nil, body = nil)
114
- response = with_asc_retry do
115
- request(:delete) do |req|
116
- req.url(url_or_path)
117
- req.options.params_encoder = Faraday::NestedParamsEncoder if params
118
- req.params = params if params
119
- req.body = body.to_json if body
120
- req.headers['Content-Type'] = 'application/json' if body
87
+ def in_house?
88
+ if token
89
+ if token.in_house.nil?
90
+ message = [
91
+ "Cannot determine if team is App Store or Enterprise via the App Store Connect API (yet)",
92
+ "Set 'in_house' on your Spaceship::ConnectAPI::Token",
93
+ "Or set 'in_house' in your App Store Connect API key JSON file",
94
+ "Or set the 'SPACESHIP_CONNECT_API_IN_HOUSE' environment variable to 'true'",
95
+ "View more info in the docs at https://docs.fastlane.tools/app-store-connect-api/"
96
+ ]
97
+ raise message.join('\n')
121
98
  end
99
+ return !!token.in_house
100
+ elsif @portal_client
101
+ return @portal_client.in_house?
102
+ else
103
+ raise "No App Store Connect API token or Portal Client set"
122
104
  end
123
- handle_response(response)
124
105
  end
125
106
 
126
- protected
127
-
128
- def with_asc_retry(tries = 5, &_block)
129
- tries = 1 if Object.const_defined?("SpecHelper")
130
- response = yield
107
+ def select_team(team_id: nil, team_name: nil)
108
+ @tunes_client.select_team(team_id: team_id, team_name: team_name)
109
+ @portal_client.select_team(team_id: team_id, team_name: team_name)
110
+
111
+ # Updating the tunes and portal clients requires resetting
112
+ # of the clients in the API modules
113
+ set_indvidual_clients(
114
+ cookie: nil,
115
+ current_team_id: nil,
116
+ token: nil,
117
+ tunes_client: tunes_client,
118
+ portal_client: portal_client
119
+ )
120
+ end
131
121
 
132
- status = response.status if response
122
+ private
133
123
 
134
- if [500, 504].include?(status)
135
- msg = "Timeout received! Retrying after 3 seconds (remaining: #{tries})..."
136
- raise msg
124
+ def set_indvidual_clients(cookie: nil, current_team_id: nil, token: nil, tunes_client: nil, portal_client: nil)
125
+ # This was added by Spaceship::ConnectAPI::TestFlight::API and is required
126
+ # to be set for API methods to have a client to send request on
127
+ if cookie || token || tunes_client
128
+ self.test_flight_request_client = Spaceship::ConnectAPI::TestFlight::Client.new(
129
+ cookie: cookie,
130
+ current_team_id: current_team_id,
131
+ token: token,
132
+ another_client: tunes_client
133
+ )
137
134
  end
138
135
 
139
- return response
140
- rescue => error
141
- tries -= 1
142
- puts(error) if Spaceship::Globals.verbose?
143
- if tries.zero?
144
- return response
145
- else
146
- retry
136
+ # This was added by Spaceship::ConnectAPI::Tunes::API and is required
137
+ # to be set for API methods to have a client to send request on
138
+ if cookie || token || tunes_client
139
+ self.tunes_request_client = Spaceship::ConnectAPI::Tunes::Client.new(
140
+ cookie: cookie,
141
+ current_team_id: current_team_id,
142
+ token: token,
143
+ another_client: tunes_client
144
+ )
147
145
  end
148
- end
149
146
 
150
- def handle_response(response)
151
- if (200...300).cover?(response.status) && (response.body.nil? || response.body.empty?)
152
- return
147
+ # This was added by Spaceship::ConnectAPI::Provisioning::API and is required
148
+ # to be set for API methods to have a client to send request on
149
+ if cookie || token || portal_client
150
+ self.provisioning_request_client = Spaceship::ConnectAPI::Provisioning::Client.new(
151
+ cookie: cookie,
152
+ current_team_id: current_team_id,
153
+ token: token,
154
+ another_client: portal_client
155
+ )
153
156
  end
154
157
 
155
- raise InternalServerError, "Server error got #{response.status}" if (500...600).cover?(response.status)
156
-
157
- unless response.body.kind_of?(Hash)
158
- raise UnexpectedResponse, response.body
158
+ # This was added by Spaceship::ConnectAPI::Users::API and is required
159
+ # to be set for API methods to have a client to send request on
160
+ if cookie || token || tunes_client
161
+ self.users_request_client = Spaceship::ConnectAPI::Users::Client.new(
162
+ cookie: cookie,
163
+ current_team_id: current_team_id,
164
+ token: token,
165
+ another_client: tunes_client
166
+ )
159
167
  end
160
-
161
- raise UnexpectedResponse, response.body['error'] if response.body['error']
162
-
163
- raise UnexpectedResponse, handle_errors(response) if response.body['errors']
164
-
165
- raise UnexpectedResponse, "Temporary App Store Connect error: #{response.body}" if response.body['statusCode'] == 'ERROR'
166
-
167
- store_csrf_tokens(response)
168
-
169
- return Spaceship::ConnectAPI::Response.new(body: response.body, status: response.status, client: self)
170
- end
171
-
172
- def handle_errors(response)
173
- # Example error format
174
- # {
175
- # "errors":[
176
- # {
177
- # "id":"cbfd8674-4802-4857-bfe8-444e1ea36e32",
178
- # "status":"409",
179
- # "code":"STATE_ERROR",
180
- # "title":"The request cannot be fulfilled because of the state of another resource.",
181
- # "detail":"Submit for review errors found.",
182
- # "meta":{
183
- # "associatedErrors":{
184
- # "/v1/appScreenshots/":[
185
- # {
186
- # "id":"23d1734f-b81f-411a-98e4-6d3e763d54ed",
187
- # "status":"409",
188
- # "code":"STATE_ERROR.SCREENSHOT_REQUIRED.APP_WATCH_SERIES_4",
189
- # "title":"App screenshot missing (APP_WATCH_SERIES_4)."
190
- # },
191
- # {
192
- # "id":"db993030-0a93-48e9-9fd7-7e5676633431",
193
- # "status":"409",
194
- # "code":"STATE_ERROR.SCREENSHOT_REQUIRED.APP_WATCH_SERIES_4",
195
- # "title":"App screenshot missing (APP_WATCH_SERIES_4)."
196
- # }
197
- # ],
198
- # "/v1/builds/d710b6fa-5235-4fe4-b791-2b80d6818db0":[
199
- # {
200
- # "id":"e421fe6f-0e3b-464b-89dc-ba437e7bb77d",
201
- # "status":"409",
202
- # "code":"ENTITY_ERROR.ATTRIBUTE.REQUIRED",
203
- # "title":"The provided entity is missing a required attribute",
204
- # "detail":"You must provide a value for the attribute 'usesNonExemptEncryption' with this request",
205
- # "source":{
206
- # "pointer":"/data/attributes/usesNonExemptEncryption"
207
- # }
208
- # }
209
- # ]
210
- # }
211
- # }
212
- # }
213
- # ]
214
- # }
215
-
216
- return response.body['errors'].map do |error|
217
- messages = [[error['title'], error['detail']].compact.join(" - ")]
218
-
219
- meta = error["meta"] || {}
220
- associated_errors = meta["associatedErrors"] || {}
221
-
222
- messages + associated_errors.values.flatten.map do |associated_error|
223
- [[associated_error["title"], associated_error["detail"]].compact.join(" - ")]
224
- end
225
- end.flatten.join("\n")
226
- end
227
-
228
- private
229
-
230
- def local_variable_get(binding, name)
231
- if binding.respond_to?(:local_variable_get)
232
- binding.local_variable_get(name)
233
- else
234
- binding.eval(name.to_s)
235
- end
236
- end
237
-
238
- def provider_id
239
- return team_id if self.provider.nil?
240
- self.provider.provider_id
241
168
  end
242
169
  end
243
170
  end
244
- # rubocop:enable Metrics/ClassLength
245
171
  end