fastlane 2.240.1 → 2.241.1
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 +51 -171
- data/bin/fastlane +4 -2
- data/credentials_manager/README.md +1 -1
- data/deliver/lib/deliver/app_screenshot.rb +8 -4
- data/deliver/lib/deliver/commands_generator.rb +1 -1
- data/deliver/lib/deliver/download_screenshots.rb +8 -1
- data/deliver/lib/deliver/sync_screenshots.rb +27 -28
- data/deliver/lib/deliver/upload_app_clip_default_experience_header_images.rb +3 -3
- data/deliver/lib/deliver/upload_metadata.rb +12 -16
- data/deliver/lib/deliver/upload_screenshots.rb +12 -14
- data/docs/README.md +25 -0
- data/fastlane/lib/assets/TeamTable.md.erb +28 -0
- data/fastlane/lib/fastlane/actions/appaloosa.rb +2 -1
- data/fastlane/lib/fastlane/actions/appium.rb +1 -1
- data/fastlane/lib/fastlane/actions/apteligent.rb +8 -1
- data/fastlane/lib/fastlane/actions/build_and_upload_to_appetize.rb +1 -1
- data/fastlane/lib/fastlane/actions/clean_build_artifacts.rb +3 -0
- data/fastlane/lib/fastlane/actions/danger.rb +5 -5
- data/fastlane/lib/fastlane/actions/docs/build_app.md +1 -2
- data/fastlane/lib/fastlane/actions/docs/create_app_online.md +1 -2
- data/fastlane/lib/fastlane/actions/docs/get_certificates.md +0 -2
- data/fastlane/lib/fastlane/actions/docs/get_push_certificate.md +1 -3
- data/fastlane/lib/fastlane/actions/docs/sync_code_signing.md +13 -3
- data/fastlane/lib/fastlane/actions/ensure_no_debug_code.rb +2 -2
- data/fastlane/lib/fastlane/actions/get_managed_play_store_publishing_rights.rb +1 -0
- data/fastlane/lib/fastlane/actions/github_api.rb +1 -2
- data/fastlane/lib/fastlane/actions/hg_commit_version_bump.rb +1 -1
- data/fastlane/lib/fastlane/actions/increment_build_number.rb +1 -1
- data/fastlane/lib/fastlane/actions/increment_version_number.rb +1 -1
- data/fastlane/lib/fastlane/actions/install_xcode_plugin.rb +16 -6
- data/fastlane/lib/fastlane/actions/lcov.rb +9 -2
- data/fastlane/lib/fastlane/actions/nexus_upload.rb +16 -4
- data/fastlane/lib/fastlane/actions/notification.rb +3 -2
- data/fastlane/lib/fastlane/actions/oclint.rb +8 -8
- data/fastlane/lib/fastlane/actions/push_to_git_remote.rb +9 -4
- data/fastlane/lib/fastlane/actions/register_device.rb +1 -1
- data/fastlane/lib/fastlane/actions/say.rb +2 -4
- data/fastlane/lib/fastlane/actions/set_pod_key.rb +3 -3
- data/fastlane/lib/fastlane/actions/sonar.rb +16 -16
- data/fastlane/lib/fastlane/actions/sourcedocs.rb +8 -8
- data/fastlane/lib/fastlane/actions/splunkmint.rb +8 -1
- data/fastlane/lib/fastlane/actions/update_project_provisioning.rb +1 -1
- data/fastlane/lib/fastlane/actions/upload_to_app_store.rb +5 -2
- data/fastlane/lib/fastlane/actions/upload_to_testflight.rb +5 -2
- data/fastlane/lib/fastlane/actions/verify_build.rb +1 -4
- data/fastlane/lib/fastlane/actions/verify_pod_keys.rb +3 -1
- data/fastlane/lib/fastlane/actions/xcode_server_get_assets.rb +20 -19
- data/fastlane/lib/fastlane/actions/xcodebuild.rb +5 -6
- data/fastlane/lib/fastlane/actions/zip.rb +2 -1
- data/fastlane/lib/fastlane/auto_complete.rb +1 -1
- data/fastlane/lib/fastlane/cli_tools_distributor.rb +14 -0
- data/fastlane/lib/fastlane/documentation/markdown_docs_generator.rb +23 -0
- data/fastlane/lib/fastlane/environment_printer.rb +12 -25
- data/fastlane/lib/fastlane/fast_file.rb +2 -1
- data/fastlane/lib/fastlane/fastlane_require.rb +5 -1
- data/fastlane/lib/fastlane/helper/sh_helper.rb +16 -6
- data/fastlane/lib/fastlane/new_action.rb +1 -1
- data/fastlane/lib/fastlane/plugins/plugin_fetcher.rb +1 -1
- data/fastlane/lib/fastlane/plugins/plugin_info_collector.rb +1 -1
- data/fastlane/lib/fastlane/plugins/plugin_manager.rb +36 -4
- data/fastlane/lib/fastlane/plugins/template/%gem_name%.gemspec.erb +1 -1
- data/fastlane/lib/fastlane/plugins/template/.github/workflows/test.yml +1 -1
- data/fastlane/lib/fastlane/plugins/template/.rubocop.yml +3 -7
- data/fastlane/lib/fastlane/swift_lane_manager.rb +22 -4
- data/fastlane/lib/fastlane/version.rb +3 -2
- data/fastlane/swift/Deliverfile.swift +1 -1
- data/fastlane/swift/DeliverfileProtocol.swift +1 -1
- data/fastlane/swift/Fastlane.swift +39 -9
- data/fastlane/swift/Gymfile.swift +1 -1
- data/fastlane/swift/GymfileProtocol.swift +1 -1
- data/fastlane/swift/MainProcess.swift +190 -20
- data/fastlane/swift/Matchfile.swift +1 -1
- data/fastlane/swift/MatchfileProtocol.swift +10 -3
- 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 +8 -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/swift/formatting/Rakefile +1 -0
- data/fastlane/swift/main.swift +4 -1
- data/fastlane_core/README.md +1 -1
- data/fastlane_core/lib/fastlane_core/analytics/analytics_event_builder.rb +1 -17
- data/fastlane_core/lib/fastlane_core/configuration/configuration.rb +11 -8
- data/fastlane_core/lib/fastlane_core/device_manager.rb +2 -2
- data/fastlane_core/lib/fastlane_core/helper.rb +34 -5
- data/fastlane_core/lib/fastlane_core/itunes_transporter.rb +0 -14
- data/fastlane_core/lib/fastlane_core/keychain_importer.rb +24 -27
- data/fastlane_core/lib/fastlane_core/languages.rb +0 -1
- data/fastlane_core/lib/fastlane_core/print_table.rb +5 -2
- data/fastlane_core/lib/fastlane_core/provisioning_profile.rb +10 -19
- data/fastlane_core/lib/fastlane_core/secrets.rb +77 -0
- data/fastlane_core/lib/fastlane_core/ui/fastlane_runner.rb +3 -3
- data/fastlane_core/lib/fastlane_core/ui/github_issue_inspector_reporter.rb +1 -1
- data/fastlane_core/lib/fastlane_core/ui/implementations/shell.rb +2 -1
- data/fastlane_core/lib/fastlane_core.rb +1 -0
- data/frameit/lib/frameit/device.rb +1 -1
- data/frameit/lib/frameit/frame_downloader.rb +2 -0
- data/frameit/lib/frameit/runner.rb +3 -2
- data/frameit/lib/frameit/screenshot.rb +0 -1
- data/gym/lib/gym/generators/build_command_generator.rb +4 -4
- data/gym/lib/gym/generators/package_command_generator_xcode7.rb +3 -3
- data/gym/lib/gym/runner.rb +1 -1
- data/internal/README.md +1 -1
- data/match/lib/match/generator.rb +1 -0
- data/match/lib/match/options.rb +13 -5
- data/match/lib/match/storage/git_storage.rb +43 -7
- data/match/lib/match/storage/gitlab/secure_file.rb +2 -1
- data/pem/lib/pem/module.rb +0 -4
- data/precheck/lib/precheck/rule_processor.rb +1 -0
- data/produce/lib/produce/itunes_connect.rb +1 -1
- data/produce/lib/produce/service.rb +0 -1
- data/produce/lib/produce.rb +0 -1
- data/scan/lib/scan/options.rb +7 -0
- data/scan/lib/scan/runner.rb +1 -1
- data/scan/lib/scan/test_command_generator.rb +12 -9
- data/scan/lib/scan/test_result_parser.rb +1 -0
- data/scan/lib/scan/xcpretty_reporter_options_generator.rb +2 -2
- data/screengrab/lib/screengrab/page.html.erb +12 -7
- data/screengrab/lib/screengrab/reports_generator.rb +1 -1
- data/sigh/lib/assets/resign.sh +17 -0
- data/sigh/lib/sigh/local_manage.rb +1 -1
- data/sigh/lib/sigh/options.rb +5 -0
- data/sigh/lib/sigh/resign.rb +3 -3
- data/sigh/lib/sigh/runner.rb +6 -1
- data/snapshot/lib/snapshot/page.html.erb +17 -12
- data/snapshot/lib/snapshot/reports_generator.rb +3 -1
- data/snapshot/lib/snapshot/simulator_launchers/simulator_launcher_base.rb +1 -1
- data/snapshot/lib/snapshot.rb +0 -2
- data/spaceship/README.md +2 -3
- data/spaceship/lib/spaceship/base.rb +3 -1
- data/spaceship/lib/spaceship/client.rb +65 -47
- data/spaceship/lib/spaceship/connect_api/models/app_screenshot_set.rb +2 -0
- data/spaceship/lib/spaceship/connect_api/models/profile.rb +7 -5
- data/spaceship/lib/spaceship/connect_api/provisioning/provisioning.rb +9 -0
- data/spaceship/lib/spaceship/du/upload_file.rb +1 -1
- data/spaceship/lib/spaceship/errors.rb +7 -1
- data/spaceship/lib/spaceship/globals.rb +2 -2
- data/spaceship/lib/spaceship/portal/merchant.rb +127 -2
- data/spaceship/lib/spaceship/portal/portal_client.rb +58 -0
- data/spaceship/lib/spaceship/portal/ui/select_team.rb +0 -2
- data/spaceship/lib/spaceship/portal/website_push.rb +8 -2
- data/spaceship/lib/spaceship/test_flight/client.rb +0 -22
- data/spaceship/lib/spaceship/test_flight.rb +0 -1
- data/spaceship/lib/spaceship/two_step_or_factor_client.rb +11 -0
- data/supply/lib/supply/options.rb +1 -0
- data/supply/lib/supply.rb +0 -1
- metadata +55 -37
- data/produce/lib/produce/available_default_languages.rb +0 -62
- data/snapshot/lib/snapshot/screenshot_flatten.rb +0 -20
- data/snapshot/lib/snapshot/screenshot_rotate.rb +0 -36
- data/spaceship/lib/spaceship/test_flight/app_test_info.rb +0 -40
- data/supply/lib/supply/apk_listing.rb +0 -14
- /data/fastlane/lib/assets/{custom_action_template.rb → custom_action_template.rb.template} +0 -0
|
@@ -605,16 +605,14 @@ module Spaceship
|
|
|
605
605
|
# Now we know if the login is successful or if we need to do 2 factor
|
|
606
606
|
|
|
607
607
|
case response.status
|
|
608
|
-
when 403
|
|
609
|
-
raise InvalidUserCredentialsError.new, "Invalid username and password combination. Used '#{user}' as the username."
|
|
610
608
|
when 200
|
|
611
|
-
|
|
609
|
+
fetch_olympus_session_after_signin
|
|
612
610
|
return response
|
|
613
611
|
when 409
|
|
614
612
|
# 2 step/factor is enabled for this account, first handle that
|
|
615
613
|
handle_two_step_or_factor(response)
|
|
616
614
|
# and then get the olympus session
|
|
617
|
-
|
|
615
|
+
fetch_olympus_session_after_signin
|
|
618
616
|
return true
|
|
619
617
|
else
|
|
620
618
|
if (response.body || "").include?('invalid="true"')
|
|
@@ -692,6 +690,16 @@ module Spaceship
|
|
|
692
690
|
return Spaceship::Hashcash.make(bits: bits, challenge: challenge)
|
|
693
691
|
end
|
|
694
692
|
|
|
693
|
+
# Apple accepts a stale key for the sign in and 2FA, and only refuses the
|
|
694
|
+
# session afterwards, with a 401 that says nothing about the key.
|
|
695
|
+
def fetch_olympus_session_after_signin
|
|
696
|
+
fetch_olympus_session
|
|
697
|
+
rescue UnauthorizedAccessError
|
|
698
|
+
raise unless service_key_from_cache?
|
|
699
|
+
|
|
700
|
+
raise UnauthorizedAccessError.new, "App Store Connect refused the session after signing in. The App Store Connect API key came from the cache at #{itc_service_key_path} and may be stale: delete that file and try again."
|
|
701
|
+
end
|
|
702
|
+
|
|
695
703
|
# Get the `itctx` from the new (22nd May 2017) API endpoint "olympus"
|
|
696
704
|
# Update (29th March 2019) olympus migrates to new appstoreconnect API
|
|
697
705
|
def fetch_olympus_session
|
|
@@ -721,58 +729,59 @@ module Spaceship
|
|
|
721
729
|
exit(has_valid_session)
|
|
722
730
|
end
|
|
723
731
|
|
|
724
|
-
# <
|
|
725
|
-
#
|
|
726
|
-
#
|
|
727
|
-
# and here the failure was worse than a missing file: itc_service_key
|
|
728
|
-
# rescues everything, so the write failing on Windows was reported as an
|
|
729
|
-
# App Store Connect outage. See #30198.
|
|
730
|
-
#
|
|
731
|
-
# On macOS this is also per user, so a cache written by one user no longer
|
|
732
|
-
# blocks another.
|
|
732
|
+
# <fastlane_user_dir>/spaceship_itc_service_key.txt: per user, and kept between
|
|
733
|
+
# runs. Dir.tmpdir (#30197) is shared by every user on Linux, and a literal
|
|
734
|
+
# "/tmp" is not a directory on Windows.
|
|
733
735
|
def itc_service_key_path
|
|
734
|
-
File.join(
|
|
736
|
+
File.join(fastlane_user_dir, "spaceship_itc_service_key.txt")
|
|
735
737
|
end
|
|
736
738
|
|
|
739
|
+
# Read on every login from GET /logout -> 302 Location: .../signout?widgetKey=<key>.
|
|
740
|
+
# A stale key only fails after 2FA, so the cache is just a fallback. See fastlane#30291.
|
|
737
741
|
def itc_service_key
|
|
738
742
|
return @service_key if @service_key
|
|
739
743
|
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
return
|
|
744
|
-
|
|
745
|
-
|
|
744
|
+
key = fetch_service_key_from_signout
|
|
745
|
+
if key.nil?
|
|
746
|
+
cached = cached_service_key
|
|
747
|
+
return @service_key = cached if cached
|
|
748
|
+
|
|
749
|
+
key = fetch_service_key_from_olympus
|
|
746
750
|
end
|
|
747
751
|
|
|
748
|
-
|
|
752
|
+
cache_service_key(key)
|
|
753
|
+
@service_key = key
|
|
754
|
+
end
|
|
749
755
|
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
756
|
+
def service_key_from_cache?
|
|
757
|
+
@service_key_from_cache == true
|
|
758
|
+
end
|
|
759
|
+
|
|
760
|
+
# Reading the cache is best effort: an unreadable cache is a reason to
|
|
761
|
+
# fetch the key again, not to fail.
|
|
762
|
+
def cached_service_key
|
|
763
|
+
return nil unless File.exist?(itc_service_key_path)
|
|
757
764
|
|
|
758
|
-
|
|
765
|
+
key = File.read(itc_service_key_path)
|
|
766
|
+
@service_key_from_cache = true
|
|
767
|
+
logger.warn("Using the cached App Store Connect API key from #{itc_service_key_path}")
|
|
768
|
+
key
|
|
769
|
+
rescue SystemCallError => ex
|
|
770
|
+
logger.warn("Could not read the cached App Store Connect API key: #{ex.message}")
|
|
771
|
+
nil
|
|
759
772
|
end
|
|
760
773
|
|
|
761
|
-
#
|
|
762
|
-
#
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
# That is preferred over the olympus endpoint below, which Apple removed in
|
|
769
|
-
# September 2026 and which now answers 404. See fastlane#30199. The olympus
|
|
770
|
-
# call is kept as a fallback in case it comes back, since it is the source
|
|
771
|
-
# Apple documented rather than one scraped out of a redirect.
|
|
772
|
-
def fetch_service_key
|
|
773
|
-
key = fetch_service_key_from_signout
|
|
774
|
-
return key if key
|
|
774
|
+
# Best effort as well: having the key and failing to save it is not a reason
|
|
775
|
+
# to fail the login. See fastlane#30198.
|
|
776
|
+
def cache_service_key(key)
|
|
777
|
+
File.write(itc_service_key_path, key)
|
|
778
|
+
rescue SystemCallError => ex
|
|
779
|
+
logger.warn("Could not cache the App Store Connect API key: #{ex.message}")
|
|
780
|
+
end
|
|
775
781
|
|
|
782
|
+
# The source Apple documented, removed in September 2026 and answering 404
|
|
783
|
+
# since. Kept as the last fallback in case it comes back. See fastlane#30199.
|
|
784
|
+
def fetch_service_key_from_olympus
|
|
776
785
|
# Fixes issue https://github.com/fastlane/fastlane/issues/13281
|
|
777
786
|
# Even though we are using https://appstoreconnect.apple.com, the service key needs to still use a
|
|
778
787
|
# hostname through itunesconnect.apple.com
|
|
@@ -830,10 +839,11 @@ module Spaceship
|
|
|
830
839
|
# said instead of the real cause.
|
|
831
840
|
#
|
|
832
841
|
# Warn rather than debug. This is the source the key normally comes from,
|
|
833
|
-
# so failing here means the run is about to depend on
|
|
834
|
-
#
|
|
835
|
-
# caller sees, and this line is
|
|
836
|
-
|
|
842
|
+
# so failing here means the run is about to depend on a cached key that may
|
|
843
|
+
# be stale, or on an endpoint Apple has already removed once. If the
|
|
844
|
+
# fallback fails too, its message is all the caller sees, and this line is
|
|
845
|
+
# the half that says why.
|
|
846
|
+
logger.warn("Could not read the App Store Connect API key from the sign out redirect, falling back to the cache or the olympus endpoint: #{ex.message}")
|
|
837
847
|
nil
|
|
838
848
|
end
|
|
839
849
|
|
|
@@ -1189,6 +1199,11 @@ module Spaceship
|
|
|
1189
1199
|
end
|
|
1190
1200
|
end
|
|
1191
1201
|
|
|
1202
|
+
# signin/init, signin/complete, and the legacy signin
|
|
1203
|
+
def sign_in_request?(response)
|
|
1204
|
+
response.env.url.host == "idmsa.apple.com" && response.env.url.path.start_with?("/appleauth/auth/signin")
|
|
1205
|
+
end
|
|
1206
|
+
|
|
1192
1207
|
def handle_error(response)
|
|
1193
1208
|
case response.status
|
|
1194
1209
|
when 401
|
|
@@ -1196,6 +1211,9 @@ module Spaceship
|
|
|
1196
1211
|
logger.warn(msg)
|
|
1197
1212
|
raise UnauthorizedAccessError.new, "Unauthorized Access"
|
|
1198
1213
|
when 403
|
|
1214
|
+
# A refused sign-in, e.g. a locked Apple ID: like do_sirp, raise what Apple said, which with_retry does not retry. See fastlane#14387.
|
|
1215
|
+
raise UnexpectedResponse, response.body if sign_in_request?(response) && response.body.kind_of?(Hash) && response.body["serviceErrors"]
|
|
1216
|
+
|
|
1199
1217
|
msg = "Access forbidden"
|
|
1200
1218
|
logger.warn(msg)
|
|
1201
1219
|
raise AccessForbiddenError.new, msg
|
|
@@ -19,6 +19,7 @@ module Spaceship
|
|
|
19
19
|
APP_IPHONE_61 = "APP_IPHONE_61"
|
|
20
20
|
APP_IPHONE_65 = "APP_IPHONE_65"
|
|
21
21
|
APP_IPHONE_67 = "APP_IPHONE_67"
|
|
22
|
+
APP_IPHONE_DUO = "APP_IPHONE_DUO"
|
|
22
23
|
|
|
23
24
|
APP_IPAD_97 = "APP_IPAD_97"
|
|
24
25
|
APP_IPAD_105 = "APP_IPAD_105"
|
|
@@ -77,6 +78,7 @@ module Spaceship
|
|
|
77
78
|
APP_IPHONE_61,
|
|
78
79
|
APP_IPHONE_65,
|
|
79
80
|
APP_IPHONE_67,
|
|
81
|
+
APP_IPHONE_DUO,
|
|
80
82
|
|
|
81
83
|
APP_IPAD_97,
|
|
82
84
|
APP_IPAD_105,
|
|
@@ -84,16 +84,18 @@ module Spaceship
|
|
|
84
84
|
return resps.flat_map(&:to_models)
|
|
85
85
|
end
|
|
86
86
|
|
|
87
|
-
def self.create(client: nil, name: nil, profile_type: nil, bundle_id_id: nil, certificate_ids: nil, device_ids: nil)
|
|
87
|
+
def self.create(client: nil, name: nil, profile_type: nil, bundle_id_id: nil, certificate_ids: nil, device_ids: nil, is_offline_profile: false)
|
|
88
88
|
client ||= Spaceship::ConnectAPI
|
|
89
|
+
attributes = {
|
|
90
|
+
name: name,
|
|
91
|
+
profileType: profile_type
|
|
92
|
+
}
|
|
93
|
+
attributes[:isOfflineProfile] = true if is_offline_profile
|
|
89
94
|
resp = client.post_profiles(
|
|
90
95
|
bundle_id_id: bundle_id_id,
|
|
91
96
|
certificates: certificate_ids,
|
|
92
97
|
devices: device_ids,
|
|
93
|
-
attributes:
|
|
94
|
-
name: name,
|
|
95
|
-
profileType: profile_type
|
|
96
|
-
}
|
|
98
|
+
attributes: attributes
|
|
97
99
|
)
|
|
98
100
|
return resp.to_models.first
|
|
99
101
|
end
|
|
@@ -3,6 +3,11 @@ require 'spaceship/connect_api/provisioning/client'
|
|
|
3
3
|
module Spaceship
|
|
4
4
|
class ConnectAPI
|
|
5
5
|
module Provisioning
|
|
6
|
+
# Raised when a profile with 'Offline Support' is requested while authenticated
|
|
7
|
+
# with an App Store Connect API key. Apple only exposes the isOfflineProfile
|
|
8
|
+
# attribute on the Developer Portal endpoint used by Apple ID (web session) logins.
|
|
9
|
+
class OfflineProfileNotSupportedError < StandardError; end
|
|
10
|
+
|
|
6
11
|
module API
|
|
7
12
|
module Version
|
|
8
13
|
V1 = "v1"
|
|
@@ -222,6 +227,10 @@ module Spaceship
|
|
|
222
227
|
end
|
|
223
228
|
|
|
224
229
|
def post_profiles(bundle_id_id: nil, certificates: nil, devices: nil, attributes: {})
|
|
230
|
+
if attributes[:isOfflineProfile] && !provisioning_request_client.web_session?
|
|
231
|
+
raise OfflineProfileNotSupportedError, "Profiles with 'Offline Support' (isOfflineProfile) can only be created when logged in with an Apple ID; the App Store Connect API does not support this attribute"
|
|
232
|
+
end
|
|
233
|
+
|
|
225
234
|
body = {
|
|
226
235
|
data: {
|
|
227
236
|
attributes: attributes,
|
|
@@ -34,7 +34,7 @@ module Spaceship
|
|
|
34
34
|
# This will copy the image into the temporary directory to remove the
|
|
35
35
|
# alpha channel there. That's done to not edit the original image.
|
|
36
36
|
def remove_alpha_channel(original)
|
|
37
|
-
path = File.join(Dir.
|
|
37
|
+
path = File.join(Dir.mktmpdir("spaceship-upload-"), File.basename(original))
|
|
38
38
|
FileUtils.copy(original, path)
|
|
39
39
|
if mac? # sips is only available on macOS
|
|
40
40
|
`sips -s format bmp '#{path}' &> /dev/null` # &> /dev/null since there is warning because of the extension
|
|
@@ -86,7 +86,13 @@ module Spaceship
|
|
|
86
86
|
end
|
|
87
87
|
|
|
88
88
|
def preferred_error_info
|
|
89
|
-
return nil unless @error_info.kind_of?(Hash)
|
|
89
|
+
return nil unless @error_info.kind_of?(Hash)
|
|
90
|
+
|
|
91
|
+
# What Apple's authentication service answers, e.g. on a locked Apple ID
|
|
92
|
+
service_errors = Array(@error_info['serviceErrors'])
|
|
93
|
+
return ["Apple provided the following error info:"] + service_errors.map { |error| [error['message'], "(#{error['code']})"].compact.join(" ") } unless service_errors.empty?
|
|
94
|
+
|
|
95
|
+
return nil unless @error_info['resultString']
|
|
90
96
|
|
|
91
97
|
[
|
|
92
98
|
"Apple provided the following error info:",
|
|
@@ -7,8 +7,8 @@ module Spaceship
|
|
|
7
7
|
# if spaceship is run with a FastlaneCore available respect the global state there
|
|
8
8
|
# otherwise fallback to $verbose
|
|
9
9
|
def self.verbose?
|
|
10
|
-
if Object.const_defined?("FastlaneCore")
|
|
11
|
-
return FastlaneCore::Globals.verbose?
|
|
10
|
+
if Object.const_defined?("FastlaneCore") && FastlaneCore.const_defined?("Globals")
|
|
11
|
+
return FastlaneCore::Globals.verbose?
|
|
12
12
|
end
|
|
13
13
|
return $verbose
|
|
14
14
|
end
|
|
@@ -46,7 +46,7 @@ module Spaceship
|
|
|
46
46
|
# @param mac [Bool] Fetches Mac merchant if true
|
|
47
47
|
# @return (Array) Returns all merchants available for this account
|
|
48
48
|
def all(mac: false)
|
|
49
|
-
client.merchants(mac: mac).map { |merchant|
|
|
49
|
+
client.merchants(mac: mac).map { |merchant| new_with_platform(merchant, mac) }
|
|
50
50
|
end
|
|
51
51
|
|
|
52
52
|
# Creates a new Merchant on the Apple Dev Portal
|
|
@@ -57,7 +57,7 @@ module Spaceship
|
|
|
57
57
|
# @return (Merchant) The Merchant you just created
|
|
58
58
|
def create!(bundle_id: nil, name: nil, mac: false)
|
|
59
59
|
new_merchant = client.create_merchant!(name, bundle_id, mac: mac)
|
|
60
|
-
|
|
60
|
+
new_with_platform(new_merchant, mac)
|
|
61
61
|
end
|
|
62
62
|
|
|
63
63
|
# Find a specific Merchant ID based on the bundle_id
|
|
@@ -68,6 +68,131 @@ module Spaceship
|
|
|
68
68
|
merchant.bundle_id == bundle_id
|
|
69
69
|
end
|
|
70
70
|
end
|
|
71
|
+
|
|
72
|
+
# Apple does not return the platform, so we record the one that was asked for.
|
|
73
|
+
def new_with_platform(attrs, mac)
|
|
74
|
+
new(attrs).tap { |merchant| merchant.platform = mac ? 'mac' : 'ios' }
|
|
75
|
+
end
|
|
76
|
+
private :new_with_platform
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Represents a domain registered to an Apple Pay Merchant ID
|
|
80
|
+
class Domain < PortalBase
|
|
81
|
+
# @return (String) The identifier of this domain, provided by the Dev Portal
|
|
82
|
+
# @example
|
|
83
|
+
# "UFVN6788YA"
|
|
84
|
+
attr_accessor :domain_id
|
|
85
|
+
|
|
86
|
+
# @return (String) The domain name you registered
|
|
87
|
+
# @example
|
|
88
|
+
# "krausefx.com"
|
|
89
|
+
attr_accessor :name
|
|
90
|
+
|
|
91
|
+
# @return (String) Status of the domain verification
|
|
92
|
+
# @example
|
|
93
|
+
# "verified"
|
|
94
|
+
attr_accessor :status
|
|
95
|
+
|
|
96
|
+
# @return (String) The URL Apple expects to serve the domain association file
|
|
97
|
+
# @example
|
|
98
|
+
# "https://krausefx.com/.well-known/apple-developer-merchantid-domain-association.txt"
|
|
99
|
+
attr_accessor :path
|
|
100
|
+
|
|
101
|
+
# @return (Bool) Can this domain be verified by the current team?
|
|
102
|
+
attr_accessor :can_verify
|
|
103
|
+
|
|
104
|
+
# @return (String) The date this domain's verification expires
|
|
105
|
+
# @example
|
|
106
|
+
# "2026-10-11"
|
|
107
|
+
attr_accessor :expiration_date
|
|
108
|
+
|
|
109
|
+
# @return (String) A human readable version of the expiration date
|
|
110
|
+
# @example
|
|
111
|
+
# "Oct 11, 2026"
|
|
112
|
+
attr_accessor :expiration_date_string
|
|
113
|
+
|
|
114
|
+
# @return (Spaceship::Portal::Merchant) The merchant this domain is registered to
|
|
115
|
+
# This is not part of Apple's response, it gets passed in by whoever fetched the domain
|
|
116
|
+
attr_accessor :merchant
|
|
117
|
+
|
|
118
|
+
attr_mapping(
|
|
119
|
+
'displayId' => :domain_id,
|
|
120
|
+
'name' => :name,
|
|
121
|
+
'status' => :status,
|
|
122
|
+
'path' => :path,
|
|
123
|
+
'canVerify' => :can_verify,
|
|
124
|
+
'expirationDate' => :expiration_date,
|
|
125
|
+
'expirationDateString' => :expiration_date_string
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
# @param attrs [Hash] The domain attributes as returned by the Dev Portal
|
|
129
|
+
# @param existing_client [Spaceship::Client] The client to use for requests
|
|
130
|
+
# @param merchant [Merchant] The merchant this domain is registered to
|
|
131
|
+
def initialize(attrs = {}, existing_client = nil, merchant: nil)
|
|
132
|
+
super(attrs, existing_client)
|
|
133
|
+
@merchant = merchant
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
class << self
|
|
137
|
+
# @param merchant [Merchant] The merchant to fetch the domains of
|
|
138
|
+
# @return (Array) Returns all domains registered to the given merchant
|
|
139
|
+
def all(merchant)
|
|
140
|
+
client.merchant_domains(merchant.merchant_id, mac: merchant.mac?).map do |domain|
|
|
141
|
+
new(domain, client, merchant: merchant)
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Registers a new domain for a Merchant ID on the Apple Dev Portal
|
|
146
|
+
#
|
|
147
|
+
# @param merchant [Merchant] the merchant to register the domain for
|
|
148
|
+
# @param domain_name [String] the domain name to register
|
|
149
|
+
# @return (Domain) The Domain you just registered
|
|
150
|
+
def create!(merchant: nil, domain_name: nil)
|
|
151
|
+
new_domain = client.create_merchant_domain!(merchant.merchant_id, domain_name, mac: merchant.mac?)
|
|
152
|
+
new(new_domain, client, merchant: merchant)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Find a specific domain of a merchant based on the domain name
|
|
156
|
+
#
|
|
157
|
+
# @param merchant [Merchant] The merchant to search the domains of
|
|
158
|
+
# @param name [String] the domain name to look for
|
|
159
|
+
# @return (Domain) The Domain you're looking for. This is nil if the domain can't be found.
|
|
160
|
+
def find(merchant, name)
|
|
161
|
+
all(merchant).find do |domain|
|
|
162
|
+
domain.name == name
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Downloads the domain association file for this domain. It has to be
|
|
168
|
+
# served at `path` before Apple is able to verify the domain
|
|
169
|
+
#
|
|
170
|
+
# @return (String) The url-safe base64 encoded contents of the file
|
|
171
|
+
def verification_file
|
|
172
|
+
client.merchant_domain_get_verification_file(domain_id, mac: merchant.mac?)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Asks Apple to verify this domain. The `verification_file` has to be
|
|
176
|
+
# served at `path` before calling this
|
|
177
|
+
#
|
|
178
|
+
# @return (Hash) The response of the Dev Portal
|
|
179
|
+
def verify
|
|
180
|
+
client.merchant_domain_verify(domain_id, mac: merchant.mac?)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Delete this Domain
|
|
184
|
+
# @return (Domain) The domain you just deleted
|
|
185
|
+
def delete!
|
|
186
|
+
client.delete_merchant_domain!(domain_id, merchant.merchant_id, mac: merchant.mac?)
|
|
187
|
+
self
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# @return (Array of Spaceship::Portal::Merchant::Domain) The domains registered to this merchant
|
|
192
|
+
def domains
|
|
193
|
+
@domains ||= client.merchant_domains(merchant_id, mac: mac?).map do |domain|
|
|
194
|
+
Domain.new(domain, client, merchant: self)
|
|
195
|
+
end
|
|
71
196
|
end
|
|
72
197
|
|
|
73
198
|
# Delete this Merchant
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require 'base64'
|
|
2
|
+
|
|
1
3
|
require_relative '../client'
|
|
2
4
|
|
|
3
5
|
require_relative 'app'
|
|
@@ -344,6 +346,62 @@ module Spaceship
|
|
|
344
346
|
end
|
|
345
347
|
end
|
|
346
348
|
|
|
349
|
+
def merchant_domains(merchant_id, mac: false)
|
|
350
|
+
r = request(:post, "account/#{platform_slug(mac)}/identifiers/listDomainsForMerchant", {
|
|
351
|
+
merchantId: merchant_id,
|
|
352
|
+
teamId: team_id
|
|
353
|
+
})
|
|
354
|
+
parse_response(r, 'domainList')
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
def merchant_domain_get_verification_file(domain_id, mac: false)
|
|
358
|
+
r = request(:get, "account/#{platform_slug(mac)}/identifiers/downloadDomainVerificationFile", {
|
|
359
|
+
teamId: team_id,
|
|
360
|
+
domainId: domain_id
|
|
361
|
+
})
|
|
362
|
+
a = parse_response(r)
|
|
363
|
+
if r.success? && a.kind_of?(String)
|
|
364
|
+
begin
|
|
365
|
+
return a if Base64.urlsafe_decode64(a.delete("\r\n")).include?("Apple Inc.")
|
|
366
|
+
# if not valid pkcs#7 just fall through
|
|
367
|
+
rescue ArgumentError
|
|
368
|
+
end
|
|
369
|
+
end
|
|
370
|
+
raise UnexpectedResponse.new, "Couldn't download verification file, got this instead: #{a}"
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
def merchant_domain_verify(domain_id, mac: false)
|
|
374
|
+
ensure_csrf(Spaceship::Portal::Merchant)
|
|
375
|
+
|
|
376
|
+
r = request(:post, "account/#{platform_slug(mac)}/identifiers/verifyDomain", {
|
|
377
|
+
domainId: domain_id,
|
|
378
|
+
teamId: team_id
|
|
379
|
+
})
|
|
380
|
+
parse_response(r)
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
def create_merchant_domain!(merchant_id, domain_name, mac: false)
|
|
384
|
+
ensure_csrf(Spaceship::Portal::Merchant)
|
|
385
|
+
|
|
386
|
+
r = request(:post, "account/#{platform_slug(mac)}/identifiers/registerDomain", {
|
|
387
|
+
domainName: domain_name,
|
|
388
|
+
merchantId: merchant_id,
|
|
389
|
+
teamId: team_id
|
|
390
|
+
})
|
|
391
|
+
parse_response(r, 'domainList').first
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
def delete_merchant_domain!(domain_id, merchant_id, mac: false)
|
|
395
|
+
ensure_csrf(Spaceship::Portal::Merchant)
|
|
396
|
+
|
|
397
|
+
r = request(:post, "account/#{platform_slug(mac)}/identifiers/removeDomain", {
|
|
398
|
+
merchantId: merchant_id,
|
|
399
|
+
domainId: domain_id,
|
|
400
|
+
teamId: team_id
|
|
401
|
+
})
|
|
402
|
+
parse_response(r)
|
|
403
|
+
end
|
|
404
|
+
|
|
347
405
|
def create_merchant!(name, bundle_id, mac: false)
|
|
348
406
|
ensure_csrf(Spaceship::Portal::Merchant)
|
|
349
407
|
|
|
@@ -31,7 +31,6 @@ module Spaceship
|
|
|
31
31
|
# {...}
|
|
32
32
|
# ]
|
|
33
33
|
|
|
34
|
-
# rubocop:disable Require/MissingRequireStatement
|
|
35
34
|
def self.ci?
|
|
36
35
|
if Object.const_defined?("FastlaneCore") && FastlaneCore.const_defined?("Helper")
|
|
37
36
|
return FastlaneCore::Helper.ci?
|
|
@@ -45,7 +44,6 @@ module Spaceship
|
|
|
45
44
|
end
|
|
46
45
|
return true
|
|
47
46
|
end
|
|
48
|
-
# rubocop:enable Require/MissingRequireStatement
|
|
49
47
|
|
|
50
48
|
def select_team(team_id: nil, team_name: nil)
|
|
51
49
|
teams = client.teams
|
|
@@ -48,7 +48,7 @@ module Spaceship
|
|
|
48
48
|
# @param mac [Bool] Fetches Mac website push if true
|
|
49
49
|
# @return (Array) Returns all website push available for this account
|
|
50
50
|
def all(mac: false)
|
|
51
|
-
client.website_push(mac: mac).map { |website_push|
|
|
51
|
+
client.website_push(mac: mac).map { |website_push| new_with_platform(website_push, mac) }
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
# Creates a new Website Push ID on the Apple Dev Portal
|
|
@@ -59,7 +59,7 @@ module Spaceship
|
|
|
59
59
|
# @return (Website Push) The Website Push you just created
|
|
60
60
|
def create!(bundle_id: nil, name: nil, mac: false)
|
|
61
61
|
new_website_push = client.create_website_push!(name, bundle_id, mac: mac)
|
|
62
|
-
|
|
62
|
+
new_with_platform(new_website_push, mac)
|
|
63
63
|
end
|
|
64
64
|
|
|
65
65
|
# Find a specific Website Push ID based on the bundle_id
|
|
@@ -70,6 +70,12 @@ module Spaceship
|
|
|
70
70
|
website_push.bundle_id == bundle_id
|
|
71
71
|
end
|
|
72
72
|
end
|
|
73
|
+
|
|
74
|
+
# Apple does not return the platform, so we record the one that was asked for.
|
|
75
|
+
def new_with_platform(attrs, mac)
|
|
76
|
+
new(attrs).tap { |website_push| website_push.platform = mac ? 'mac' : 'ios' }
|
|
77
|
+
end
|
|
78
|
+
private :new_with_platform
|
|
73
79
|
end
|
|
74
80
|
|
|
75
81
|
# Delete this Website Push ID.
|
|
@@ -286,28 +286,6 @@ module Spaceship
|
|
|
286
286
|
handle_response(response)
|
|
287
287
|
end
|
|
288
288
|
|
|
289
|
-
##
|
|
290
|
-
# @!group AppTestInfo
|
|
291
|
-
##
|
|
292
|
-
|
|
293
|
-
def get_app_test_info(app_id: nil)
|
|
294
|
-
assert_required_params(__method__, binding)
|
|
295
|
-
|
|
296
|
-
response = request(:get, "providers/#{team_id}/apps/#{app_id}/testInfo")
|
|
297
|
-
handle_response(response)
|
|
298
|
-
end
|
|
299
|
-
|
|
300
|
-
def put_app_test_info(app_id: nil, app_test_info: nil)
|
|
301
|
-
assert_required_params(__method__, binding)
|
|
302
|
-
|
|
303
|
-
response = request(:put) do |req|
|
|
304
|
-
req.url("providers/#{team_id}/apps/#{app_id}/testInfo")
|
|
305
|
-
req.body = app_test_info.to_json
|
|
306
|
-
req.headers['Content-Type'] = 'application/json'
|
|
307
|
-
end
|
|
308
|
-
handle_response(response)
|
|
309
|
-
end
|
|
310
|
-
|
|
311
289
|
protected
|
|
312
290
|
|
|
313
291
|
def handle_response(response)
|
|
@@ -20,10 +20,21 @@ module Spaceship
|
|
|
20
20
|
elsif r.body.kind_of?(Hash) && r.body["trustedPhoneNumbers"].kind_of?(Array) && r.body["trustedPhoneNumbers"].first.kind_of?(Hash)
|
|
21
21
|
handle_two_factor(r)
|
|
22
22
|
else
|
|
23
|
+
raise_two_factor_service_errors!(r.body)
|
|
23
24
|
raise "Although response from Apple indicated activated Two-step Verification or Two-factor Authentication, spaceship didn't know how to handle this response: #{r.body}"
|
|
24
25
|
end
|
|
25
26
|
end
|
|
26
27
|
|
|
28
|
+
# Apple answers without any way to verify, e.g. when it cannot send the SMS code (-28248). See fastlane#29443.
|
|
29
|
+
def raise_two_factor_service_errors!(body)
|
|
30
|
+
errors = body.kind_of?(Hash) ? Array(body["serviceErrors"]) : []
|
|
31
|
+
return if errors.empty?
|
|
32
|
+
|
|
33
|
+
message = errors.map { |error| "#{error['message']} (#{error['code']})" }.join(" ")
|
|
34
|
+
message += " This Apple ID has no trusted devices: signing in to it on an Apple device adds one." if body["noTrustedDevices"]
|
|
35
|
+
raise Tunes::Error.new, "Apple could not start two-factor authentication: #{message}"
|
|
36
|
+
end
|
|
37
|
+
|
|
27
38
|
def handle_two_step(response)
|
|
28
39
|
if response.body.fetch("securityCode", {})["tooManyCodesLock"].to_s.length > 0
|
|
29
40
|
raise Tunes::Error.new, "Too many verification codes have been sent. Enter the last code you received, use one of your devices, or try again later."
|
|
@@ -108,6 +108,7 @@ module Supply
|
|
|
108
108
|
conflicting_options: [:issuer, :key, :json_key],
|
|
109
109
|
optional: true,
|
|
110
110
|
description: "The raw content of a Google credentials JSON file (Application Default, Workload Identity, or Service Account), used to authenticate with Google",
|
|
111
|
+
sensitive: true,
|
|
111
112
|
code_gen_sensitive: true,
|
|
112
113
|
default_value: CredentialsManager::AppfileConfig.try_fetch_value(:json_key_data_raw),
|
|
113
114
|
default_value_dynamic: true,
|
data/supply/lib/supply.rb
CHANGED