fastlane 2.155.1 → 2.157.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +72 -72
  3. data/deliver/lib/deliver.rb +1 -0
  4. data/deliver/lib/deliver/app_screenshot_iterator.rb +95 -0
  5. data/deliver/lib/deliver/detect_values.rb +4 -1
  6. data/deliver/lib/deliver/languages.rb +7 -0
  7. data/deliver/lib/deliver/loader.rb +4 -5
  8. data/deliver/lib/deliver/queue_worker.rb +64 -0
  9. data/deliver/lib/deliver/runner.rb +7 -5
  10. data/deliver/lib/deliver/upload_screenshots.rb +143 -128
  11. data/fastlane/lib/fastlane/actions/app_store_connect_api_key.rb +120 -0
  12. data/fastlane/lib/fastlane/actions/commit_version_bump.rb +1 -1
  13. data/fastlane/lib/fastlane/actions/docs/upload_to_play_store.md +2 -0
  14. data/fastlane/lib/fastlane/actions/docs/upload_to_testflight.md +17 -1
  15. data/fastlane/lib/fastlane/actions/set_changelog.rb +2 -2
  16. data/fastlane/lib/fastlane/actions/sonar.rb +5 -0
  17. data/fastlane/lib/fastlane/actions/spaceship_stats.rb +73 -0
  18. data/fastlane/lib/fastlane/actions/upload_to_testflight.rb +4 -0
  19. data/fastlane/lib/fastlane/version.rb +1 -1
  20. data/fastlane/swift/Deliverfile.swift +1 -1
  21. data/fastlane/swift/DeliverfileProtocol.swift +1 -1
  22. data/fastlane/swift/Fastlane.swift +68 -8
  23. data/fastlane/swift/Gymfile.swift +1 -1
  24. data/fastlane/swift/GymfileProtocol.swift +1 -1
  25. data/fastlane/swift/Matchfile.swift +1 -1
  26. data/fastlane/swift/MatchfileProtocol.swift +1 -1
  27. data/fastlane/swift/Precheckfile.swift +1 -1
  28. data/fastlane/swift/PrecheckfileProtocol.swift +1 -1
  29. data/fastlane/swift/Scanfile.swift +1 -1
  30. data/fastlane/swift/ScanfileProtocol.swift +1 -1
  31. data/fastlane/swift/Screengrabfile.swift +1 -1
  32. data/fastlane/swift/ScreengrabfileProtocol.swift +1 -1
  33. data/fastlane/swift/Snapshotfile.swift +1 -1
  34. data/fastlane/swift/SnapshotfileProtocol.swift +1 -1
  35. data/fastlane_core/lib/fastlane_core/itunes_transporter.rb +71 -42
  36. data/fastlane_core/lib/fastlane_core/project.rb +1 -0
  37. data/gym/lib/gym/error_handler.rb +1 -1
  38. data/gym/lib/gym/generators/build_command_generator.rb +0 -1
  39. data/pilot/lib/pilot/build_manager.rb +18 -4
  40. data/pilot/lib/pilot/manager.rb +15 -5
  41. data/pilot/lib/pilot/options.rb +16 -0
  42. data/produce/lib/produce/itunes_connect.rb +2 -2
  43. data/scan/lib/scan/test_command_generator.rb +3 -1
  44. data/screengrab/lib/screengrab/runner.rb +8 -7
  45. data/sigh/lib/sigh/runner.rb +9 -5
  46. data/snapshot/lib/snapshot/test_command_generator_base.rb +3 -1
  47. data/spaceship/lib/spaceship.rb +4 -0
  48. data/spaceship/lib/spaceship/client.rb +2 -0
  49. data/spaceship/lib/spaceship/connect_api.rb +0 -15
  50. data/spaceship/lib/spaceship/connect_api/api_client.rb +270 -0
  51. data/spaceship/lib/spaceship/connect_api/client.rb +139 -213
  52. data/spaceship/lib/spaceship/connect_api/models/profile.rb +3 -2
  53. data/spaceship/lib/spaceship/connect_api/provisioning/client.rb +8 -17
  54. data/spaceship/lib/spaceship/connect_api/provisioning/provisioning.rb +75 -64
  55. data/spaceship/lib/spaceship/connect_api/spaceship.rb +94 -0
  56. data/spaceship/lib/spaceship/connect_api/testflight/client.rb +8 -17
  57. data/spaceship/lib/spaceship/connect_api/testflight/testflight.rb +288 -277
  58. data/spaceship/lib/spaceship/connect_api/token.rb +46 -5
  59. data/spaceship/lib/spaceship/connect_api/token_refresh_middleware.rb +24 -0
  60. data/spaceship/lib/spaceship/connect_api/tunes/client.rb +8 -17
  61. data/spaceship/lib/spaceship/connect_api/tunes/tunes.rb +717 -706
  62. data/spaceship/lib/spaceship/connect_api/users/client.rb +8 -17
  63. data/spaceship/lib/spaceship/connect_api/users/users.rb +28 -17
  64. data/spaceship/lib/spaceship/stats_middleware.rb +65 -0
  65. metadata +25 -18
  66. data/sigh/lib/sigh/.runner.rb.swp +0 -0
  67. data/spaceship/lib/spaceship/connect_api/models/.device.rb.swp +0 -0
@@ -4,10 +4,17 @@ require 'digest/md5'
4
4
  require_relative 'app_screenshot'
5
5
  require_relative 'module'
6
6
  require_relative 'loader'
7
+ require_relative 'queue_worker'
8
+ require_relative 'app_screenshot_iterator'
7
9
 
8
10
  module Deliver
9
11
  # upload screenshots to App Store Connect
10
12
  class UploadScreenshots
13
+ DeleteScreenshotJob = Struct.new(:app_screenshot, :localization, :app_screenshot_set)
14
+ UploadScreenshotJob = Struct.new(:app_screenshot_set, :path)
15
+
16
+ NUMBER_OF_THREADS = Helper.test? ? 1 : [ENV.fetch("DELIVER_NUMBER_OF_THREADS", 10).to_i, 10].min
17
+
11
18
  def upload(options, screenshots)
12
19
  return if options[:skip_screenshots]
13
20
  return if options[:edit_live]
@@ -50,57 +57,47 @@ module Deliver
50
57
  localizations = version.get_app_store_version_localizations
51
58
  end
52
59
 
53
- upload_screenshots(screenshots_per_language, localizations, options)
60
+ upload_screenshots(localizations, screenshots_per_language)
61
+
62
+ Helper.show_loading_indicator("Sorting screenshots uploaded...")
63
+ sort_screenshots(localizations)
64
+ Helper.hide_loading_indicator
65
+
66
+ UI.success("Successfully uploaded screenshots to App Store Connect")
54
67
  end
55
68
 
56
69
  def delete_screenshots(localizations, screenshots_per_language, tries: 5)
57
70
  tries -= 1
58
71
 
59
- # Get localizations on version
60
- localizations.each do |localization|
61
- # Only delete screenshots if trying to upload
62
- next unless screenshots_per_language.keys.include?(localization.locale)
63
-
64
- # Iterate over all screenshots for each set and delete
65
- screenshot_sets = localization.get_app_screenshot_sets
66
-
67
- # Multi threading delete on single localization
68
- threads = []
69
- errors = []
70
-
71
- screenshot_sets.each do |screenshot_set|
72
- UI.message("Removing all previously uploaded screenshots for '#{localization.locale}' '#{screenshot_set.screenshot_display_type}'...")
73
- screenshot_set.app_screenshots.each do |screenshot|
74
- UI.verbose("Deleting screenshot - #{localization.locale} #{screenshot_set.screenshot_display_type} #{screenshot.id}")
75
- threads << Thread.new do
76
- begin
77
- screenshot.delete!
78
- UI.verbose("Deleted screenshot - #{localization.locale} #{screenshot_set.screenshot_display_type} #{screenshot.id}")
79
- rescue => error
80
- UI.verbose("Failed to delete screenshot - #{localization.locale} #{screenshot_set.screenshot_display_type} #{screenshot.id}")
81
- errors << error
82
- end
83
- end
84
- end
72
+ worker = QueueWorker.new(NUMBER_OF_THREADS) do |job|
73
+ start_time = Time.now
74
+ target = "#{job.localization.locale} #{job.app_screenshot_set.screenshot_display_type} #{job.app_screenshot.id}"
75
+ begin
76
+ UI.verbose("Deleting '#{target}'")
77
+ job.app_screenshot.delete!
78
+ UI.message("Deleted '#{target}' - (#{Time.now - start_time} secs)")
79
+ rescue => error
80
+ UI.error("Failed to delete screenshot #{target} - (#{Time.now - start_time} secs)")
81
+ UI.error(error.message)
85
82
  end
83
+ end
86
84
 
87
- sleep(1) # Feels bad but sleeping a bit to let the threads catchup
88
-
89
- unless threads.empty?
90
- Helper.show_loading_indicator("Waiting for screenshots to be deleted for '#{localization.locale}'... (might be slow)") unless FastlaneCore::Globals.verbose?
91
- threads.each(&:join)
92
- Helper.hide_loading_indicator unless FastlaneCore::Globals.verbose?
93
- end
85
+ iterator = AppScreenshotIterator.new(localizations)
86
+ iterator.each_app_screenshot do |localization, app_screenshot_set, app_screenshot|
87
+ # Only delete screenshots if trying to upload
88
+ next unless screenshots_per_language.keys.include?(localization.locale)
94
89
 
95
- # Crash if any errors happen while deleting
96
- errors.each do |error|
97
- UI.error(error.message)
98
- end
90
+ UI.verbose("Queued delete sceeenshot job for #{localization.locale} #{app_screenshot_set.screenshot_display_type} #{app_screenshot.id}")
91
+ worker.enqueue(DeleteScreenshotJob.new(app_screenshot, localization, app_screenshot_set))
99
92
  end
100
93
 
94
+ worker.start
95
+
101
96
  # Verify all screenshots have been deleted
102
97
  # Sometimes API requests will fail but screenshots will still be deleted
103
- count = count_screenshots(localizations)
98
+ count = iterator.each_app_screenshot_set.map { |_, app_screenshot_set| app_screenshot_set }
99
+ .reduce(0) { |sum, app_screenshot_set| sum + app_screenshot_set.app_screenshots.size }
100
+
104
101
  UI.important("Number of screenshots not deleted: #{count}")
105
102
  if count > 0
106
103
  if tries.zero?
@@ -114,113 +111,127 @@ module Deliver
114
111
  end
115
112
  end
116
113
 
117
- def count_screenshots(localizations)
118
- count = 0
119
- localizations.each do |localization|
120
- screenshot_sets = localization.get_app_screenshot_sets
121
- screenshot_sets.each do |screenshot_set|
122
- count += screenshot_set.app_screenshots.size
114
+ def upload_screenshots(localizations, screenshots_per_language, tries: 5)
115
+ tries -= 1
116
+
117
+ # Upload screenshots
118
+ worker = QueueWorker.new(NUMBER_OF_THREADS) do |job|
119
+ begin
120
+ UI.verbose("Uploading '#{job.path}'...")
121
+ start_time = Time.now
122
+ job.app_screenshot_set.upload_screenshot(path: job.path, wait_for_processing: false)
123
+ UI.message("Uploaded '#{job.path}'... (#{Time.now - start_time} secs)")
124
+ rescue => error
125
+ UI.error(error)
123
126
  end
124
127
  end
125
128
 
126
- return count
127
- end
129
+ number_of_screenshots = 0
130
+ iterator = AppScreenshotIterator.new(localizations)
131
+ iterator.each_local_screenshot(screenshots_per_language) do |localization, app_screenshot_set, screenshot, index|
132
+ if index >= 10
133
+ UI.error("Too many screenshots found for device '#{screenshot.device_type}' in '#{screenshot.language}', skipping this one (#{screenshot.path})")
134
+ next
135
+ end
128
136
 
129
- def upload_screenshots(screenshots_per_language, localizations, options)
130
- # Check if should wait for processing
131
- # Default to waiting if submitting for review (since needed for submission)
132
- # Otherwise use enviroment variable
133
- if ENV["DELIVER_SKIP_WAIT_FOR_SCREENSHOT_PROCESSING"].nil?
134
- wait_for_processing = options[:submit_for_review]
135
- UI.verbose("Setting wait_for_processing from ':submit_for_review' option")
136
- else
137
- UI.verbose("Setting wait_for_processing from 'DELIVER_SKIP_WAIT_FOR_SCREENSHOT_PROCESSING' environment variable")
138
- wait_for_processing = !FastlaneCore::Env.truthy?("DELIVER_SKIP_WAIT_FOR_SCREENSHOT_PROCESSING")
139
- end
137
+ checksum = UploadScreenshots.calculate_checksum(screenshot.path)
138
+ duplicate = (app_screenshot_set.app_screenshots || []).any? { |s| s.source_file_checksum == checksum }
140
139
 
141
- if wait_for_processing
142
- UI.important("Will wait for screenshot image processing")
143
- UI.important("Set env DELIVER_SKIP_WAIT_FOR_SCREENSHOT_PROCESSING=true to skip waiting for screenshots to process")
144
- else
145
- UI.important("Skipping the wait for screenshot image processing (which may affect submission)")
146
- UI.important("Set env DELIVER_SKIP_WAIT_FOR_SCREENSHOT_PROCESSING=false to wait for screenshots to process")
140
+ # Enqueue uploading job if it's not duplicated otherwise screenshot will be skipped
141
+ if duplicate
142
+ UI.message("Previous uploaded. Skipping '#{screenshot.path}'...")
143
+ else
144
+ worker.enqueue(UploadScreenshotJob.new(app_screenshot_set, screenshot.path))
145
+ end
146
+
147
+ number_of_screenshots += 1
147
148
  end
148
149
 
149
- # Upload screenshots
150
- indized = {} # per language and device type
150
+ worker.start
151
151
 
152
- screenshots_per_language.each do |language, screenshots_for_language|
153
- # Find localization to upload screenshots to
154
- localization = localizations.find do |l|
155
- l.locale == language
156
- end
152
+ UI.verbose('Uploading jobs are completed')
157
153
 
158
- unless localization
159
- UI.error("Couldn't find localization on version for #{language}")
160
- next
161
- end
154
+ Helper.show_loading_indicator("Waiting for all the screenshots processed...")
155
+ states = wait_for_complete(iterator)
156
+ Helper.hide_loading_indicator
157
+ retry_upload_screenshots_if_needed(iterator, states, number_of_screenshots, tries, localizations, screenshots_per_language)
162
158
 
163
- indized[localization.locale] ||= {}
159
+ UI.message("Successfully uploaded all screenshots")
160
+ end
164
161
 
165
- # Create map to find screenshot set to add screenshot to
166
- app_screenshot_sets_map = {}
167
- app_screenshot_sets = localization.get_app_screenshot_sets
168
- app_screenshot_sets.each do |app_screenshot_set|
169
- app_screenshot_sets_map[app_screenshot_set.screenshot_display_type] = app_screenshot_set
162
+ # Verify all screenshots have been processed
163
+ def wait_for_complete(iterator)
164
+ loop do
165
+ states = iterator.each_app_screenshot.map { |_, _, app_screenshot| app_screenshot }.each_with_object({}) do |app_screenshot, hash|
166
+ state = app_screenshot.asset_delivery_state['state']
167
+ hash[state] ||= 0
168
+ hash[state] += 1
169
+ end
170
170
 
171
- # Set initial screnshot count
172
- indized[localization.locale][app_screenshot_set.screenshot_display_type] ||= {
173
- count: app_screenshot_set.app_screenshots.size,
174
- checksums: []
175
- }
171
+ is_processing = states.fetch('UPLOAD_COMPLETE', 0) > 0
172
+ return states unless is_processing
176
173
 
177
- checksums = app_screenshot_set.app_screenshots.map(&:source_file_checksum).uniq
178
- indized[localization.locale][app_screenshot_set.screenshot_display_type][:checksums] = checksums
179
- end
174
+ UI.verbose("There are still incomplete screenshots - #{states}")
175
+ sleep(5)
176
+ end
177
+ end
180
178
 
181
- UI.message("Uploading #{screenshots_for_language.length} screenshots for language #{language}")
182
- screenshots_for_language.each do |screenshot|
183
- display_type = screenshot.device_type
184
- set = app_screenshot_sets_map[display_type]
179
+ # Verify all screenshots states on App Store Connect are okay
180
+ def retry_upload_screenshots_if_needed(iterator, states, number_of_screenshots, tries, localizations, screenshots_per_language)
181
+ is_failure = states.fetch("FAILED", 0) > 0
182
+ is_missing_screenshot = !screenshots_per_language.empty? && !verify_local_screenshots_are_uploaded(iterator, screenshots_per_language)
183
+ return unless is_failure || is_missing_screenshot
185
184
 
186
- if display_type.nil?
187
- UI.error("Error... Screenshot size #{screenshot.screen_size} not valid for App Store Connect")
188
- next
189
- end
185
+ if tries.zero?
186
+ iterator.each_app_screenshot.select { |_, _, app_screenshot| app_screenshot.error? }.each do |localization, _, app_screenshot|
187
+ UI.error("#{app_screenshot.file_name} for #{localization.locale} has error(s) - #{app_screenshot.error_messages.join(', ')}")
188
+ end
189
+ incomplete_screenshot_count = states.reject { |k, v| k == 'COMPLETE' }.reduce(0) { |sum, (k, v)| sum + v }
190
+ UI.user_error!("Failed verification of all screenshots uploaded... #{incomplete_screenshot_count} incomplete screenshot(s) still exist")
191
+ else
192
+ UI.error("Failed to upload all screenshots... Tries remaining: #{tries}")
193
+ # Delete bad entries before retry
194
+ iterator.each_app_screenshot do |_, _, app_screenshot|
195
+ app_screenshot.delete! unless app_screenshot.complete?
196
+ end
197
+ upload_screenshots(localizations, screenshots_per_language, tries: tries)
198
+ end
199
+ end
190
200
 
191
- unless set
192
- set = localization.create_app_screenshot_set(attributes: {
193
- screenshotDisplayType: display_type
194
- })
195
- app_screenshot_sets_map[display_type] = set
201
+ # Return `true` if all the local screenshots are uploaded to App Store Connect
202
+ def verify_local_screenshots_are_uploaded(iterator, screenshots_per_language)
203
+ # Check if local screenshots' checksum exist on App Store Connect
204
+ checksum_to_app_screenshot = iterator.each_app_screenshot.map { |_, _, app_screenshot| [app_screenshot.source_file_checksum, app_screenshot] }.to_h
196
205
 
197
- indized[localization.locale][set.screenshot_display_type] = {
198
- count: 0,
199
- checksums: []
200
- }
201
- end
206
+ missing_local_screenshots = iterator.each_local_screenshot(screenshots_per_language).select do |_, _, local_screenshot, index|
207
+ checksum = UploadScreenshots.calculate_checksum(local_screenshot.path)
208
+ checksum_to_app_screenshot[checksum].nil? && index < 10 # if index is more than 10, it's skipped
209
+ end
202
210
 
203
- index = indized[localization.locale][set.screenshot_display_type][:count]
211
+ missing_local_screenshots.each do |_, _, screenshot, _|
212
+ UI.error("#{screenshot.path} is missing on App Store Connect.")
213
+ end
204
214
 
205
- if index >= 10
206
- UI.error("Too many screenshots found for device '#{screenshot.device_type}' in '#{screenshot.language}', skipping this one (#{screenshot.path})")
207
- next
208
- end
215
+ missing_local_screenshots.empty?
216
+ end
209
217
 
210
- bytes = File.binread(screenshot.path)
211
- checksum = Digest::MD5.hexdigest(bytes)
212
- duplicate = indized[localization.locale][set.screenshot_display_type][:checksums].include?(checksum)
218
+ def sort_screenshots(localizations)
219
+ iterator = AppScreenshotIterator.new(localizations)
213
220
 
214
- if duplicate
215
- UI.message("Previous uploaded. Skipping '#{screenshot.path}'...")
216
- else
217
- indized[localization.locale][set.screenshot_display_type][:count] += 1
218
- UI.message("Uploading '#{screenshot.path}'...")
219
- set.upload_screenshot(path: screenshot.path, wait_for_processing: wait_for_processing)
220
- end
221
+ # Re-order screenshots within app_screenshot_set
222
+ worker = QueueWorker.new(NUMBER_OF_THREADS) do |app_screenshot_set|
223
+ original_ids = app_screenshot_set.app_screenshots.map(&:id)
224
+ sorted_ids = app_screenshot_set.app_screenshots.sort_by(&:file_name).map(&:id)
225
+ if original_ids != sorted_ids
226
+ app_screenshot_set.reorder_screenshots(app_screenshot_ids: sorted_ids)
221
227
  end
222
228
  end
223
- UI.success("Successfully uploaded screenshots to App Store Connect")
229
+
230
+ iterator.each_app_screenshot_set do |_, app_screenshot_set|
231
+ worker.enqueue(app_screenshot_set)
232
+ end
233
+
234
+ worker.start
224
235
  end
225
236
 
226
237
  def collect_screenshots(options)
@@ -293,11 +304,15 @@ module Deliver
293
304
 
294
305
  # helper method so Spaceship::Tunes.client.available_languages is easier to test
295
306
  def self.available_languages
296
- if Helper.test?
297
- FastlaneCore::Languages::ALL_LANGUAGES
298
- else
299
- Spaceship::Tunes.client.available_languages
300
- end
307
+ # 2020-08-24 - Available locales are not available as an endpoint in App Store Connect
308
+ # Update with Spaceship::Tunes.client.available_languages.sort (as long as endpoint is avilable)
309
+ Deliver::Languages::ALL_LANGUAGES
310
+ end
311
+
312
+ # helper method to mock this step in tests
313
+ def self.calculate_checksum(path)
314
+ bytes = File.binread(path)
315
+ Digest::MD5.hexdigest(bytes)
301
316
  end
302
317
  end
303
318
  end
@@ -0,0 +1,120 @@
1
+ module Fastlane
2
+ module Actions
3
+ module SharedValues
4
+ APP_STORE_CONNECT_API_KEY = :APP_STORE_CONNECT_API_KEY
5
+ end
6
+
7
+ class AppStoreConnectApiKeyAction < Action
8
+ def self.run(options)
9
+ key_id = options[:key_id]
10
+ issuer_id = options[:issuer_id]
11
+ key_content = options[:key_content]
12
+ key_filepath = options[:key_filepath]
13
+ duration = options[:duration]
14
+ in_house = options[:in_house]
15
+
16
+ if key_content.nil? && key_filepath.nil?
17
+ UI.user_error!(":key_content or :key_filepath is required")
18
+ end
19
+
20
+ # This hash matches the named arguments on
21
+ # the Spaceship::ConnectAPI::Token.create method
22
+ key = {
23
+ key_id: key_id,
24
+ issuer_id: issuer_id,
25
+ key: key_content || File.binread(key_filepath),
26
+ duration: duration,
27
+ in_house: in_house
28
+ }
29
+
30
+ Actions.lane_context[SharedValues::APP_STORE_CONNECT_API_KEY] = key
31
+
32
+ return key
33
+ end
34
+
35
+ def self.description
36
+ "Load the App Store Connect API token to use in other fastlane tools and actions"
37
+ end
38
+
39
+ def self.available_options
40
+ [
41
+ FastlaneCore::ConfigItem.new(key: :key_id,
42
+ env_name: "APP_STORE_CONNECT_API_KEY_KEY_ID",
43
+ description: "The key ID"),
44
+ FastlaneCore::ConfigItem.new(key: :issuer_id,
45
+ env_name: "APP_STORE_CONNECT_API_KEY_ISSUER_ID",
46
+ description: "The issuer ID"),
47
+ FastlaneCore::ConfigItem.new(key: :key_filepath,
48
+ env_name: "APP_STORE_CONNECT_API_KEY_KEY_FILEPATH",
49
+ description: "The path to the key p8 file",
50
+ optional: true,
51
+ conflicting_options: [:key_content],
52
+ verify_block: proc do |value|
53
+ UI.user_error!("Couldn't find key p8 file at path '#{value}'") unless File.exist?(value)
54
+ end),
55
+ FastlaneCore::ConfigItem.new(key: :key_content,
56
+ env_name: "APP_STORE_CONNECT_API_KEY_KEY",
57
+ description: "The content of the key p8 file",
58
+ optional: true,
59
+ conflicting_options: [:filepath]),
60
+ FastlaneCore::ConfigItem.new(key: :duration,
61
+ env_name: "APP_STORE_CONNECT_API_KEY_DURATION",
62
+ description: "The token session duration",
63
+ optional: true,
64
+ type: Integer),
65
+ FastlaneCore::ConfigItem.new(key: :in_house,
66
+ env_name: "APP_STORE_CONNECT_API_KEY_IN_HOUSE",
67
+ description: "Is App Store or Enterprise (in house) team? App Store Connect API cannot not determine this on its own (yet)",
68
+ optional: true,
69
+ type: Boolean)
70
+ ]
71
+ end
72
+
73
+ def self.output
74
+ [
75
+ ['APP_STORE_CONNECT_API_KEY', 'The App Store Connect API key information used for authorization requests. This hash can be passed directly into the :api_key options on other tools or into Spaceship::ConnectAPI::Token.create method']
76
+ ]
77
+ end
78
+
79
+ def self.author
80
+ ["joshdholtz"]
81
+ end
82
+
83
+ def self.is_supported?(platform)
84
+ true
85
+ end
86
+
87
+ def self.details
88
+ [
89
+ "Load the App Store Connect API token to use in other fastlane tools and actions"
90
+ ].join("\n")
91
+ end
92
+
93
+ def self.example_code
94
+ [
95
+ 'app_store_connect_api_key(
96
+ key_id: "D83848D23",
97
+ issuer_id: "227b0bbf-ada8-458c-9d62-3d8022b7d07f",
98
+ key_filepath: "D83848D23.p8"
99
+ )',
100
+ 'app_store_connect_api_key(
101
+ key_id: "D83848D23",
102
+ issuer_id: "227b0bbf-ada8-458c-9d62-3d8022b7d07f",
103
+ key_filepath: "D83848D23.p8",
104
+ duration: 200,
105
+ in_house: true
106
+ )',
107
+ 'app_store_connect_api_key(
108
+ key_id: "D83848D23",
109
+ issuer_id: "227b0bbf-ada8-458c-9d62-3d8022b7d07f",
110
+ key_content: "-----BEGIN EC PRIVATE KEY-----\nfewfawefawfe\n-----END EC PRIVATE KEY-----"
111
+ )'
112
+ ]
113
+ end
114
+
115
+ def self.category
116
+ :app_store_connect
117
+ end
118
+ end
119
+ end
120
+ end