fastlane 2.190.0 → 2.191.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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