fastlane 2.150.0.rc4 → 2.150.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +76 -76
  3. data/deliver/lib/deliver/download_screenshots.rb +48 -26
  4. data/deliver/lib/deliver/runner.rb +4 -1
  5. data/deliver/lib/deliver/submit_for_review.rb +26 -7
  6. data/deliver/lib/deliver/upload_metadata.rb +33 -14
  7. data/deliver/lib/deliver/upload_screenshots.rb +24 -5
  8. data/fastlane/lib/fastlane/actions/.hockey.rb.swp +0 -0
  9. data/fastlane/lib/fastlane/actions/.slack.rb.swp +0 -0
  10. data/fastlane/lib/fastlane/actions/.update_project_provisioning.rb.swp +0 -0
  11. data/fastlane/lib/fastlane/actions/docs/upload_to_app_store.md.erb +4 -0
  12. data/fastlane/lib/fastlane/version.rb +1 -1
  13. data/fastlane/swift/Deliverfile.swift +1 -1
  14. data/fastlane/swift/DeliverfileProtocol.swift +6 -6
  15. data/fastlane/swift/Fastlane.swift +22 -19
  16. data/fastlane/swift/FastlaneSwiftRunner/FastlaneSwiftRunner.xcodeproj/project.xcworkspace/xcuserdata/josh.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  17. data/fastlane/swift/Gymfile.swift +1 -1
  18. data/fastlane/swift/Matchfile.swift +1 -1
  19. data/fastlane/swift/Precheckfile.swift +1 -1
  20. data/fastlane/swift/Scanfile.swift +1 -1
  21. data/fastlane/swift/Screengrabfile.swift +1 -1
  22. data/fastlane/swift/Snapshotfile.swift +1 -1
  23. data/fastlane/swift/SocketClient.swift +2 -1
  24. data/fastlane_core/lib/fastlane_core/build_watcher.rb +4 -4
  25. data/fastlane_core/lib/fastlane_core/ipa_file_analyser.rb +0 -1
  26. data/fastlane_core/lib/fastlane_core/provisioning_profile.rb +1 -1
  27. data/pilot/lib/pilot/.manager.rb.swp +0 -0
  28. data/produce/lib/produce/itunes_connect.rb +29 -2
  29. data/spaceship/lib/spaceship/connect_api.rb +1 -1
  30. data/spaceship/lib/spaceship/{.DS_Store → connect_api/.DS_Store} +0 -0
  31. data/spaceship/lib/spaceship/connect_api/client.rb +1 -1
  32. data/spaceship/lib/spaceship/connect_api/models/app.rb +38 -3
  33. data/spaceship/lib/spaceship/connect_api/models/app_info.rb +5 -1
  34. data/spaceship/lib/spaceship/connect_api/models/app_screenshot.rb +54 -1
  35. data/spaceship/lib/spaceship/connect_api/models/app_screenshot_set.rb +32 -2
  36. data/spaceship/lib/spaceship/connect_api/models/{app_review_attachment.rb → app_store_review_attachment.rb} +6 -6
  37. data/spaceship/lib/spaceship/connect_api/models/app_store_review_detail.rb +7 -8
  38. data/spaceship/lib/spaceship/connect_api/models/app_store_version.rb +21 -3
  39. data/spaceship/lib/spaceship/connect_api/models/user.rb +2 -1
  40. data/spaceship/lib/spaceship/connect_api/tunes/tunes.rb +38 -20
  41. data/spaceship/lib/spaceship/connect_api/users/users.rb +13 -0
  42. metadata +36 -31
@@ -40,7 +40,7 @@ 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
42
  require 'spaceship/connect_api/models/app_price_tier'
43
- require 'spaceship/connect_api/models/app_review_attachment'
43
+ require 'spaceship/connect_api/models/app_store_review_attachment'
44
44
  require 'spaceship/connect_api/models/app_store_review_detail'
45
45
  require 'spaceship/connect_api/models/app_store_version_submission'
46
46
  require 'spaceship/connect_api/models/app_screenshot_set'
@@ -133,7 +133,7 @@ module Spaceship
133
133
  tries -= 1
134
134
  status = response.status if response
135
135
 
136
- if [504].include?(status)
136
+ if [500, 504].include?(status)
137
137
  msg = "Timeout received! Retrying after 3 seconds (remaining: #{tries})..."
138
138
  raise msg
139
139
  end
@@ -54,14 +54,15 @@ module Spaceship
54
54
  end
55
55
  end
56
56
 
57
- def self.create(name: nil, version_string: nil, sku: nil, primary_locale: nil, bundle_id: nil, platforms: nil)
57
+ def self.create(name: nil, version_string: nil, sku: nil, primary_locale: nil, bundle_id: nil, platforms: nil, company_name: nil)
58
58
  Spaceship::ConnectAPI.post_app(
59
59
  name: name,
60
60
  version_string: version_string,
61
61
  sku: sku,
62
62
  primary_locale: primary_locale,
63
63
  bundle_id: bundle_id,
64
- platforms: platforms
64
+ platforms: platforms,
65
+ company_name: company_name
65
66
  )
66
67
  end
67
68
 
@@ -109,6 +110,26 @@ module Spaceship
109
110
  # App Store Versions
110
111
  #
111
112
 
113
+ def reject_version_if_possible!
114
+ platform ||= Spaceship::ConnectAPI::Platform::IOS
115
+ filter = {
116
+ appStoreState: [
117
+ Spaceship::ConnectAPI::AppStoreVersion::AppStoreState::PENDING_DEVELOPER_RELEASE,
118
+ Spaceship::ConnectAPI::AppStoreVersion::AppStoreState::IN_REVIEW,
119
+ Spaceship::ConnectAPI::AppStoreVersion::AppStoreState::WAITING_FOR_REVIEW
120
+ ].join(","),
121
+ platform: platform
122
+ }
123
+
124
+ # Get the latest version
125
+ version = get_app_store_versions(filter: filter, includes: "appStoreVersionSubmission")
126
+ .sort_by { |v| Gem::Version.new(v.version_string) }
127
+ .last
128
+
129
+ return false if version.nil?
130
+ return version.reject!
131
+ end
132
+
112
133
  # Will make sure the current edit_version matches the given version number
113
134
  # This will either create a new version or change the version number
114
135
  # from an existing version
@@ -153,7 +174,11 @@ module Spaceship
153
174
  ].join(","),
154
175
  platform: platform
155
176
  }
156
- return get_app_store_versions(filter: filter, includes: includes).first
177
+
178
+ # Get the latest version
179
+ return get_app_store_versions(filter: filter, includes: includes)
180
+ .sort_by { |v| Gem::Version.new(v.version_string) }
181
+ .last
157
182
  end
158
183
 
159
184
  def get_app_store_versions(filter: {}, includes: nil, limit: nil, sort: nil)
@@ -231,6 +256,16 @@ module Spaceship
231
256
  ).all_pages
232
257
  return resps.flat_map(&:to_models).first
233
258
  end
259
+
260
+ #
261
+ # Users
262
+ #
263
+
264
+ def add_users(user_ids: nil)
265
+ user_ids.each do |user_id|
266
+ Spaceship::ConnectAPI.add_user_visible_apps(user_id: user_id, app_ids: [id])
267
+ end
268
+ end
234
269
  end
235
270
  end
236
271
  end
@@ -11,6 +11,9 @@ module Spaceship
11
11
 
12
12
  module AppStoreState
13
13
  READY_FOR_SALE = "READY_FOR_SALE"
14
+ PROCESSING_FOR_APP_STORE = "PROCESSING_FOR_APP_STORE"
15
+ PENDING_DEVELOPER_RELEASE = "PENDING_DEVELOPER_RELEASE"
16
+ IN_REVIEW = "IN_REVIEW"
14
17
  WAITING_FOR_REVIEW = "WAITING_FOR_REVIEW"
15
18
  DEVELOPER_REJECTED = "DEVELOPER_REJECTED"
16
19
  REJECTED = "REJECTED"
@@ -60,7 +63,8 @@ module Spaceship
60
63
  end
61
64
 
62
65
  def get_app_info_localizations(filter: {}, includes: nil, limit: nil, sort: nil)
63
- return Spaceship::ConnectAPI.get_app_info_localizations(app_info_id: id, filter: filter, includes: includes, limit: limit, sort: sort)
66
+ resp = Spaceship::ConnectAPI.get_app_info_localizations(app_info_id: id, filter: filter, includes: includes, limit: limit, sort: sort)
67
+ return resp.to_models
64
68
  end
65
69
  end
66
70
  end
@@ -37,12 +37,42 @@ module Spaceship
37
37
  (asset_delivery_state || {})["state"] == "COMPLETE"
38
38
  end
39
39
 
40
+ def error?
41
+ (asset_delivery_state || {})["state"] == "FAILED"
42
+ end
43
+
44
+ def error_messages
45
+ errors = (asset_delivery_state || {})["errors"]
46
+ (errors || []).map do |error|
47
+ [error["code"], error["description"]].compact.join(" - ")
48
+ end
49
+ end
50
+
51
+ # This does not download the source image (exact image that was uploaded)
52
+ # This downloads a modified version.
53
+ # This image won't have the same checksums as source_file_checksum.
54
+ #
55
+ # There is an open radar for allowing downloading of source file.
56
+ # https://openradar.appspot.com/radar?id=4980344105205760
57
+ def image_asset_url(width: nil, height: nil, type: "png")
58
+ return nil if image_asset.nil?
59
+
60
+ template_url = image_asset["templateUrl"]
61
+ width ||= image_asset["width"]
62
+ height ||= image_asset["height"]
63
+
64
+ return template_url
65
+ .gsub("{w}", width.to_s)
66
+ .gsub("{h}", height.to_s)
67
+ .gsub("{f}", type)
68
+ end
69
+
40
70
  #
41
71
  # API
42
72
  #
43
73
  #
44
74
 
45
- def self.create(app_screenshot_set_id: nil, path: nil)
75
+ def self.create(app_screenshot_set_id: nil, path: nil, wait_for_processing: true)
46
76
  require 'faraday'
47
77
 
48
78
  filename = File.basename(path)
@@ -70,6 +100,9 @@ module Spaceship
70
100
  sourceFileChecksum: Digest::MD5.hexdigest(bytes)
71
101
  }
72
102
 
103
+ # Patch screenshot that file upload is complete
104
+ # Catch error if patch retries due to 504. Origal patch
105
+ # may go through by return response as 504.
73
106
  begin
74
107
  screenshot = Spaceship::ConnectAPI.patch_app_screenshot(
75
108
  app_screenshot_id: screenshot.id,
@@ -82,6 +115,26 @@ module Spaceship
82
115
  raise error unless screenshot.complete?
83
116
  end
84
117
 
118
+ # Wait for processing
119
+ if wait_for_processing
120
+ loop do
121
+ if screenshot.complete?
122
+ puts("Screenshot processing complete!") if Spaceship::Globals.verbose?
123
+ break
124
+ elsif screenshot.error?
125
+ messages = ["Error processing screenshot '#{screenshot.file_name}'"] + screenshot.error_messages
126
+ raise messages.join(". ")
127
+ end
128
+
129
+ # Poll every 2 seconds
130
+ sleep_time = 2
131
+ puts("Waiting #{sleep_time} seconds before checking status of processing...") if Spaceship::Globals.verbose?
132
+ sleep(sleep_time)
133
+
134
+ screenshot = Spaceship::ConnectAPI.get_app_screenshot(app_screenshot_id: screenshot.id).first
135
+ end
136
+ end
137
+
85
138
  return screenshot
86
139
  end
87
140
 
@@ -39,8 +39,24 @@ module Spaceship
39
39
  APP_WATCH_SERIES_3 = "APP_WATCH_SERIES_3"
40
40
  APP_WATCH_SERIES_4 = "APP_WATCH_SERIES_4"
41
41
 
42
+ APP_APPLE_TV = "APP_APPLE_TV"
43
+
42
44
  APP_DESKTOP = "APP_DESKTOP"
43
45
 
46
+ ALL_IMESSAGE = [
47
+ IMESSAGE_APP_IPHONE_40,
48
+ IMESSAGE_APP_IPHONE_47,
49
+ IMESSAGE_APP_IPHONE_55,
50
+ IMESSAGE_APP_IPHONE_58,
51
+ IMESSAGE_APP_IPHONE_65,
52
+
53
+ IMESSAGE_APP_IPAD_97,
54
+ IMESSAGE_APP_IPAD_105,
55
+ IMESSAGE_APP_IPAD_PRO_129,
56
+ IMESSAGE_APP_IPAD_PRO_3GEN_11,
57
+ IMESSAGE_APP_IPAD_PRO_3GEN_129
58
+ ]
59
+
44
60
  ALL = [
45
61
  APP_IPHONE_35,
46
62
  APP_IPHONE_40,
@@ -84,6 +100,14 @@ module Spaceship
84
100
  return "appScreenshotSets"
85
101
  end
86
102
 
103
+ def apple_tv?
104
+ DisplayType::APP_APPLE_TV == screenshot_display_type
105
+ end
106
+
107
+ def imessage?
108
+ DisplayType::ALL_IMESSAGE.include?(screenshot_display_type)
109
+ end
110
+
87
111
  #
88
112
  # API
89
113
  #
@@ -93,8 +117,14 @@ module Spaceship
93
117
  return resp.to_models
94
118
  end
95
119
 
96
- def upload_screenshot(path: nil)
97
- return Spaceship::ConnectAPI::AppScreenshot.create(app_screenshot_set_id: id, path: path)
120
+ def upload_screenshot(path: nil, wait_for_processing: true)
121
+ return Spaceship::ConnectAPI::AppScreenshot.create(app_screenshot_set_id: id, path: path, wait_for_processing: wait_for_processing)
122
+ end
123
+
124
+ def reorder_screenshots(app_screenshot_ids: nil)
125
+ Spaceship::ConnectAPI.patch_app_screenshot_set_screenshots(app_screenshot_set_id: id, app_screenshot_ids: app_screenshot_ids)
126
+
127
+ return Spaceship::ConnectAPI.get_app_screenshot_set(app_screenshot_set_id: id, includes: "appScreenshots").first
98
128
  end
99
129
  end
100
130
  end
@@ -4,7 +4,7 @@ require 'digest/md5'
4
4
 
5
5
  module Spaceship
6
6
  class ConnectAPI
7
- class AppReviewAttachment
7
+ class AppStoreReviewAttachment
8
8
  include Spaceship::ConnectAPI::Model
9
9
 
10
10
  attr_accessor :file_name
@@ -22,7 +22,7 @@ module Spaceship
22
22
  })
23
23
 
24
24
  def self.type
25
- return "appReviewAttachments"
25
+ return "appStoreReviewAttachments"
26
26
  end
27
27
 
28
28
  #
@@ -42,7 +42,7 @@ module Spaceship
42
42
  }
43
43
 
44
44
  # Create placeholder
45
- attachment = Spaceship::ConnectAPI.post_app_review_attachment(
45
+ attachment = Spaceship::ConnectAPI.post_app_store_review_attachment(
46
46
  app_store_review_detail_id: app_store_review_detail_id,
47
47
  attributes: post_attributes
48
48
  ).to_models.first
@@ -57,14 +57,14 @@ module Spaceship
57
57
  sourceFileChecksum: Digest::MD5.hexdigest(bytes)
58
58
  }
59
59
 
60
- Spaceship::ConnectAPI.patch_app_review_attachment(
61
- app_review_attachment_id: attachment.id,
60
+ Spaceship::ConnectAPI.patch_app_store_review_attachment(
61
+ app_store_review_attachment_id: attachment.id,
62
62
  attributes: patch_attributes
63
63
  ).to_models.first
64
64
  end
65
65
 
66
66
  def delete!(filter: {}, includes: nil, limit: nil, sort: nil)
67
- Spaceship::ConnectAPI.delete_app_review_attachment(app_review_attachment_id: id)
67
+ Spaceship::ConnectAPI.delete_app_store_review_attachment(app_store_review_attachment_id: id)
68
68
  end
69
69
  end
70
70
  end
@@ -1,5 +1,5 @@
1
1
  require_relative '../model'
2
- require_relative './app_review_attachment'
2
+ require_relative './app_store_review_attachment'
3
3
 
4
4
  module Spaceship
5
5
  class ConnectAPI
@@ -15,6 +15,8 @@ module Spaceship
15
15
  attr_accessor :demo_account_required
16
16
  attr_accessor :notes
17
17
 
18
+ attr_accessor :app_store_review_attachments
19
+
18
20
  attr_mapping({
19
21
  "contactFirstName" => "contact_first_name",
20
22
  "contactLastName" => "contact_last_name",
@@ -23,7 +25,9 @@ module Spaceship
23
25
  "demoAccountName" => "demo_account_name",
24
26
  "demoAccountPassword" => "demo_account_password",
25
27
  "demoAccountRequired" => "demo_account_required",
26
- "notes" => "notes"
28
+ "notes" => "notes",
29
+
30
+ "appStoreReviewAttachments" => "app_store_review_attachments"
27
31
  })
28
32
 
29
33
  def self.type
@@ -34,17 +38,12 @@ module Spaceship
34
38
  # API
35
39
  #
36
40
 
37
- def fetch_app_review_attachments
38
- resp = Spaceship::ConnectAPI.get_app_review_attachments(app_store_review_detail_id: id)
39
- return resp.to_models
40
- end
41
-
42
41
  def update(attributes: nil)
43
42
  return Spaceship::ConnectAPI.patch_app_store_review_detail(app_store_review_detail_id: id, attributes: attributes)
44
43
  end
45
44
 
46
45
  def upload_attachment(path: nil)
47
- return Spaceship::ConnectAPI::AppReviewAttachment.create(app_store_review_detail_id: id, path: path)
46
+ return Spaceship::ConnectAPI::AppStoreReviewAttachment.create(app_store_review_detail_id: id, path: path)
48
47
  end
49
48
  end
50
49
  end
@@ -19,8 +19,13 @@ module Spaceship
19
19
  attr_accessor :downloadable
20
20
  attr_accessor :created_date
21
21
 
22
+ attr_accessor :app_store_version_submission
23
+
22
24
  module AppStoreState
23
25
  READY_FOR_SALE = "READY_FOR_SALE"
26
+ PROCESSING_FOR_APP_STORE = "PROCESSING_FOR_APP_STORE"
27
+ PENDING_DEVELOPER_RELEASE = "PENDING_DEVELOPER_RELEASE"
28
+ IN_REVIEW = "IN_REVIEW"
24
29
  WAITING_FOR_REVIEW = "WAITING_FOR_REVIEW"
25
30
  DEVELOPER_REJECTED = "DEVELOPER_REJECTED"
26
31
  REJECTED = "REJECTED"
@@ -47,13 +52,26 @@ module Spaceship
47
52
  "usesIdfa" => "uses_idfa",
48
53
  "isWatchOnly" => "is_watch_only",
49
54
  "downloadable" => "downloadable",
50
- "createdDate" => "created_date"
55
+ "createdDate" => "created_date",
56
+
57
+ "appStoreVersionSubmission" => "app_store_version_submission"
51
58
  })
52
59
 
53
60
  def self.type
54
61
  return "appStoreVersions"
55
62
  end
56
63
 
64
+ def can_reject?
65
+ raise "No app_store_version_submission included" unless app_store_version_submission
66
+ return app_store_version_submission.can_reject
67
+ end
68
+
69
+ def reject!
70
+ return false unless can_reject?
71
+ app_store_version_submission.delete!
72
+ return true
73
+ end
74
+
57
75
  #
58
76
  # API
59
77
  #
@@ -104,8 +122,8 @@ module Spaceship
104
122
  return resp.to_models.first
105
123
  end
106
124
 
107
- def fetch_app_store_review_detail
108
- resp = Spaceship::ConnectAPI.get_app_store_review_detail(app_store_version_id: id)
125
+ def fetch_app_store_review_detail(includes: "appStoreReviewAttachments")
126
+ resp = Spaceship::ConnectAPI.get_app_store_review_detail(app_store_version_id: id, includes: includes)
109
127
  return resp.to_models.first
110
128
  end
111
129
 
@@ -39,7 +39,8 @@ module Spaceship
39
39
  #
40
40
 
41
41
  def self.all(filter: {}, includes: nil, limit: nil, sort: nil)
42
- return Spaceship::ConnectAPI.get_users(filter: filter, includes: includes)
42
+ resps = Spaceship::ConnectAPI.get_users(filter: filter, includes: includes).all_pages
43
+ return resps.flat_map(&:to_models)
43
44
  end
44
45
 
45
46
  def self.find(email: nil, includes: nil)
@@ -28,7 +28,7 @@ module Spaceship
28
28
  # app
29
29
  #
30
30
 
31
- def post_app(name: nil, version_string: nil, sku: nil, primary_locale: nil, bundle_id: nil, platforms: nil)
31
+ def post_app(name: nil, version_string: nil, sku: nil, primary_locale: nil, bundle_id: nil, platforms: nil, company_name: nil)
32
32
  included = []
33
33
  included << {
34
34
  type: "appInfos",
@@ -103,14 +103,17 @@ module Spaceship
103
103
  }
104
104
  }
105
105
 
106
+ app_attributes = {
107
+ sku: sku,
108
+ primaryLocale: primary_locale,
109
+ bundleId: bundle_id
110
+ }
111
+ app_attributes[:companyName] = company_name if company_name
112
+
106
113
  body = {
107
114
  data: {
108
115
  type: "apps",
109
- attributes: {
110
- sku: sku,
111
- primaryLocale: primary_locale,
112
- bundleId: bundle_id
113
- },
116
+ attributes: app_attributes,
114
117
  relationships: relationships
115
118
  },
116
119
  included: included
@@ -270,15 +273,10 @@ module Spaceship
270
273
  # appReviewAttachments
271
274
  #
272
275
 
273
- def get_app_review_attachments(app_store_review_detail_id: nil, filter: {}, includes: nil, limit: nil, sort: nil)
274
- params = Client.instance.build_params(filter: filter, includes: includes, limit: limit, sort: sort)
275
- Client.instance.get("appStoreReviewDetails/#{app_store_review_detail_id}/appReviewAttachments", params)
276
- end
277
-
278
- def post_app_review_attachment(app_store_review_detail_id: nil, attributes: {})
276
+ def post_app_store_review_attachment(app_store_review_detail_id: nil, attributes: {})
279
277
  body = {
280
278
  data: {
281
- type: "appReviewAttachments",
279
+ type: "appStoreReviewAttachments",
282
280
  attributes: attributes,
283
281
  relationships: {
284
282
  appStoreReviewDetail: {
@@ -291,24 +289,24 @@ module Spaceship
291
289
  }
292
290
  }
293
291
 
294
- Client.instance.post("appReviewAttachments", body)
292
+ Client.instance.post("appStoreReviewAttachments", body)
295
293
  end
296
294
 
297
- def patch_app_review_attachment(app_review_attachment_id: nil, attributes: {})
295
+ def patch_app_store_review_attachment(app_store_review_attachment_id: nil, attributes: {})
298
296
  body = {
299
297
  data: {
300
- type: "appReviewAttachments",
301
- id: app_review_attachment_id,
298
+ type: "appStoreReviewAttachments",
299
+ id: app_store_review_attachment_id,
302
300
  attributes: attributes
303
301
  }
304
302
  }
305
303
 
306
- Client.instance.patch("appReviewAttachments/#{app_review_attachment_id}", body)
304
+ Client.instance.patch("appStoreReviewAttachments/#{app_store_review_attachment_id}", body)
307
305
  end
308
306
 
309
- def delete_app_review_attachment(app_review_attachment_id: nil)
307
+ def delete_app_store_review_attachment(app_store_review_attachment_id: nil)
310
308
  params = Client.instance.build_params(filter: nil, includes: nil, limit: nil, sort: nil)
311
- Client.instance.delete("appReviewAttachments/#{app_review_attachment_id}", params)
309
+ Client.instance.delete("appStoreReviewAttachments/#{app_store_review_attachment_id}", params)
312
310
  end
313
311
 
314
312
  #
@@ -320,6 +318,11 @@ module Spaceship
320
318
  Client.instance.get("appScreenshotSets", params)
321
319
  end
322
320
 
321
+ def get_app_screenshot_set(app_screenshot_set_id: nil, filter: {}, includes: nil, limit: nil, sort: nil)
322
+ params = Client.instance.build_params(filter: filter, includes: includes, limit: limit, sort: sort)
323
+ Client.instance.get("appScreenshotSets/#{app_screenshot_set_id}", params)
324
+ end
325
+
323
326
  def post_app_screenshot_set(app_store_version_localization_id: nil, attributes: {})
324
327
  body = {
325
328
  data: {
@@ -339,6 +342,21 @@ module Spaceship
339
342
  Client.instance.post("appScreenshotSets", body)
340
343
  end
341
344
 
345
+ def patch_app_screenshot_set_screenshots(app_screenshot_set_id: nil, app_screenshot_ids: nil)
346
+ app_screenshot_ids ||= []
347
+
348
+ body = {
349
+ data: app_screenshot_ids.map do |app_screenshot_id|
350
+ {
351
+ type: "appScreenshots",
352
+ id: app_screenshot_id
353
+ }
354
+ end
355
+ }
356
+
357
+ Client.instance.patch("appScreenshotSets/#{app_screenshot_set_id}/relationships/appScreenshots", body)
358
+ end
359
+
342
360
  #
343
361
  # appScreenshots
344
362
  #