fastlane 2.190.0 → 2.191.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +88 -88
  3. data/deliver/lib/deliver/app_screenshot.rb +2 -1
  4. data/deliver/lib/deliver/app_screenshot_iterator.rb +2 -2
  5. data/deliver/lib/deliver/loader.rb +1 -1
  6. data/deliver/lib/deliver/options.rb +6 -0
  7. data/deliver/lib/deliver/runner.rb +9 -1
  8. data/deliver/lib/deliver/screenshot_comparable.rb +62 -0
  9. data/deliver/lib/deliver/sync_screenshots.rb +200 -0
  10. data/fastlane/lib/fastlane/actions/zip.rb +3 -2
  11. data/fastlane/lib/fastlane/features.rb +3 -0
  12. data/fastlane/lib/fastlane/version.rb +1 -1
  13. data/fastlane/swift/Deliverfile.swift +1 -1
  14. data/fastlane/swift/DeliverfileProtocol.swift +5 -1
  15. data/fastlane/swift/Fastlane.swift +13 -1
  16. data/fastlane/swift/Gymfile.swift +1 -1
  17. data/fastlane/swift/GymfileProtocol.swift +1 -1
  18. data/fastlane/swift/Matchfile.swift +1 -1
  19. data/fastlane/swift/MatchfileProtocol.swift +1 -1
  20. data/fastlane/swift/Precheckfile.swift +1 -1
  21. data/fastlane/swift/PrecheckfileProtocol.swift +1 -1
  22. data/fastlane/swift/Scanfile.swift +1 -1
  23. data/fastlane/swift/ScanfileProtocol.swift +1 -1
  24. data/fastlane/swift/Screengrabfile.swift +1 -1
  25. data/fastlane/swift/ScreengrabfileProtocol.swift +1 -1
  26. data/fastlane/swift/Snapshotfile.swift +1 -1
  27. data/fastlane/swift/SnapshotfileProtocol.swift +1 -1
  28. data/fastlane/swift/formatting/Brewfile.lock.json +2 -2
  29. data/produce/lib/produce/commands_generator.rb +28 -0
  30. data/produce/lib/produce/service.rb +15 -0
  31. data/spaceship/lib/spaceship/connect_api.rb +1 -0
  32. data/spaceship/lib/spaceship/connect_api/models/app.rb +7 -0
  33. data/spaceship/lib/spaceship/connect_api/models/capabilities.rb +27 -0
  34. data/spaceship/lib/spaceship/connect_api/provisioning/provisioning.rb +5 -0
  35. data/spaceship/lib/spaceship/connect_api/users/users.rb +34 -1
  36. data/supply/lib/supply/uploader.rb +1 -1
  37. metadata +24 -21
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8705b34d3c2ba652057cd97cf7482e1490cfdfdd26650ee1cb001b3e2d55553c
4
- data.tar.gz: 4fc0b307f940f973ea7d9a70fe88ef567a97fcb9398ae9dfac52d9c4041c58c9
3
+ metadata.gz: 191c5f2f91fe567ba6f0c35848e69c0b921480ef1f2076bd531b774b6773bada
4
+ data.tar.gz: 6cf068574ea7ea72ac092791021fc78c2d1486d9e6f03772cc9564420e98a989
5
5
  SHA512:
6
- metadata.gz: 8421768a3ca7030a72a000a3df67a864b7461ee3e221c1e8e91d061aa2d83eac121ed9781e75fc81e2f1e11d822309318d2f0f5aea52cfdd7ce5180c745e359f
7
- data.tar.gz: bf7ca0c1eb8f5daed3a4ea7a44ffad3898535e173805f342a3590440a248cc9d5bbb4b6a9edea98af191d20cb740316ba9b5b044a75b1c0109ac1923a5df6326
6
+ metadata.gz: aacb03423627e0dc324915167515f0a4c8faebd6349c9a989320332baa668782f7dfbd0c20899e0905fcffc3d3ad3f8e254e05f75b30facff9744636b37c3c00
7
+ data.tar.gz: fa30c4044f3e3dffa7b53ed4a48d9e6fa2805649538077e2134f41eeada06e9bc66f4821124a5e59fb29542727aa8df9b278660ae522fbab134bee4427c6bc13
data/README.md CHANGED
@@ -35,11 +35,11 @@ If the above doesn't help, please [submit an issue](https://github.com/fastlane/
35
35
  <!-- This table is regenerated and resorted on each release -->
36
36
  <table id='team'>
37
37
  <tr>
38
- <td id='luka-mirosevic'>
39
- <a href='https://github.com/lmirosevic'>
40
- <img src='https://github.com/lmirosevic.png' width='140px;'>
38
+ <td id='josh-holtz'>
39
+ <a href='https://github.com/joshdholtz'>
40
+ <img src='https://github.com/joshdholtz.png' width='140px;'>
41
41
  </a>
42
- <h4 align='center'><a href='https://twitter.com/lmirosevic'>Luka Mirosevic</a></h4>
42
+ <h4 align='center'><a href='https://twitter.com/joshdholtz'>Josh Holtz</a></h4>
43
43
  </td>
44
44
  <td id='aaron-brager'>
45
45
  <a href='https://github.com/getaaron'>
@@ -47,11 +47,17 @@ If the above doesn't help, please [submit an issue](https://github.com/fastlane/
47
47
  </a>
48
48
  <h4 align='center'><a href='https://twitter.com/getaaron'>Aaron Brager</a></h4>
49
49
  </td>
50
- <td id='fumiya-nakamura'>
51
- <a href='https://github.com/nafu'>
52
- <img src='https://github.com/nafu.png' width='140px;'>
50
+ <td id='daniel-jankowski'>
51
+ <a href='https://github.com/mollyIV'>
52
+ <img src='https://github.com/mollyIV.png' width='140px;'>
53
53
  </a>
54
- <h4 align='center'><a href='https://twitter.com/nafu003'>Fumiya Nakamura</a></h4>
54
+ <h4 align='center'><a href='https://twitter.com/mollyIV'>Daniel Jankowski</a></h4>
55
+ </td>
56
+ <td id='luka-mirosevic'>
57
+ <a href='https://github.com/lmirosevic'>
58
+ <img src='https://github.com/lmirosevic.png' width='140px;'>
59
+ </a>
60
+ <h4 align='center'><a href='https://twitter.com/lmirosevic'>Luka Mirosevic</a></h4>
55
61
  </td>
56
62
  <td id='satoshi-namai'>
57
63
  <a href='https://github.com/ainame'>
@@ -59,51 +65,51 @@ If the above doesn't help, please [submit an issue](https://github.com/fastlane/
59
65
  </a>
60
66
  <h4 align='center'><a href='https://twitter.com/ainame'>Satoshi Namai</a></h4>
61
67
  </td>
62
- <td id='manish-rathi'>
63
- <a href='https://github.com/crazymanish'>
64
- <img src='https://github.com/crazymanish.png' width='140px;'>
65
- </a>
66
- <h4 align='center'><a href='https://twitter.com/iammanishrathi'>Manish Rathi</a></h4>
67
- </td>
68
68
  </tr>
69
69
  <tr>
70
- <td id='andrew-mcburney'>
71
- <a href='https://github.com/armcburney'>
72
- <img src='https://github.com/armcburney.png' width='140px;'>
70
+ <td id='max-ott'>
71
+ <a href='https://github.com/max-ott'>
72
+ <img src='https://github.com/max-ott.png' width='140px;'>
73
73
  </a>
74
- <h4 align='center'><a href='https://twitter.com/armcburney'>Andrew McBurney</a></h4>
74
+ <h4 align='center'><a href='https://twitter.com/ott_max'>Max Ott</a></h4>
75
75
  </td>
76
- <td id='kohki-miki'>
77
- <a href='https://github.com/giginet'>
78
- <img src='https://github.com/giginet.png' width='140px;'>
76
+ <td id='maksym-grebenets'>
77
+ <a href='https://github.com/mgrebenets'>
78
+ <img src='https://github.com/mgrebenets.png' width='140px;'>
79
79
  </a>
80
- <h4 align='center'><a href='https://twitter.com/giginet'>Kohki Miki</a></h4>
80
+ <h4 align='center'><a href='https://twitter.com/mgrebenets'>Maksym Grebenets</a></h4>
81
81
  </td>
82
- <td id='olivier-halligon'>
83
- <a href='https://github.com/AliSoftware'>
84
- <img src='https://github.com/AliSoftware.png' width='140px;'>
82
+ <td id='felix-krause'>
83
+ <a href='https://github.com/KrauseFx'>
84
+ <img src='https://github.com/KrauseFx.png' width='140px;'>
85
85
  </a>
86
- <h4 align='center'><a href='https://twitter.com/aligatr'>Olivier Halligon</a></h4>
86
+ <h4 align='center'><a href='https://twitter.com/KrauseFx'>Felix Krause</a></h4>
87
87
  </td>
88
- <td id='danielle-tomlinson'>
89
- <a href='https://github.com/endocrimes'>
90
- <img src='https://github.com/endocrimes.png' width='140px;'>
88
+ <td id='kohki-miki'>
89
+ <a href='https://github.com/giginet'>
90
+ <img src='https://github.com/giginet.png' width='140px;'>
91
91
  </a>
92
- <h4 align='center'><a href='https://twitter.com/endocrimes'>Danielle Tomlinson</a></h4>
92
+ <h4 align='center'><a href='https://twitter.com/giginet'>Kohki Miki</a></h4>
93
93
  </td>
94
- <td id='jorge-revuelta-h'>
95
- <a href='https://github.com/minuscorp'>
96
- <img src='https://github.com/minuscorp.png' width='140px;'>
94
+ <td id='helmut-januschka'>
95
+ <a href='https://github.com/hjanuschka'>
96
+ <img src='https://github.com/hjanuschka.png' width='140px;'>
97
97
  </a>
98
- <h4 align='center'><a href='https://twitter.com/minuscorp'>Jorge Revuelta H</a></h4>
98
+ <h4 align='center'><a href='https://twitter.com/hjanuschka'>Helmut Januschka</a></h4>
99
99
  </td>
100
100
  </tr>
101
101
  <tr>
102
- <td id='manu-wallner'>
103
- <a href='https://github.com/milch'>
104
- <img src='https://github.com/milch.png' width='140px;'>
102
+ <td id='jimmy-dee'>
103
+ <a href='https://github.com/jdee'>
104
+ <img src='https://github.com/jdee.png' width='140px;'>
105
105
  </a>
106
- <h4 align='center'><a href='https://twitter.com/acrooow'>Manu Wallner</a></h4>
106
+ <h4 align='center'>Jimmy Dee</h4>
107
+ </td>
108
+ <td id='roger-oba'>
109
+ <a href='https://github.com/rogerluan'>
110
+ <img src='https://github.com/rogerluan.png' width='140px;'>
111
+ </a>
112
+ <h4 align='center'><a href='https://twitter.com/rogerluan_'>Roger Oba</a></h4>
107
113
  </td>
108
114
  <td id='matthew-ellis'>
109
115
  <a href='https://github.com/matthewellis'>
@@ -111,31 +117,31 @@ If the above doesn't help, please [submit an issue](https://github.com/fastlane/
111
117
  </a>
112
118
  <h4 align='center'><a href='https://twitter.com/mellis1995'>Matthew Ellis</a></h4>
113
119
  </td>
114
- <td id='felix-krause'>
115
- <a href='https://github.com/KrauseFx'>
116
- <img src='https://github.com/KrauseFx.png' width='140px;'>
117
- </a>
118
- <h4 align='center'><a href='https://twitter.com/KrauseFx'>Felix Krause</a></h4>
119
- </td>
120
- <td id='maksym-grebenets'>
121
- <a href='https://github.com/mgrebenets'>
122
- <img src='https://github.com/mgrebenets.png' width='140px;'>
120
+ <td id='jorge-revuelta-h'>
121
+ <a href='https://github.com/minuscorp'>
122
+ <img src='https://github.com/minuscorp.png' width='140px;'>
123
123
  </a>
124
- <h4 align='center'><a href='https://twitter.com/mgrebenets'>Maksym Grebenets</a></h4>
124
+ <h4 align='center'><a href='https://twitter.com/minuscorp'>Jorge Revuelta H</a></h4>
125
125
  </td>
126
- <td id='helmut-januschka'>
127
- <a href='https://github.com/hjanuschka'>
128
- <img src='https://github.com/hjanuschka.png' width='140px;'>
126
+ <td id='jan-piotrowski'>
127
+ <a href='https://github.com/janpio'>
128
+ <img src='https://github.com/janpio.png' width='140px;'>
129
129
  </a>
130
- <h4 align='center'><a href='https://twitter.com/hjanuschka'>Helmut Januschka</a></h4>
130
+ <h4 align='center'><a href='https://twitter.com/Sujan'>Jan Piotrowski</a></h4>
131
131
  </td>
132
132
  </tr>
133
133
  <tr>
134
- <td id='jimmy-dee'>
135
- <a href='https://github.com/jdee'>
136
- <img src='https://github.com/jdee.png' width='140px;'>
134
+ <td id='manish-rathi'>
135
+ <a href='https://github.com/crazymanish'>
136
+ <img src='https://github.com/crazymanish.png' width='140px;'>
137
137
  </a>
138
- <h4 align='center'>Jimmy Dee</h4>
138
+ <h4 align='center'><a href='https://twitter.com/iammanishrathi'>Manish Rathi</a></h4>
139
+ </td>
140
+ <td id='danielle-tomlinson'>
141
+ <a href='https://github.com/endocrimes'>
142
+ <img src='https://github.com/endocrimes.png' width='140px;'>
143
+ </a>
144
+ <h4 align='center'><a href='https://twitter.com/endocrimes'>Danielle Tomlinson</a></h4>
139
145
  </td>
140
146
  <td id='joshua-liebowitz'>
141
147
  <a href='https://github.com/taquitos'>
@@ -143,55 +149,49 @@ If the above doesn't help, please [submit an issue](https://github.com/fastlane/
143
149
  </a>
144
150
  <h4 align='center'><a href='https://twitter.com/taquitos'>Joshua Liebowitz</a></h4>
145
151
  </td>
152
+ <td id='fumiya-nakamura'>
153
+ <a href='https://github.com/nafu'>
154
+ <img src='https://github.com/nafu.png' width='140px;'>
155
+ </a>
156
+ <h4 align='center'><a href='https://twitter.com/nafu003'>Fumiya Nakamura</a></h4>
157
+ </td>
146
158
  <td id='jérôme-lacoste'>
147
159
  <a href='https://github.com/lacostej'>
148
160
  <img src='https://github.com/lacostej.png' width='140px;'>
149
161
  </a>
150
162
  <h4 align='center'><a href='https://twitter.com/lacostej'>Jérôme Lacoste</a></h4>
151
163
  </td>
152
- <td id='stefan-natchev'>
153
- <a href='https://github.com/snatchev'>
154
- <img src='https://github.com/snatchev.png' width='140px;'>
155
- </a>
156
- <h4 align='center'><a href='https://twitter.com/snatchev'>Stefan Natchev</a></h4>
157
- </td>
158
- <td id='iulian-onofrei'>
159
- <a href='https://github.com/revolter'>
160
- <img src='https://github.com/revolter.png' width='140px;'>
161
- </a>
162
- <h4 align='center'><a href='https://twitter.com/Revolt666'>Iulian Onofrei</a></h4>
163
- </td>
164
164
  </tr>
165
165
  <tr>
166
- <td id='roger-oba'>
167
- <a href='https://github.com/rogerluan'>
168
- <img src='https://github.com/rogerluan.png' width='140px;'>
166
+ <td id='manu-wallner'>
167
+ <a href='https://github.com/milch'>
168
+ <img src='https://github.com/milch.png' width='140px;'>
169
169
  </a>
170
- <h4 align='center'><a href='https://twitter.com/rogerluan_'>Roger Oba</a></h4>
170
+ <h4 align='center'><a href='https://twitter.com/acrooow'>Manu Wallner</a></h4>
171
171
  </td>
172
- <td id='daniel-jankowski'>
173
- <a href='https://github.com/mollyIV'>
174
- <img src='https://github.com/mollyIV.png' width='140px;'>
172
+ <td id='andrew-mcburney'>
173
+ <a href='https://github.com/armcburney'>
174
+ <img src='https://github.com/armcburney.png' width='140px;'>
175
175
  </a>
176
- <h4 align='center'><a href='https://twitter.com/mollyIV'>Daniel Jankowski</a></h4>
176
+ <h4 align='center'><a href='https://twitter.com/armcburney'>Andrew McBurney</a></h4>
177
177
  </td>
178
- <td id='josh-holtz'>
179
- <a href='https://github.com/joshdholtz'>
180
- <img src='https://github.com/joshdholtz.png' width='140px;'>
178
+ <td id='olivier-halligon'>
179
+ <a href='https://github.com/AliSoftware'>
180
+ <img src='https://github.com/AliSoftware.png' width='140px;'>
181
181
  </a>
182
- <h4 align='center'><a href='https://twitter.com/joshdholtz'>Josh Holtz</a></h4>
182
+ <h4 align='center'><a href='https://twitter.com/aligatr'>Olivier Halligon</a></h4>
183
183
  </td>
184
- <td id='max-ott'>
185
- <a href='https://github.com/max-ott'>
186
- <img src='https://github.com/max-ott.png' width='140px;'>
184
+ <td id='iulian-onofrei'>
185
+ <a href='https://github.com/revolter'>
186
+ <img src='https://github.com/revolter.png' width='140px;'>
187
187
  </a>
188
- <h4 align='center'><a href='https://twitter.com/ott_max'>Max Ott</a></h4>
188
+ <h4 align='center'><a href='https://twitter.com/Revolt666'>Iulian Onofrei</a></h4>
189
189
  </td>
190
- <td id='jan-piotrowski'>
191
- <a href='https://github.com/janpio'>
192
- <img src='https://github.com/janpio.png' width='140px;'>
190
+ <td id='stefan-natchev'>
191
+ <a href='https://github.com/snatchev'>
192
+ <img src='https://github.com/snatchev.png' width='140px;'>
193
193
  </a>
194
- <h4 align='center'><a href='https://twitter.com/Sujan'>Jan Piotrowski</a></h4>
194
+ <h4 align='center'><a href='https://twitter.com/snatchev'>Stefan Natchev</a></h4>
195
195
  </td>
196
196
  </tr>
197
197
  </table>
@@ -324,7 +324,8 @@ module Deliver
324
324
  is_3rd_gen = [
325
325
  "iPad Pro (12.9-inch) (3rd generation)", # default simulator name has this
326
326
  "iPad Pro (12.9-inch) (4th generation)", # default simulator name has this
327
- "ipadPro129" # downloaded screenshots name has this
327
+ "ipadPro129", # downloaded screenshots name has this,
328
+ "3GEN" # downloaded screenshots name from App Store Connect API has this
328
329
  ].any? { |key| filename.include?(key) }
329
330
  if is_3rd_gen
330
331
  if screen_size == ScreenSize::IOS_IPAD_PRO
@@ -85,8 +85,8 @@ module Deliver
85
85
  app_screenshot_set ||= localization.create_app_screenshot_set(attributes: { screenshotDisplayType: display_type })
86
86
 
87
87
  # iterate over screenshots per display size with index
88
- screenshots.each do |screenshot|
89
- yield(localization, app_screenshot_set, screenshot)
88
+ screenshots.each.with_index do |screenshot, index|
89
+ yield(localization, app_screenshot_set, screenshot, index)
90
90
  end
91
91
  end
92
92
  end
@@ -82,7 +82,7 @@ module Deliver
82
82
  #
83
83
  # @param root [String] A directory path
84
84
  # @param ignore_validation [String] Set false not to raise the error when finding invalid folder name
85
- # @return [Array<AppScreenshot>] The list of AppScreenshot that exist under given `root` directory
85
+ # @return [Array<Deliver::AppScreenshot>] The list of AppScreenshot that exist under given `root` directory
86
86
  def self.load_app_screenshots(root, ignore_validation)
87
87
  screenshots = language_folders(root, ignore_validation, true).flat_map do |language_folder|
88
88
  paths = if language_folder.framed_file_paths.count > 0
@@ -162,6 +162,12 @@ module Deliver
162
162
  description: "Clear all previously uploaded screenshots before uploading the new ones",
163
163
  type: Boolean,
164
164
  default_value: false),
165
+ FastlaneCore::ConfigItem.new(key: :sync_screenshots,
166
+ env_name: "DELIVER_SYNC_SCREENSHOTS",
167
+ description: "Sync screenshots with local ones. This is currently beta option" \
168
+ "so set true to 'FASTLANE_ENABLE_BETA_DELIVER_SYNC_SCREENSHOTS' environment variable as well",
169
+ type: Boolean,
170
+ default_value: false),
165
171
  FastlaneCore::ConfigItem.new(key: :submit_for_review,
166
172
  env_name: "DELIVER_SUBMIT_FOR_REVIEW",
167
173
  description: "Submit the new version for Review after uploading everything",
@@ -10,6 +10,7 @@ require_relative 'submit_for_review'
10
10
  require_relative 'upload_price_tier'
11
11
  require_relative 'upload_metadata'
12
12
  require_relative 'upload_screenshots'
13
+ require_relative 'sync_screenshots'
13
14
  require_relative 'detect_values'
14
15
 
15
16
  module Deliver
@@ -143,7 +144,14 @@ module Deliver
143
144
 
144
145
  # Commit
145
146
  upload_metadata.upload(options)
146
- upload_screenshots.upload(options, screenshots)
147
+
148
+ if options[:sync_screenshots]
149
+ sync_screenshots = SyncScreenshots.new(app: Deliver.cache[:app], platform: Spaceship::ConnectAPI::Platform.map(options[:platform]))
150
+ sync_screenshots.sync(screenshots)
151
+ else
152
+ upload_screenshots.upload(options, screenshots)
153
+ end
154
+
147
155
  UploadPriceTier.new.upload(options)
148
156
  end
149
157
 
@@ -0,0 +1,62 @@
1
+ require 'spaceship/connect_api/models/app_screenshot'
2
+ require 'spaceship/connect_api/models/app_screenshot_set'
3
+
4
+ require_relative 'app_screenshot'
5
+
6
+ module Deliver
7
+ # This clsas enables you to compare equality between different representations of the screenshots
8
+ # in the standard API `Array#-` that requires objects to implements `eql?` and `hash`.
9
+ class ScreenshotComparable
10
+ # A unique key value that is consist of locale, filename, and checksum.
11
+ attr_reader :key
12
+
13
+ # A hash object that contains the source data of this representation class
14
+ attr_reader :context
15
+
16
+ def self.create_from_local(screenshot:, app_screenshot_set:)
17
+ raise ArgumentError unless screenshot.kind_of?(Deliver::AppScreenshot)
18
+ raise ArgumentError unless app_screenshot_set.kind_of?(Spaceship::ConnectAPI::AppScreenshotSet)
19
+
20
+ new(
21
+ path: "#{screenshot.language}/#{File.basename(screenshot.path)}",
22
+ checksum: calculate_checksum(screenshot.path),
23
+ context: {
24
+ screenshot: screenshot,
25
+ app_screenshot_set: app_screenshot_set
26
+ }
27
+ )
28
+ end
29
+
30
+ def self.create_from_remote(app_screenshot:, locale:)
31
+ raise ArgumentError unless app_screenshot.kind_of?(Spaceship::ConnectAPI::AppScreenshot)
32
+ raise ArgumentError unless locale.kind_of?(String)
33
+
34
+ new(
35
+ path: "#{locale}/#{app_screenshot.file_name}",
36
+ checksum: app_screenshot.source_file_checksum,
37
+ context: {
38
+ app_screenshot: app_screenshot,
39
+ locale: locale
40
+ }
41
+ )
42
+ end
43
+
44
+ def self.calculate_checksum(path)
45
+ bytes = File.binread(path)
46
+ Digest::MD5.hexdigest(bytes)
47
+ end
48
+
49
+ def initialize(path:, checksum:, context:)
50
+ @key = "#{path}/#{checksum}"
51
+ @context = context
52
+ end
53
+
54
+ def eql?(other)
55
+ key == other.key
56
+ end
57
+
58
+ def hash
59
+ key.hash
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,200 @@
1
+ require 'fastlane_core'
2
+ require 'digest/md5'
3
+ require 'naturally'
4
+
5
+ require_relative 'app_screenshot'
6
+ require_relative 'app_screenshot_iterator'
7
+ require_relative 'loader'
8
+ require_relative 'screenshot_comparable'
9
+
10
+ module Deliver
11
+ class SyncScreenshots
12
+ DeleteScreenshotJob = Struct.new(:app_screenshot, :locale)
13
+ UploadScreenshotJob = Struct.new(:app_screenshot_set, :path)
14
+
15
+ class UploadResult
16
+ attr_reader :asset_delivery_state_counts, :failing_screenshots
17
+
18
+ def initialize(asset_delivery_state_counts:, failing_screenshots:)
19
+ @asset_delivery_state_counts = asset_delivery_state_counts
20
+ @failing_screenshots = failing_screenshots
21
+ end
22
+
23
+ def processing?
24
+ @asset_delivery_state_counts.fetch('UPLOAD_COMPLETE', 0) > 0
25
+ end
26
+
27
+ def screenshot_count
28
+ @asset_delivery_state_counts.fetch('COMPLETE', 0)
29
+ end
30
+ end
31
+
32
+ def initialize(app:, platform:)
33
+ @app = app
34
+ @platform = platform
35
+ end
36
+
37
+ def sync_from_path(screenshots_path)
38
+ # load local screenshots
39
+ screenshots = Deliver::Loader.load_app_screenshots(screenshots_path, true)
40
+ sync(screenshots)
41
+ end
42
+
43
+ def sync(screenshots)
44
+ UI.important('This is currently a beta feature in fastlane. This may cause some errors on your environment.')
45
+
46
+ unless FastlaneCore::Feature.enabled?('FASTLANE_ENABLE_BETA_DELIVER_SYNC_SCREENSHOTS')
47
+ UI.user_error!('Please set a value to "FASTLANE_ENABLE_BETA_DELIVER_SYNC_SCREENSHOTS" environment variable ' \
48
+ 'if you acknowleage the risk and try this out.')
49
+ end
50
+
51
+ UI.important("Will begin uploading snapshots for '#{version.version_string}' on App Store Connect")
52
+
53
+ # enable localizations that will be used
54
+ screenshots_per_language = screenshots.group_by(&:language)
55
+ enable_localizations(screenshots_per_language.keys)
56
+
57
+ # create iterator
58
+ localizations = fetch_localizations
59
+ iterator = Deliver::AppScreenshotIterator.new(localizations)
60
+
61
+ # sync local screenshots with remote settings by deleting and uploading
62
+ UI.message("Starting with the upload of screenshots...")
63
+ replace_screenshots(iterator, screenshots)
64
+
65
+ # ensure screenshots within screenshot sets are sorted in right order
66
+ sort_screenshots(iterator)
67
+
68
+ UI.important('Screenshots are synced successfully!')
69
+ end
70
+
71
+ def enable_localizations(locales)
72
+ localizations = fetch_localizations
73
+ locales_to_enable = locales - localizations.map(&:locale)
74
+ Helper.show_loading_indicator("Activating localizations for #{locales_to_enable.join(', ')}...")
75
+ locales_to_enable.each do |locale|
76
+ version.create_app_store_version_localization(attributes: { locale: locale })
77
+ end
78
+ Helper.hide_loading_indicator
79
+ end
80
+
81
+ def replace_screenshots(iterator, screenshots, retries = 3)
82
+ # delete and upload screenshots to get App Store Connect in sync
83
+ do_replace_screenshots(iterator, screenshots, create_delete_worker, create_upload_worker)
84
+
85
+ # wait for screenshots to be processed on App Store Connect end and
86
+ # ensure the number of uploaded screenshots matches the one in local
87
+ result = wait_for_complete(iterator)
88
+ return if !result.processing? && result.screenshot_count == screenshots.count
89
+
90
+ if retries.zero?
91
+ UI.crash!("Retried uploading screenshots #{retries} but there are still failures of processing screenshots." \
92
+ "Check App Store Connect console to work out which screenshots processed unsuccessfully.")
93
+ end
94
+
95
+ # retry with deleting failing screenshots
96
+ result.failing_screenshots.each(&:delete!)
97
+ replace_screenshots(iterator, screenshots, retries - 1)
98
+ end
99
+
100
+ # This is a testable method that focuses on figuring out what to update
101
+ def do_replace_screenshots(iterator, screenshots, delete_worker, upload_worker)
102
+ remote_screenshots = iterator.each_app_screenshot.map do |localization, app_screenshot_set, app_screenshot|
103
+ ScreenshotComparable.create_from_remote(app_screenshot: app_screenshot, locale: localization.locale)
104
+ end
105
+
106
+ local_screenshots = iterator.each_local_screenshot(screenshots.group_by(&:language)).map do |localization, app_screenshot_set, screenshot, index|
107
+ if index >= 10
108
+ UI.user_error!("Found #{localization.locale} has more than 10 screenshots for #{app_screenshot_set.screenshot_display_type}. "\
109
+ "Make sure containts only necessary screenshots.")
110
+ end
111
+ ScreenshotComparable.create_from_local(screenshot: screenshot, app_screenshot_set: app_screenshot_set)
112
+ end
113
+
114
+ # Thanks to `Array#-` API and `ScreenshotComparable`, working out diffs between local screenshot directory and App Store Connect
115
+ # is as easy as you can see below. The former one finds what is missing in local and the latter one is visa versa.
116
+ screenshots_to_delete = remote_screenshots - local_screenshots
117
+ screenshots_to_upload = local_screenshots - remote_screenshots
118
+
119
+ delete_jobs = screenshots_to_delete.map { |x| DeleteScreenshotJob.new(x.context[:app_screenshot], x.context[:locale]) }
120
+ delete_worker.batch_enqueue(delete_jobs)
121
+ delete_worker.start
122
+
123
+ upload_jobs = screenshots_to_upload.map { |x| UploadScreenshotJob.new(x.context[:app_screenshot_set], x.context[:screenshot].path) }
124
+ upload_worker.batch_enqueue(upload_jobs)
125
+ upload_worker.start
126
+ end
127
+
128
+ def wait_for_complete(iterator)
129
+ retry_count = 0
130
+ Helper.show_loading_indicator("Waiting for all the screenshots processed...")
131
+ loop do
132
+ failing_screenshots = []
133
+ state_counts = iterator.each_app_screenshot.map { |_, _, app_screenshot| app_screenshot }.each_with_object({}) do |app_screenshot, hash|
134
+ state = app_screenshot.asset_delivery_state['state']
135
+ hash[state] ||= 0
136
+ hash[state] += 1
137
+ failing_screenshots << app_screenshot if app_screenshot.error?
138
+ end
139
+
140
+ result = UploadResult.new(asset_delivery_state_counts: state_counts, failing_screenshots: failing_screenshots)
141
+ return result unless result.processing?
142
+
143
+ # sleep with exponential backoff
144
+ interval = 5 + (2**retry_count)
145
+ UI.message("There are still incomplete screenshots. Will check the states again in #{interval} secs - #{state_counts}")
146
+ sleep(interval)
147
+ retry_count += 1
148
+ end
149
+ ensure
150
+ Helper.hide_loading_indicator
151
+ end
152
+
153
+ def sort_screenshots(iterator)
154
+ Helper.show_loading_indicator("Sorting screenshots uploaded...")
155
+ sort_worker = create_sort_worker
156
+ sort_worker.batch_enqueue(iterator.each_app_screenshot_set.to_a.map { |_, set| set })
157
+ sort_worker.start
158
+ Helper.hide_loading_indicator
159
+ end
160
+
161
+ private
162
+
163
+ def version
164
+ @version ||= @app.get_edit_app_store_version(platform: @platform)
165
+ end
166
+
167
+ def fetch_localizations
168
+ version.get_app_store_version_localizations
169
+ end
170
+
171
+ def create_upload_worker
172
+ FastlaneCore::QueueWorker.new do |job|
173
+ UI.verbose("Uploading '#{job.path}'...")
174
+ start_time = Time.now
175
+ job.app_screenshot_set.upload_screenshot(path: job.path, wait_for_processing: false)
176
+ UI.message("Uploaded '#{job.path}'... (#{Time.now - start_time} secs)")
177
+ end
178
+ end
179
+
180
+ def create_delete_worker
181
+ FastlaneCore::QueueWorker.new do |job|
182
+ target = "id=#{job.app_screenshot.id} #{job.locale} #{job.app_screenshot.file_name}"
183
+ UI.verbose("Deleting '#{target}'")
184
+ start_time = Time.now
185
+ job.app_screenshot.delete!
186
+ UI.message("Deleted '#{target}' - (#{Time.now - start_time} secs)")
187
+ end
188
+ end
189
+
190
+ def create_sort_worker
191
+ FastlaneCore::QueueWorker.new do |app_screenshot_set|
192
+ original_ids = app_screenshot_set.app_screenshots.map(&:id)
193
+ sorted_ids = Naturally.sort(app_screenshot_set.app_screenshots, by: :file_name).map(&:id)
194
+ if original_ids != sorted_ids
195
+ app_screenshot_set.reorder_screenshots(app_screenshot_ids: sorted_ids)
196
+ end
197
+ end
198
+ end
199
+ end
200
+ end
@@ -52,8 +52,8 @@ module Fastlane
52
52
  # The zip command is executed from the paths **parent** directory, as a result we use just the basename, which is the file or folder within
53
53
  basename = File.basename(path)
54
54
 
55
- command << output_path
56
- command << basename
55
+ command << output_path.shellescape
56
+ command << basename.shellescape
57
57
 
58
58
  unless include.empty?
59
59
  command << "-i"
@@ -68,6 +68,7 @@ module Fastlane
68
68
  command
69
69
  end
70
70
  end
71
+
71
72
  def self.run(params)
72
73
  Runner.new(params).run
73
74
  end
@@ -2,3 +2,6 @@
2
2
 
3
3
  # FastlaneCore::Feature.register(env_var: 'YOUR_FEATURE_SWITCH_ENV_VAR',
4
4
  # description: 'Describe what this feature switch controls')
5
+
6
+ FastlaneCore::Feature.register(env_var: 'FASTLANE_ENABLE_BETA_DELIVER_SYNC_SCREENSHOTS',
7
+ description: 'Use a newly implemented screenshots synchronization logic')
@@ -1,5 +1,5 @@
1
1
  module Fastlane
2
- VERSION = '2.190.0'.freeze
2
+ VERSION = '2.191.0'.freeze
3
3
  DESCRIPTION = "The easiest way to automate beta deployments and releases for your iOS and Android apps".freeze
4
4
  MINIMUM_XCODE_RELEASE = "7.0".freeze
5
5
  RUBOCOP_REQUIREMENT = '1.12.1'.freeze
@@ -17,4 +17,4 @@ public class Deliverfile: DeliverfileProtocol {
17
17
  // during the `init` process, and you won't see this message
18
18
  }
19
19
 
20
- // Generated with fastlane 2.190.0
20
+ // Generated with fastlane 2.191.0
@@ -59,6 +59,9 @@ public protocol DeliverfileProtocol: class {
59
59
  /// Clear all previously uploaded screenshots before uploading the new ones
60
60
  var overwriteScreenshots: Bool { get }
61
61
 
62
+ /// Sync screenshots with local ones. This is currently beta optionso set true to 'FASTLANE_ENABLE_BETA_DELIVER_SYNC_SCREENSHOTS' environment variable as well
63
+ var syncScreenshots: Bool { get }
64
+
62
65
  /// Submit the new version for Review after uploading everything
63
66
  var submitForReview: Bool { get }
64
67
 
@@ -209,6 +212,7 @@ public extension DeliverfileProtocol {
209
212
  var skipAppVersionUpdate: Bool { return false }
210
213
  var force: Bool { return false }
211
214
  var overwriteScreenshots: Bool { return false }
215
+ var syncScreenshots: Bool { return false }
212
216
  var submitForReview: Bool { return false }
213
217
  var rejectIfPossible: Bool { return false }
214
218
  var automaticRelease: Bool? { return nil }
@@ -256,4 +260,4 @@ public extension DeliverfileProtocol {
256
260
 
257
261
  // Please don't remove the lines below
258
262
  // They are used to detect outdated files
259
- // FastlaneRunnerAPIVersion [0.9.78]
263
+ // FastlaneRunnerAPIVersion [0.9.79]
@@ -661,6 +661,7 @@ public func appledoc(input: [String],
661
661
  - skipAppVersionUpdate: Don’t create or update the app version that is being prepared for submission
662
662
  - force: Skip verification of HTML preview file
663
663
  - overwriteScreenshots: Clear all previously uploaded screenshots before uploading the new ones
664
+ - syncScreenshots: Sync screenshots with local ones. This is currently beta optionso set true to 'FASTLANE_ENABLE_BETA_DELIVER_SYNC_SCREENSHOTS' environment variable as well
664
665
  - submitForReview: Submit the new version for Review after uploading everything
665
666
  - rejectIfPossible: Rejects the previously submitted build if it's in a state where it's possible
666
667
  - automaticRelease: Should the app be automatically released once it's approved? (Can not be used together with `auto_release_date`)
@@ -731,6 +732,7 @@ public func appstore(apiKeyPath: OptionalConfigValue<String?> = .fastlaneDefault
731
732
  skipAppVersionUpdate: OptionalConfigValue<Bool> = .fastlaneDefault(false),
732
733
  force: OptionalConfigValue<Bool> = .fastlaneDefault(false),
733
734
  overwriteScreenshots: OptionalConfigValue<Bool> = .fastlaneDefault(false),
735
+ syncScreenshots: OptionalConfigValue<Bool> = .fastlaneDefault(false),
734
736
  submitForReview: OptionalConfigValue<Bool> = .fastlaneDefault(false),
735
737
  rejectIfPossible: OptionalConfigValue<Bool> = .fastlaneDefault(false),
736
738
  automaticRelease: OptionalConfigValue<Bool?> = .fastlaneDefault(nil),
@@ -794,6 +796,7 @@ public func appstore(apiKeyPath: OptionalConfigValue<String?> = .fastlaneDefault
794
796
  let skipAppVersionUpdateArg = skipAppVersionUpdate.asRubyArgument(name: "skip_app_version_update", type: nil)
795
797
  let forceArg = force.asRubyArgument(name: "force", type: nil)
796
798
  let overwriteScreenshotsArg = overwriteScreenshots.asRubyArgument(name: "overwrite_screenshots", type: nil)
799
+ let syncScreenshotsArg = syncScreenshots.asRubyArgument(name: "sync_screenshots", type: nil)
797
800
  let submitForReviewArg = submitForReview.asRubyArgument(name: "submit_for_review", type: nil)
798
801
  let rejectIfPossibleArg = rejectIfPossible.asRubyArgument(name: "reject_if_possible", type: nil)
799
802
  let automaticReleaseArg = automaticRelease.asRubyArgument(name: "automatic_release", type: nil)
@@ -856,6 +859,7 @@ public func appstore(apiKeyPath: OptionalConfigValue<String?> = .fastlaneDefault
856
859
  skipAppVersionUpdateArg,
857
860
  forceArg,
858
861
  overwriteScreenshotsArg,
862
+ syncScreenshotsArg,
859
863
  submitForReviewArg,
860
864
  rejectIfPossibleArg,
861
865
  automaticReleaseArg,
@@ -3668,6 +3672,7 @@ public func deleteKeychain(name: OptionalConfigValue<String?> = .fastlaneDefault
3668
3672
  - skipAppVersionUpdate: Don’t create or update the app version that is being prepared for submission
3669
3673
  - force: Skip verification of HTML preview file
3670
3674
  - overwriteScreenshots: Clear all previously uploaded screenshots before uploading the new ones
3675
+ - syncScreenshots: Sync screenshots with local ones. This is currently beta optionso set true to 'FASTLANE_ENABLE_BETA_DELIVER_SYNC_SCREENSHOTS' environment variable as well
3671
3676
  - submitForReview: Submit the new version for Review after uploading everything
3672
3677
  - rejectIfPossible: Rejects the previously submitted build if it's in a state where it's possible
3673
3678
  - automaticRelease: Should the app be automatically released once it's approved? (Can not be used together with `auto_release_date`)
@@ -3738,6 +3743,7 @@ public func deliver(apiKeyPath: OptionalConfigValue<String?> = .fastlaneDefault(
3738
3743
  skipAppVersionUpdate: OptionalConfigValue<Bool> = .fastlaneDefault(deliverfile.skipAppVersionUpdate),
3739
3744
  force: OptionalConfigValue<Bool> = .fastlaneDefault(deliverfile.force),
3740
3745
  overwriteScreenshots: OptionalConfigValue<Bool> = .fastlaneDefault(deliverfile.overwriteScreenshots),
3746
+ syncScreenshots: OptionalConfigValue<Bool> = .fastlaneDefault(deliverfile.syncScreenshots),
3741
3747
  submitForReview: OptionalConfigValue<Bool> = .fastlaneDefault(deliverfile.submitForReview),
3742
3748
  rejectIfPossible: OptionalConfigValue<Bool> = .fastlaneDefault(deliverfile.rejectIfPossible),
3743
3749
  automaticRelease: OptionalConfigValue<Bool?> = .fastlaneDefault(deliverfile.automaticRelease),
@@ -3801,6 +3807,7 @@ public func deliver(apiKeyPath: OptionalConfigValue<String?> = .fastlaneDefault(
3801
3807
  let skipAppVersionUpdateArg = skipAppVersionUpdate.asRubyArgument(name: "skip_app_version_update", type: nil)
3802
3808
  let forceArg = force.asRubyArgument(name: "force", type: nil)
3803
3809
  let overwriteScreenshotsArg = overwriteScreenshots.asRubyArgument(name: "overwrite_screenshots", type: nil)
3810
+ let syncScreenshotsArg = syncScreenshots.asRubyArgument(name: "sync_screenshots", type: nil)
3804
3811
  let submitForReviewArg = submitForReview.asRubyArgument(name: "submit_for_review", type: nil)
3805
3812
  let rejectIfPossibleArg = rejectIfPossible.asRubyArgument(name: "reject_if_possible", type: nil)
3806
3813
  let automaticReleaseArg = automaticRelease.asRubyArgument(name: "automatic_release", type: nil)
@@ -3863,6 +3870,7 @@ public func deliver(apiKeyPath: OptionalConfigValue<String?> = .fastlaneDefault(
3863
3870
  skipAppVersionUpdateArg,
3864
3871
  forceArg,
3865
3872
  overwriteScreenshotsArg,
3873
+ syncScreenshotsArg,
3866
3874
  submitForReviewArg,
3867
3875
  rejectIfPossibleArg,
3868
3876
  automaticReleaseArg,
@@ -12004,6 +12012,7 @@ public func uploadSymbolsToSentry(apiHost: String = "https://app.getsentry.com/a
12004
12012
  - skipAppVersionUpdate: Don’t create or update the app version that is being prepared for submission
12005
12013
  - force: Skip verification of HTML preview file
12006
12014
  - overwriteScreenshots: Clear all previously uploaded screenshots before uploading the new ones
12015
+ - syncScreenshots: Sync screenshots with local ones. This is currently beta optionso set true to 'FASTLANE_ENABLE_BETA_DELIVER_SYNC_SCREENSHOTS' environment variable as well
12007
12016
  - submitForReview: Submit the new version for Review after uploading everything
12008
12017
  - rejectIfPossible: Rejects the previously submitted build if it's in a state where it's possible
12009
12018
  - automaticRelease: Should the app be automatically released once it's approved? (Can not be used together with `auto_release_date`)
@@ -12074,6 +12083,7 @@ public func uploadToAppStore(apiKeyPath: OptionalConfigValue<String?> = .fastlan
12074
12083
  skipAppVersionUpdate: OptionalConfigValue<Bool> = .fastlaneDefault(false),
12075
12084
  force: OptionalConfigValue<Bool> = .fastlaneDefault(false),
12076
12085
  overwriteScreenshots: OptionalConfigValue<Bool> = .fastlaneDefault(false),
12086
+ syncScreenshots: OptionalConfigValue<Bool> = .fastlaneDefault(false),
12077
12087
  submitForReview: OptionalConfigValue<Bool> = .fastlaneDefault(false),
12078
12088
  rejectIfPossible: OptionalConfigValue<Bool> = .fastlaneDefault(false),
12079
12089
  automaticRelease: OptionalConfigValue<Bool?> = .fastlaneDefault(nil),
@@ -12137,6 +12147,7 @@ public func uploadToAppStore(apiKeyPath: OptionalConfigValue<String?> = .fastlan
12137
12147
  let skipAppVersionUpdateArg = skipAppVersionUpdate.asRubyArgument(name: "skip_app_version_update", type: nil)
12138
12148
  let forceArg = force.asRubyArgument(name: "force", type: nil)
12139
12149
  let overwriteScreenshotsArg = overwriteScreenshots.asRubyArgument(name: "overwrite_screenshots", type: nil)
12150
+ let syncScreenshotsArg = syncScreenshots.asRubyArgument(name: "sync_screenshots", type: nil)
12140
12151
  let submitForReviewArg = submitForReview.asRubyArgument(name: "submit_for_review", type: nil)
12141
12152
  let rejectIfPossibleArg = rejectIfPossible.asRubyArgument(name: "reject_if_possible", type: nil)
12142
12153
  let automaticReleaseArg = automaticRelease.asRubyArgument(name: "automatic_release", type: nil)
@@ -12199,6 +12210,7 @@ public func uploadToAppStore(apiKeyPath: OptionalConfigValue<String?> = .fastlan
12199
12210
  skipAppVersionUpdateArg,
12200
12211
  forceArg,
12201
12212
  overwriteScreenshotsArg,
12213
+ syncScreenshotsArg,
12202
12214
  submitForReviewArg,
12203
12215
  rejectIfPossibleArg,
12204
12216
  automaticReleaseArg,
@@ -13201,4 +13213,4 @@ public let snapshotfile = Snapshotfile()
13201
13213
 
13202
13214
  // Please don't remove the lines below
13203
13215
  // They are used to detect outdated files
13204
- // FastlaneRunnerAPIVersion [0.9.131]
13216
+ // FastlaneRunnerAPIVersion [0.9.132]
@@ -17,4 +17,4 @@ public class Gymfile: GymfileProtocol {
17
17
  // during the `init` process, and you won't see this message
18
18
  }
19
19
 
20
- // Generated with fastlane 2.190.0
20
+ // Generated with fastlane 2.191.0
@@ -196,4 +196,4 @@ public extension GymfileProtocol {
196
196
 
197
197
  // Please don't remove the lines below
198
198
  // They are used to detect outdated files
199
- // FastlaneRunnerAPIVersion [0.9.81]
199
+ // FastlaneRunnerAPIVersion [0.9.82]
@@ -17,4 +17,4 @@ public class Matchfile: MatchfileProtocol {
17
17
  // during the `init` process, and you won't see this message
18
18
  }
19
19
 
20
- // Generated with fastlane 2.190.0
20
+ // Generated with fastlane 2.191.0
@@ -184,4 +184,4 @@ public extension MatchfileProtocol {
184
184
 
185
185
  // Please don't remove the lines below
186
186
  // They are used to detect outdated files
187
- // FastlaneRunnerAPIVersion [0.9.75]
187
+ // FastlaneRunnerAPIVersion [0.9.76]
@@ -17,4 +17,4 @@ public class Precheckfile: PrecheckfileProtocol {
17
17
  // during the `init` process, and you won't see this message
18
18
  }
19
19
 
20
- // Generated with fastlane 2.190.0
20
+ // Generated with fastlane 2.191.0
@@ -52,4 +52,4 @@ public extension PrecheckfileProtocol {
52
52
 
53
53
  // Please don't remove the lines below
54
54
  // They are used to detect outdated files
55
- // FastlaneRunnerAPIVersion [0.9.74]
55
+ // FastlaneRunnerAPIVersion [0.9.75]
@@ -17,4 +17,4 @@ public class Scanfile: ScanfileProtocol {
17
17
  // during the `init` process, and you won't see this message
18
18
  }
19
19
 
20
- // Generated with fastlane 2.190.0
20
+ // Generated with fastlane 2.191.0
@@ -296,4 +296,4 @@ public extension ScanfileProtocol {
296
296
 
297
297
  // Please don't remove the lines below
298
298
  // They are used to detect outdated files
299
- // FastlaneRunnerAPIVersion [0.9.86]
299
+ // FastlaneRunnerAPIVersion [0.9.87]
@@ -17,4 +17,4 @@ public class Screengrabfile: ScreengrabfileProtocol {
17
17
  // during the `init` process, and you won't see this message
18
18
  }
19
19
 
20
- // Generated with fastlane 2.190.0
20
+ // Generated with fastlane 2.191.0
@@ -96,4 +96,4 @@ public extension ScreengrabfileProtocol {
96
96
 
97
97
  // Please don't remove the lines below
98
98
  // They are used to detect outdated files
99
- // FastlaneRunnerAPIVersion [0.9.76]
99
+ // FastlaneRunnerAPIVersion [0.9.77]
@@ -17,4 +17,4 @@ public class Snapshotfile: SnapshotfileProtocol {
17
17
  // during the `init` process, and you won't see this message
18
18
  }
19
19
 
20
- // Generated with fastlane 2.190.0
20
+ // Generated with fastlane 2.191.0
@@ -200,4 +200,4 @@ public extension SnapshotfileProtocol {
200
200
 
201
201
  // Please don't remove the lines below
202
202
  // They are used to detect outdated files
203
- // FastlaneRunnerAPIVersion [0.9.70]
203
+ // FastlaneRunnerAPIVersion [0.9.71]
@@ -51,9 +51,9 @@
51
51
  "macOS": "11.0"
52
52
  },
53
53
  "monterey": {
54
- "HOMEBREW_VERSION": "3.2.5-35-g4b6b3c2",
54
+ "HOMEBREW_VERSION": "3.2.6-34-g6bb3699",
55
55
  "HOMEBREW_PREFIX": "/usr/local",
56
- "Homebrew/homebrew-core": "8482b78bceae40d19a8fc427b9935ce8f177b9b6",
56
+ "Homebrew/homebrew-core": "b7523de28df0f0f819ff2c49c84611eec19f5455",
57
57
  "CLT": "13.0.0.0.1.1626155413",
58
58
  "Xcode": "13.0",
59
59
  "macOS": "12.0"
@@ -3,6 +3,7 @@ require 'commander'
3
3
  require 'fastlane/version'
4
4
  require 'fastlane_core/ui/help_formatter'
5
5
  require 'fastlane_core/configuration/config_item'
6
+ require 'fastlane_core/print_table'
6
7
  require_relative 'module'
7
8
  require_relative 'manager'
8
9
  require_relative 'options'
@@ -196,6 +197,33 @@ module Produce
196
197
  end
197
198
  end
198
199
 
200
+ command :available_services do |c|
201
+ c.syntax = 'fastlane produce available_services -a APP_IDENTIFIER'
202
+ c.description = 'Displays a list of allowed Application Services for a specific app.'
203
+ c.example('Check Available Services', 'fastlane produce available_services -a com.example.app')
204
+
205
+ FastlaneCore::CommanderGenerator.new.generate(Produce::Options.available_options, command: c)
206
+
207
+ c.action do |args, options|
208
+ # Filter the options so that we can still build the configuration
209
+ allowed_keys = Produce::Options.available_options.collect(&:key)
210
+ Produce.config = FastlaneCore::Configuration.create(Produce::Options.available_options, options.__hash__.select { |key, value| allowed_keys.include?(key) })
211
+
212
+ require 'produce/service'
213
+ require 'terminal-table'
214
+
215
+ services = Produce::Service.available_services(options, args)
216
+ rows = services.map { |capabilities| [capabilities.name, capabilities.id, capabilities.description] }
217
+ table = Terminal::Table.new(
218
+ title: "Available Services",
219
+ headings: ['Name', 'ID', 'Description'],
220
+ rows: FastlaneCore::PrintTable.transform_output(rows),
221
+ style: { all_separators: true }
222
+ )
223
+ puts(table)
224
+ end
225
+ end
226
+
199
227
  command :group do |c|
200
228
  c.syntax = 'fastlane produce group'
201
229
  c.description = 'Ensure that a specific App Group exists'
@@ -16,6 +16,10 @@ module Produce
16
16
  self.new.disable(options, args)
17
17
  end
18
18
 
19
+ def self.available_services(options, args)
20
+ self.new.available_services(options, args)
21
+ end
22
+
19
23
  def enable(options, _args)
20
24
  unless bundle_id
21
25
  UI.message("[DevCenter] App '#{Produce.config[:app_identifier]}' does not exist")
@@ -40,6 +44,17 @@ module Produce
40
44
  UI.success("Done! Disabled #{disabled} services.")
41
45
  end
42
46
 
47
+ def available_services(options, _args)
48
+ unless bundle_id
49
+ UI.message("[DevCenter] App '#{Produce.config[:app_identifier]}' does not exist")
50
+ return
51
+ end
52
+
53
+ UI.success("[DevCenter] App found '#{bundle_id.name}'")
54
+ UI.message("Fetching available services")
55
+ return Spaceship::ConnectAPI::Capabilities.all
56
+ end
57
+
43
58
  def valid_services_for(options)
44
59
  allowed_keys = [:access_wifi, :app_attest, :app_group, :apple_pay, :associated_domains, :auto_fill_credential, :car_play_audio_app, :car_play_messaging_app,
45
60
  :car_play_navigation_app, :car_play_voip_calling_app, :class_kit, :icloud, :critical_alerts, :custom_network_protocol, :data_protection,
@@ -10,6 +10,7 @@ require 'spaceship/connect_api/tunes/tunes'
10
10
 
11
11
  require 'spaceship/connect_api/models/bundle_id_capability'
12
12
  require 'spaceship/connect_api/models/bundle_id'
13
+ require 'spaceship/connect_api/models/capabilities'
13
14
  require 'spaceship/connect_api/models/certificate'
14
15
  require 'spaceship/connect_api/models/device'
15
16
  require 'spaceship/connect_api/models/profile'
@@ -423,6 +423,13 @@ module Spaceship
423
423
  client.add_user_visible_apps(user_id: user_id, app_ids: [id])
424
424
  end
425
425
  end
426
+
427
+ def remove_users(client: nil, user_ids: nil)
428
+ client ||= Spaceship::ConnectAPI
429
+ user_ids.each do |user_id|
430
+ client.delete_user_visible_apps(user_id: user_id, app_ids: [id])
431
+ end
432
+ end
426
433
  end
427
434
  end
428
435
  end
@@ -0,0 +1,27 @@
1
+ require_relative '../model'
2
+
3
+ module Spaceship
4
+ class ConnectAPI
5
+ class Capabilities
6
+ include Spaceship::ConnectAPI::Model
7
+
8
+ attr_accessor :name
9
+ attr_accessor :description
10
+
11
+ attr_mapping({
12
+ "name" => "name",
13
+ "description" => "description",
14
+ })
15
+
16
+ def self.type
17
+ return "capabilities"
18
+ end
19
+
20
+ def self.all(client: nil)
21
+ client ||= Spaceship::ConnectAPI
22
+ resp = client.get_available_bundle_id_capabilities(bundle_id_id: id).all_pages
23
+ return resp.flat_map(&:to_models)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -54,6 +54,11 @@ module Spaceship
54
54
  provisioning_request_client.get("bundleIds/#{bundle_id_id}/bundleIdCapabilities", params)
55
55
  end
56
56
 
57
+ def get_available_bundle_id_capabilities(bundle_id_id:)
58
+ params = provisioning_request_client.build_params(filter: { bundleId: bundle_id_id })
59
+ provisioning_request_client.get("capabilities", params)
60
+ end
61
+
57
62
  def post_bundle_id_capability(bundle_id_id:, capability_type:, settings: [])
58
63
  body = {
59
64
  data: {
@@ -28,8 +28,13 @@ module Spaceship
28
28
  users_request_client.delete("users/#{user_id}")
29
29
  end
30
30
 
31
- # Change app permissions for user
31
+ # Add app permissions for user
32
+ # @deprecated Use {#post_user_visible_apps} instead.
32
33
  def add_user_visible_apps(user_id: nil, app_ids: nil)
34
+ post_user_visible_apps(user_id: user_id, app_ids: app_ids)
35
+ end
36
+
37
+ def post_user_visible_apps(user_id: nil, app_ids: nil)
33
38
  body = {
34
39
  data: app_ids.map do |app_id|
35
40
  {
@@ -42,6 +47,34 @@ module Spaceship
42
47
  users_request_client.post("users/#{user_id}/relationships/visibleApps", body)
43
48
  end
44
49
 
50
+ # Replace app permissions for user
51
+ def patch_user_visible_apps(user_id: nil, app_ids: nil)
52
+ body = {
53
+ data: app_ids.map do |app_id|
54
+ {
55
+ type: "apps",
56
+ id: app_id
57
+ }
58
+ end
59
+ }
60
+
61
+ users_request_client.patch("users/#{user_id}/relationships/visibleApps", body)
62
+ end
63
+
64
+ # Remove app permissions for user
65
+ def delete_user_visible_apps(user_id: nil, app_ids: nil)
66
+ body = {
67
+ data: app_ids.map do |app_id|
68
+ {
69
+ type: "apps",
70
+ id: app_id
71
+ }
72
+ end
73
+ }
74
+ params = nil
75
+ users_request_client.delete("users/#{user_id}/relationships/visibleApps", params, body)
76
+ end
77
+
45
78
  # Get app permissions for user
46
79
  def get_user_visible_apps(user_id: id, limit: nil)
47
80
  params = users_request_client.build_params(filter: {}, includes: nil, limit: limit, sort: nil)
@@ -302,7 +302,7 @@ module Supply
302
302
 
303
303
  def upload_mapping(apk_version_codes)
304
304
  mapping_paths = [Supply.config[:mapping]] unless (mapping_paths = Supply.config[:mapping_paths])
305
- mapping_paths.zip(apk_version_codes).each do |mapping_path, version_code|
305
+ mapping_paths.product(apk_version_codes).each do |mapping_path, version_code|
306
306
  if mapping_path
307
307
  UI.message("Preparing mapping at path '#{mapping_path}', version code #{version_code} for upload...")
308
308
  client.upload_mapping(mapping_path, version_code)
metadata CHANGED
@@ -1,38 +1,38 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fastlane
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.190.0
4
+ version: 2.191.0
5
5
  platform: ruby
6
6
  authors:
7
- - Helmut Januschka
7
+ - Andrew McBurney
8
+ - Fumiya Nakamura
9
+ - Manu Wallner
10
+ - Jimmy Dee
11
+ - Satoshi Namai
12
+ - Josh Holtz
13
+ - Felix Krause
8
14
  - Jérôme Lacoste
9
- - Max Ott
10
- - Daniel Jankowski
11
- - Stefan Natchev
12
- - Luka Mirosevic
13
- - Jorge Revuelta H
15
+ - Manish Rathi
14
16
  - Kohki Miki
15
- - Maksym Grebenets
16
- - Joshua Liebowitz
17
+ - Iulian Onofrei
17
18
  - Matthew Ellis
18
- - Roger Oba
19
- - Josh Holtz
20
- - Jimmy Dee
21
- - Satoshi Namai
19
+ - Aaron Brager
22
20
  - Danielle Tomlinson
23
- - Fumiya Nakamura
21
+ - Max Ott
22
+ - Roger Oba
24
23
  - Olivier Halligon
25
- - Manu Wallner
24
+ - Maksym Grebenets
26
25
  - Jan Piotrowski
27
- - Aaron Brager
28
- - Iulian Onofrei
29
- - Andrew McBurney
30
- - Felix Krause
31
- - Manish Rathi
26
+ - Jorge Revuelta H
27
+ - Luka Mirosevic
28
+ - Daniel Jankowski
29
+ - Stefan Natchev
30
+ - Helmut Januschka
31
+ - Joshua Liebowitz
32
32
  autorequire:
33
33
  bindir: bin
34
34
  cert_chain: []
35
- date: 2021-08-03 00:00:00.000000000 Z
35
+ date: 2021-08-06 00:00:00.000000000 Z
36
36
  dependencies:
37
37
  - !ruby/object:Gem::Dependency
38
38
  name: xcodeproj
@@ -971,8 +971,10 @@ files:
971
971
  - deliver/lib/deliver/module.rb
972
972
  - deliver/lib/deliver/options.rb
973
973
  - deliver/lib/deliver/runner.rb
974
+ - deliver/lib/deliver/screenshot_comparable.rb
974
975
  - deliver/lib/deliver/setup.rb
975
976
  - deliver/lib/deliver/submit_for_review.rb
977
+ - deliver/lib/deliver/sync_screenshots.rb
976
978
  - deliver/lib/deliver/upload_metadata.rb
977
979
  - deliver/lib/deliver/upload_price_tier.rb
978
980
  - deliver/lib/deliver/upload_screenshots.rb
@@ -1680,6 +1682,7 @@ files:
1680
1682
  - spaceship/lib/spaceship/connect_api/models/build_delivery.rb
1681
1683
  - spaceship/lib/spaceship/connect_api/models/bundle_id.rb
1682
1684
  - spaceship/lib/spaceship/connect_api/models/bundle_id_capability.rb
1685
+ - spaceship/lib/spaceship/connect_api/models/capabilities.rb
1683
1686
  - spaceship/lib/spaceship/connect_api/models/certificate.rb
1684
1687
  - spaceship/lib/spaceship/connect_api/models/custom_app_organization.rb
1685
1688
  - spaceship/lib/spaceship/connect_api/models/custom_app_user.rb