fastlane 2.156.1 → 2.157.4
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 +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
|