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.
- checksums.yaml +4 -4
- data/README.md +72 -72
- data/deliver/lib/deliver.rb +1 -0
- data/deliver/lib/deliver/app_screenshot_iterator.rb +95 -0
- data/deliver/lib/deliver/detect_values.rb +4 -1
- data/deliver/lib/deliver/languages.rb +7 -0
- data/deliver/lib/deliver/loader.rb +4 -5
- data/deliver/lib/deliver/queue_worker.rb +64 -0
- data/deliver/lib/deliver/runner.rb +7 -5
- data/deliver/lib/deliver/upload_screenshots.rb +143 -128
- data/fastlane/lib/fastlane/actions/app_store_connect_api_key.rb +120 -0
- data/fastlane/lib/fastlane/actions/commit_version_bump.rb +1 -1
- data/fastlane/lib/fastlane/actions/docs/upload_to_play_store.md +2 -0
- data/fastlane/lib/fastlane/actions/docs/upload_to_testflight.md +17 -1
- data/fastlane/lib/fastlane/actions/set_changelog.rb +2 -2
- data/fastlane/lib/fastlane/actions/sonar.rb +5 -0
- data/fastlane/lib/fastlane/actions/spaceship_stats.rb +73 -0
- data/fastlane/lib/fastlane/actions/upload_to_testflight.rb +4 -0
- data/fastlane/lib/fastlane/version.rb +1 -1
- data/fastlane/swift/Deliverfile.swift +1 -1
- data/fastlane/swift/DeliverfileProtocol.swift +1 -1
- data/fastlane/swift/Fastlane.swift +68 -8
- data/fastlane/swift/Gymfile.swift +1 -1
- data/fastlane/swift/GymfileProtocol.swift +1 -1
- data/fastlane/swift/Matchfile.swift +1 -1
- data/fastlane/swift/MatchfileProtocol.swift +1 -1
- data/fastlane/swift/Precheckfile.swift +1 -1
- data/fastlane/swift/PrecheckfileProtocol.swift +1 -1
- data/fastlane/swift/Scanfile.swift +1 -1
- data/fastlane/swift/ScanfileProtocol.swift +1 -1
- data/fastlane/swift/Screengrabfile.swift +1 -1
- data/fastlane/swift/ScreengrabfileProtocol.swift +1 -1
- data/fastlane/swift/Snapshotfile.swift +1 -1
- data/fastlane/swift/SnapshotfileProtocol.swift +1 -1
- data/fastlane_core/lib/fastlane_core/itunes_transporter.rb +71 -42
- data/fastlane_core/lib/fastlane_core/project.rb +1 -0
- data/gym/lib/gym/error_handler.rb +1 -1
- data/gym/lib/gym/generators/build_command_generator.rb +0 -1
- data/pilot/lib/pilot/build_manager.rb +18 -4
- data/pilot/lib/pilot/manager.rb +15 -5
- data/pilot/lib/pilot/options.rb +16 -0
- data/produce/lib/produce/itunes_connect.rb +2 -2
- data/scan/lib/scan/test_command_generator.rb +3 -1
- data/screengrab/lib/screengrab/runner.rb +8 -7
- data/sigh/lib/sigh/runner.rb +9 -5
- data/snapshot/lib/snapshot/test_command_generator_base.rb +3 -1
- data/spaceship/lib/spaceship.rb +4 -0
- data/spaceship/lib/spaceship/client.rb +2 -0
- data/spaceship/lib/spaceship/connect_api.rb +0 -15
- data/spaceship/lib/spaceship/connect_api/api_client.rb +270 -0
- data/spaceship/lib/spaceship/connect_api/client.rb +139 -213
- data/spaceship/lib/spaceship/connect_api/models/profile.rb +3 -2
- data/spaceship/lib/spaceship/connect_api/provisioning/client.rb +8 -17
- data/spaceship/lib/spaceship/connect_api/provisioning/provisioning.rb +75 -64
- data/spaceship/lib/spaceship/connect_api/spaceship.rb +94 -0
- data/spaceship/lib/spaceship/connect_api/testflight/client.rb +8 -17
- data/spaceship/lib/spaceship/connect_api/testflight/testflight.rb +288 -277
- data/spaceship/lib/spaceship/connect_api/token.rb +46 -5
- data/spaceship/lib/spaceship/connect_api/token_refresh_middleware.rb +24 -0
- data/spaceship/lib/spaceship/connect_api/tunes/client.rb +8 -17
- data/spaceship/lib/spaceship/connect_api/tunes/tunes.rb +717 -706
- data/spaceship/lib/spaceship/connect_api/users/client.rb +8 -17
- data/spaceship/lib/spaceship/connect_api/users/users.rb +28 -17
- data/spaceship/lib/spaceship/stats_middleware.rb +65 -0
- metadata +25 -18
- data/sigh/lib/sigh/.runner.rb.swp +0 -0
- 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")
|
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] ||
|
352
|
-
return generic_transporter unless
|
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 =
|
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
|
-
|
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
|
data/pilot/lib/pilot/manager.rb
CHANGED
@@ -17,12 +17,22 @@ module Pilot
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def login
|
20
|
-
|
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
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
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
|
data/pilot/lib/pilot/options.rb
CHANGED
@@ -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::
|
13
|
-
Spaceship::
|
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
|
-
|
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
|
|
data/sigh/lib/sigh/runner.rb
CHANGED
@@ -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
|
-
|
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]
|
data/spaceship/lib/spaceship.rb
CHANGED
@@ -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
|