fastlane 2.156.1 → 2.157.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +82 -82
- data/deliver/lib/deliver.rb +1 -0
- data/deliver/lib/deliver/app_screenshot_iterator.rb +26 -29
- 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/runner.rb +8 -5
- data/deliver/lib/deliver/upload_screenshots.rb +34 -17
- data/fastlane/lib/fastlane/actions/{.git_commit.rb.swp → .ensure_git_status_clean.rb.swp} +0 -0
- data/fastlane/lib/fastlane/actions/.hockey.rb.swp +0 -0
- data/fastlane/lib/fastlane/actions/.slack.rb.swp +0 -0
- data/fastlane/lib/fastlane/actions/.update_project_provisioning.rb.swp +0 -0
- data/fastlane/lib/fastlane/actions/app_store_build_number.rb +12 -8
- 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/download_dsyms.rb +89 -68
- data/fastlane/lib/fastlane/actions/set_changelog.rb +3 -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 +78 -18
- data/fastlane/swift/FastlaneSwiftRunner/FastlaneSwiftRunner.xcodeproj/project.xcworkspace/xcuserdata/josh.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- 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/command_executor.rb +1 -0
- data/fastlane_core/lib/fastlane_core/itunes_transporter.rb +71 -42
- data/gym/lib/gym/error_handler.rb +1 -1
- data/match/lib/match/spaceship_ensure.rb +5 -5
- data/{fastlane/lib/fastlane/.erb_template_helper.rb.swp → pilot/lib/pilot/.manager.rb.swp} +0 -0
- data/pilot/lib/pilot/build_manager.rb +15 -4
- data/pilot/lib/pilot/manager.rb +15 -5
- data/pilot/lib/pilot/options.rb +16 -0
- data/produce/lib/produce/itunes_connect.rb +3 -2
- data/screengrab/lib/screengrab/runner.rb +29 -10
- data/sigh/lib/assets/resign.sh +9 -6
- data/sigh/lib/sigh/runner.rb +5 -4
- data/spaceship/lib/spaceship.rb +4 -0
- data/spaceship/lib/spaceship/client.rb +3 -0
- data/spaceship/lib/spaceship/connect_api.rb +1 -15
- data/spaceship/lib/spaceship/{.DS_Store → connect_api/.DS_Store} +0 -0
- data/spaceship/lib/spaceship/connect_api/api_client.rb +270 -0
- data/spaceship/lib/spaceship/connect_api/client.rb +155 -210
- data/spaceship/lib/spaceship/connect_api/file_uploader.rb +2 -0
- data/spaceship/lib/spaceship/connect_api/models/app.rb +5 -5
- data/spaceship/lib/spaceship/connect_api/models/app_price_point.rb +26 -0
- data/spaceship/lib/spaceship/connect_api/models/app_store_version.rb +7 -1
- 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 +98 -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 +725 -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 +33 -22
- data/spaceship/lib/spaceship/connect_api/.client.rb.swp +0 -0
@@ -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
|
@@ -18,18 +18,18 @@ module Match
|
|
18
18
|
UI.important("More information https://docs.fastlane.tools/actions/match/#access-control")
|
19
19
|
end
|
20
20
|
|
21
|
+
# Prompts select team if multiple teams and none specified
|
21
22
|
UI.message("Verifying that the certificate and profile are still valid on the Dev Portal...")
|
22
|
-
Spaceship.login(user)
|
23
|
-
Spaceship.select_team(team_id: team_id, team_name: team_name)
|
23
|
+
Spaceship::ConnectAPI.login(user, use_portal: true, use_tunes: false, portal_team_id: team_id, team_name: team_name)
|
24
24
|
end
|
25
25
|
|
26
26
|
# The team ID of the currently logged in team
|
27
27
|
def team_id
|
28
|
-
return Spaceship.client.
|
28
|
+
return Spaceship::ConnectAPI.client.portal_team_id
|
29
29
|
end
|
30
30
|
|
31
31
|
def bundle_identifier_exists(username: nil, app_identifier: nil, platform: nil)
|
32
|
-
found = Spaceship.
|
32
|
+
found = Spaceship::ConnectAPI::BundleId.find(app_identifier)
|
33
33
|
return if found
|
34
34
|
|
35
35
|
require 'sigh/runner'
|
@@ -39,7 +39,7 @@ module Match
|
|
39
39
|
})
|
40
40
|
UI.error("An app with that bundle ID needs to exist in order to create a provisioning profile for it")
|
41
41
|
UI.error("================================================================")
|
42
|
-
available_apps = Spaceship.
|
42
|
+
available_apps = Spaceship::ConnectAPI::BundleId.all.collect { |a| "#{a.identifier} (#{a.name})" }
|
43
43
|
UI.message("Available apps:\n- #{available_apps.join("\n- ")}")
|
44
44
|
UI.error("Make sure to run `fastlane match` with the same user and team every time.")
|
45
45
|
UI.user_error!("Couldn't find bundle identifier '#{app_identifier}' for the user '#{username}'")
|
Binary file
|
@@ -343,22 +343,33 @@ 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']
|
357
367
|
provider_id = generic_transporter.provider_ids[name]
|
358
368
|
UI.verbose("Inferred provider id #{provider_id} for team #{name}.")
|
359
369
|
return FastlaneCore::ItunesTransporter.new(options[:username], nil, false, provider_id)
|
360
370
|
rescue => ex
|
361
|
-
|
371
|
+
STDERR.puts(ex.to_s)
|
372
|
+
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
373
|
return generic_transporter
|
363
374
|
end
|
364
375
|
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], use_portal: false, use_tunes: true, tunes_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,9 @@ 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
|
-
|
13
|
-
|
12
|
+
# Team selection passed though FASTLANE_ITC_TEAM_ID and FASTLANE_ITC_TEAM_NAME environment variables
|
13
|
+
# Prompts select team if multiple teams and none specified
|
14
|
+
Spaceship::ConnectAPI.login(Produce.config[:username], nil, use_portal: false, use_tunes: true)
|
14
15
|
|
15
16
|
create_new_app
|
16
17
|
end
|
@@ -64,6 +64,7 @@ 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
70
|
clear_device_previous_screenshots(@config[:app_package_name], device_serial, device_screenshots_paths)
|
@@ -81,7 +82,7 @@ module Screengrab
|
|
81
82
|
end
|
82
83
|
|
83
84
|
def select_device
|
84
|
-
adb = Fastlane::Helper::AdbHelper.new(adb_host: @config[:adb_host])
|
85
|
+
adb = Fastlane::Helper::AdbHelper.new(adb_path: @android_env.adb_path, adb_host: @config[:adb_host])
|
85
86
|
devices = adb.load_all_devices
|
86
87
|
|
87
88
|
UI.user_error!('There are no connected and authorized devices or emulators') if devices.empty?
|
@@ -298,9 +299,17 @@ module Screengrab
|
|
298
299
|
device_screenshots_paths.each do |device_path|
|
299
300
|
if_device_path_exists(@config[:app_package_name], device_serial, device_path) do |path|
|
300
301
|
next unless path.include?(locale)
|
301
|
-
run_adb_command("-s #{device_serial} pull #{path} #{tempdir}",
|
302
|
-
|
303
|
-
|
302
|
+
out = run_adb_command("-s #{device_serial} pull #{path} #{tempdir}",
|
303
|
+
print_all: false,
|
304
|
+
print_command: true,
|
305
|
+
raise_errors: false)
|
306
|
+
if out =~ /Permission denied/
|
307
|
+
dir = File.dirname(path)
|
308
|
+
base = File.basename(path)
|
309
|
+
run_adb_command("-s #{device_serial} shell run-as #{@config[:app_package_name]} 'tar -cC #{dir} #{base}' | tar -xvC #{tempdir}",
|
310
|
+
print_all: false,
|
311
|
+
print_command: true)
|
312
|
+
end
|
304
313
|
end
|
305
314
|
end
|
306
315
|
|
@@ -380,16 +389,26 @@ module Screengrab
|
|
380
389
|
packages.split("\n").map { |package| package.gsub("package:", "") }
|
381
390
|
end
|
382
391
|
|
383
|
-
def run_adb_command(command, print_all: false, print_command: false)
|
392
|
+
def run_adb_command(command, print_all: false, print_command: false, raise_errors: true)
|
384
393
|
adb_path = @android_env.adb_path.chomp("adb")
|
385
394
|
adb_host = @config[:adb_host]
|
386
395
|
host = adb_host.nil? ? '' : "-H #{adb_host} "
|
387
|
-
output =
|
388
|
-
|
389
|
-
|
396
|
+
output = ''
|
397
|
+
begin
|
398
|
+
errout = nil
|
399
|
+
cmdout = @executor.execute(command: adb_path + "adb " + host + command,
|
400
|
+
print_all: print_all,
|
401
|
+
print_command: print_command,
|
402
|
+
error: raise_errors ? nil : proc { |out, status| errout = out }) || ''
|
403
|
+
output = errout || cmdout
|
404
|
+
rescue => ex
|
405
|
+
if raise_errors
|
406
|
+
raise ex
|
407
|
+
end
|
408
|
+
end
|
390
409
|
output.lines.reject do |line|
|
391
|
-
# Debug/Warning output from ADB
|
392
|
-
line.start_with?('adb: ')
|
410
|
+
# Debug/Warning output from ADB
|
411
|
+
line.start_with?('adb: ') && !line.start_with?('adb: error: ')
|
393
412
|
end.join('') # Lines retain their newline chars
|
394
413
|
end
|
395
414
|
|
data/sigh/lib/assets/resign.sh
CHANGED
@@ -72,6 +72,9 @@
|
|
72
72
|
# new features June 2020
|
73
73
|
# 1. enable (re)signing of OnDemandResources when ipa has been built for the appstore
|
74
74
|
#
|
75
|
+
# new features August 2020
|
76
|
+
# 1. fixes usage for users with GNU-sed in their $PATH
|
77
|
+
#
|
75
78
|
|
76
79
|
# Logging functions
|
77
80
|
|
@@ -762,7 +765,7 @@ function resign {
|
|
762
765
|
|
763
766
|
# Get the entry from app's entitlements
|
764
767
|
# Read it with PlistBuddy as XML, then strip the header and <plist></plist> part
|
765
|
-
ENTITLEMENTS_VALUE="$(PlistBuddy -x -c "Print $KEY" "$APP_ENTITLEMENTS" 2>/dev/null | sed -e 's,.*<plist[^>]*>\(.*\)</plist>,\1,g')"
|
768
|
+
ENTITLEMENTS_VALUE="$(PlistBuddy -x -c "Print $KEY" "$APP_ENTITLEMENTS" 2>/dev/null | /usr/bin/sed -e 's,.*<plist[^>]*>\(.*\)</plist>,\1,g')"
|
766
769
|
if [[ -z "$ENTITLEMENTS_VALUE" ]]; then
|
767
770
|
log "No value for '$KEY'"
|
768
771
|
continue
|
@@ -780,7 +783,7 @@ function resign {
|
|
780
783
|
log "Certificate $CERTIFICATE matches a SHA1 pattern"
|
781
784
|
local certificate_matches="$( security find-identity -v -p codesigning | grep -m 1 "$CERTIFICATE" )"
|
782
785
|
if [ -n "$certificate_matches" ]; then
|
783
|
-
certificate_name="$(
|
786
|
+
certificate_name="$(/usr/bin/sed -E s/[^\"]+\"\([^\"]+\)\".*/\\1/ <<< $certificate_matches )"
|
784
787
|
log "Certificate name: $certificate_name"
|
785
788
|
fi
|
786
789
|
fi
|
@@ -807,18 +810,18 @@ function resign {
|
|
807
810
|
# otherwise it interprets they key path as nested keys
|
808
811
|
# TODO: Should be able to replace with echo ${KEY//\./\\\\.} and remove shellcheck disable directive
|
809
812
|
# shellcheck disable=SC2001
|
810
|
-
PLUTIL_KEY=$(echo "$KEY" | sed 's/\./\\\./g')
|
813
|
+
PLUTIL_KEY=$(echo "$KEY" | /usr/bin/sed 's/\./\\\./g')
|
811
814
|
plutil -insert "$PLUTIL_KEY" -xml "$ENTITLEMENTS_VALUE" "$PATCHED_ENTITLEMENTS"
|
812
815
|
|
813
816
|
# Patch the ID value if specified
|
814
817
|
if [[ "$ID_TYPE" == "APP_ID" ]]; then
|
815
818
|
# Replace old value with new value in patched entitlements
|
816
819
|
log "Replacing old app identifier prefix '$OLD_APP_ID' with new value '$NEW_APP_ID'"
|
817
|
-
sed -i .bak "s/$OLD_APP_ID/$NEW_APP_ID/g" "$PATCHED_ENTITLEMENTS"
|
820
|
+
/usr/bin/sed -i .bak "s/$OLD_APP_ID/$NEW_APP_ID/g" "$PATCHED_ENTITLEMENTS"
|
818
821
|
elif [[ "$ID_TYPE" == "TEAM_ID" ]]; then
|
819
822
|
# Replace old team identifier with new value
|
820
823
|
log "Replacing old team ID '$OLD_TEAM_ID' with new team ID: '$NEW_TEAM_ID'"
|
821
|
-
sed -i .bak "s/$OLD_TEAM_ID/$NEW_TEAM_ID/g" "$PATCHED_ENTITLEMENTS"
|
824
|
+
/usr/bin/sed -i .bak "s/$OLD_TEAM_ID/$NEW_TEAM_ID/g" "$PATCHED_ENTITLEMENTS"
|
822
825
|
else
|
823
826
|
continue
|
824
827
|
fi
|
@@ -835,7 +838,7 @@ function resign {
|
|
835
838
|
# e.g. <string>AB1GP98Q19.com.example.foo</string>
|
836
839
|
# vs
|
837
840
|
# com.example.foo
|
838
|
-
sed -i .bak "s!${OLD_BUNDLE_ID}</string>!${NEW_BUNDLE_ID}</string>!g" "$PATCHED_ENTITLEMENTS"
|
841
|
+
/usr/bin/sed -i .bak "s!${OLD_BUNDLE_ID}</string>!${NEW_BUNDLE_ID}</string>!g" "$PATCHED_ENTITLEMENTS"
|
839
842
|
|
840
843
|
log "Resigning application using certificate: '$CERTIFICATE'"
|
841
844
|
log "and patched entitlements:"
|
data/sigh/lib/sigh/runner.rb
CHANGED
@@ -17,9 +17,10 @@ module Sigh
|
|
17
17
|
hide_keys: [:output_path],
|
18
18
|
title: "Summary for sigh #{Fastlane::VERSION}")
|
19
19
|
|
20
|
+
# Team selection passed though FASTLANE_ITC_TEAM_ID and FASTLANE_ITC_TEAM_NAME environment variables
|
21
|
+
# Prompts select team if multiple teams and none specified
|
20
22
|
UI.message("Starting login with user '#{Sigh.config[:username]}'")
|
21
|
-
Spaceship.login(Sigh.config[:username], nil)
|
22
|
-
Spaceship.select_team
|
23
|
+
Spaceship::ConnectAPI.login(Sigh.config[:username], nil, use_portal: true, use_tunes: false)
|
23
24
|
UI.message("Successfully logged in")
|
24
25
|
|
25
26
|
profiles = [] if Sigh.config[:skip_fetch_profiles]
|
@@ -60,12 +61,12 @@ module Sigh
|
|
60
61
|
case Sigh.config[:platform]
|
61
62
|
when "ios"
|
62
63
|
@profile_type = Spaceship::ConnectAPI::Profile::ProfileType::IOS_APP_STORE
|
63
|
-
@profile_type = Spaceship::ConnectAPI::Profile::ProfileType::IOS_APP_INHOUSE if Spaceship.client.in_house?
|
64
|
+
@profile_type = Spaceship::ConnectAPI::Profile::ProfileType::IOS_APP_INHOUSE if Spaceship::ConnectAPI.client.in_house?
|
64
65
|
@profile_type = Spaceship::ConnectAPI::Profile::ProfileType::IOS_APP_ADHOC if Sigh.config[:adhoc]
|
65
66
|
@profile_type = Spaceship::ConnectAPI::Profile::ProfileType::IOS_APP_DEVELOPMENT if Sigh.config[:development]
|
66
67
|
when "tvos"
|
67
68
|
@profile_type = Spaceship::ConnectAPI::Profile::ProfileType::TVOS_APP_STORE
|
68
|
-
@profile_type = Spaceship::ConnectAPI::Profile::ProfileType::TVOS_APP_INHOUSE if Spaceship.client.in_house?
|
69
|
+
@profile_type = Spaceship::ConnectAPI::Profile::ProfileType::TVOS_APP_INHOUSE if Spaceship::ConnectAPI.client.in_house?
|
69
70
|
@profile_type = Spaceship::ConnectAPI::Profile::ProfileType::TVOS_APP_ADHOC if Sigh.config[:adhoc]
|
70
71
|
@profile_type = Spaceship::ConnectAPI::Profile::ProfileType::TVOS_APP_DEVELOPMENT if Sigh.config[:development]
|
71
72
|
when "macos"
|
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'
|
@@ -6,6 +6,7 @@ require 'logger'
|
|
6
6
|
require 'tmpdir'
|
7
7
|
require 'cgi'
|
8
8
|
require 'tempfile'
|
9
|
+
require 'openssl'
|
9
10
|
|
10
11
|
require 'fastlane/version'
|
11
12
|
require_relative 'helper/net_http_generic_request'
|
@@ -16,6 +17,7 @@ require_relative 'errors'
|
|
16
17
|
require_relative 'tunes/errors'
|
17
18
|
require_relative 'globals'
|
18
19
|
require_relative 'provider'
|
20
|
+
require_relative 'stats_middleware'
|
19
21
|
|
20
22
|
Faraday::Utils.default_params_encoder = Faraday::FlatParamsEncoder
|
21
23
|
|
@@ -209,6 +211,7 @@ module Spaceship
|
|
209
211
|
c.response(:plist, content_type: /\bplist$/)
|
210
212
|
c.use(:cookie_jar, jar: @cookie)
|
211
213
|
c.use(FaradayMiddleware::RelsMiddleware)
|
214
|
+
c.use(Spaceship::StatsMiddleware)
|
212
215
|
c.adapter(Faraday.default_adapter)
|
213
216
|
|
214
217
|
if ENV['SPACESHIP_DEBUG']
|
@@ -39,6 +39,7 @@ require 'spaceship/connect_api/models/app_info_localization'
|
|
39
39
|
require 'spaceship/connect_api/models/app_preview_set'
|
40
40
|
require 'spaceship/connect_api/models/app_preview'
|
41
41
|
require 'spaceship/connect_api/models/app_price'
|
42
|
+
require 'spaceship/connect_api/models/app_price_point'
|
42
43
|
require 'spaceship/connect_api/models/app_price_tier'
|
43
44
|
require 'spaceship/connect_api/models/app_store_review_attachment'
|
44
45
|
require 'spaceship/connect_api/models/app_store_review_detail'
|
@@ -56,21 +57,6 @@ require 'spaceship/connect_api/models/territory'
|
|
56
57
|
|
57
58
|
module Spaceship
|
58
59
|
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
60
|
# Defined in the App Store Connect API docs:
|
75
61
|
# https://developer.apple.com/documentation/appstoreconnectapi/platform
|
76
62
|
#
|
Binary file
|
@@ -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
|