fastlane 2.190.0 → 2.193.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +90 -90
- data/deliver/lib/deliver/app_screenshot.rb +2 -1
- data/deliver/lib/deliver/app_screenshot_iterator.rb +2 -2
- data/deliver/lib/deliver/loader.rb +1 -1
- data/deliver/lib/deliver/options.rb +6 -0
- data/deliver/lib/deliver/runner.rb +9 -1
- data/deliver/lib/deliver/screenshot_comparable.rb +62 -0
- data/deliver/lib/deliver/sync_screenshots.rb +200 -0
- data/fastlane/lib/assets/completions/completion.bash +4 -1
- data/fastlane/lib/assets/completions/completion.zsh +6 -5
- data/fastlane/lib/fastlane/actions/create_xcframework.rb +97 -17
- data/fastlane/lib/fastlane/actions/get_provisioning_profile.rb +1 -1
- data/fastlane/lib/fastlane/actions/gradle.rb +1 -1
- data/fastlane/lib/fastlane/actions/notarize.rb +77 -1
- data/fastlane/lib/fastlane/actions/push_git_tags.rb +1 -1
- data/fastlane/lib/fastlane/actions/sync_code_signing.rb +1 -1
- data/fastlane/lib/fastlane/actions/upload_to_testflight.rb +3 -1
- data/fastlane/lib/fastlane/actions/zip.rb +4 -3
- data/fastlane/lib/fastlane/features.rb +3 -0
- data/fastlane/lib/fastlane/version.rb +1 -1
- data/fastlane/swift/Deliverfile.swift +1 -1
- data/fastlane/swift/DeliverfileProtocol.swift +5 -1
- data/fastlane/swift/Fastlane.swift +82 -20
- data/fastlane/swift/Gymfile.swift +1 -1
- data/fastlane/swift/GymfileProtocol.swift +1 -1
- data/fastlane/swift/Matchfile.swift +1 -1
- data/fastlane/swift/MatchfileProtocol.swift +2 -2
- data/fastlane/swift/Precheckfile.swift +1 -1
- data/fastlane/swift/PrecheckfileProtocol.swift +1 -1
- data/fastlane/swift/Scanfile.swift +1 -1
- data/fastlane/swift/ScanfileProtocol.swift +1 -1
- data/fastlane/swift/Screengrabfile.swift +1 -1
- data/fastlane/swift/ScreengrabfileProtocol.swift +1 -1
- data/fastlane/swift/Snapshotfile.swift +1 -1
- data/fastlane/swift/SnapshotfileProtocol.swift +1 -1
- data/fastlane/swift/formatting/Brewfile.lock.json +8 -8
- data/fastlane_core/lib/fastlane_core/build_watcher.rb +25 -6
- data/fastlane_core/lib/fastlane_core/pkg_file_analyser.rb +5 -0
- data/fastlane_core/lib/fastlane_core/ui/fastlane_runner.rb +41 -0
- data/match/lib/match/options.rb +1 -1
- data/match/lib/match/runner.rb +10 -9
- data/pilot/lib/pilot/build_manager.rb +14 -4
- data/pilot/lib/pilot/manager.rb +3 -1
- data/pilot/lib/pilot/options.rb +20 -1
- data/produce/lib/produce/commands_generator.rb +28 -0
- data/produce/lib/produce/service.rb +16 -1
- data/spaceship/lib/spaceship/connect_api/api_client.rb +15 -1
- data/spaceship/lib/spaceship/connect_api/models/app.rb +9 -1
- data/spaceship/lib/spaceship/connect_api/models/build.rb +4 -0
- data/spaceship/lib/spaceship/connect_api/models/build_beta_detail.rb +4 -0
- data/spaceship/lib/spaceship/connect_api/models/capabilities.rb +27 -0
- data/spaceship/lib/spaceship/connect_api/provisioning/provisioning.rb +5 -0
- data/spaceship/lib/spaceship/connect_api/testflight/.testflight.rb.swp +0 -0
- data/spaceship/lib/spaceship/connect_api/testflight/testflight.rb +7 -5
- data/spaceship/lib/spaceship/connect_api/token.rb +2 -0
- data/spaceship/lib/spaceship/connect_api/users/users.rb +34 -1
- data/spaceship/lib/spaceship/connect_api.rb +1 -0
- data/supply/lib/supply/client.rb +38 -5
- data/supply/lib/supply/options.rb +7 -0
- data/supply/lib/supply/uploader.rb +1 -1
- metadata +39 -21
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 16bbd4e5c7f94860e5d9cba08578ab2918bccfe31092380f43d859da4f8393e7
|
4
|
+
data.tar.gz: f527779d5963a3f3110c7b5b4bb6bda4c816a01e391b16919c7ef5c0c841b82d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 148193a7bc745d1c044cadad094875e5375c8f0ed611231695228bd1656aa3f5030018a88cf484d7c811bcd4161ac191f786f3cfcce759a9e59d70cc5a814e34
|
7
|
+
data.tar.gz: b041a08c28429bc1a32c7a08702ebca0e260ac051734541b4345cb441a3cfaeb33d08f75887346bc974380463c1c8eddae468024069ad505c5edaa2ec5fdfc1c
|
data/README.md
CHANGED
@@ -35,23 +35,43 @@ 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='iulian-onofrei'>
|
39
|
+
<a href='https://github.com/revolter'>
|
40
|
+
<img src='https://github.com/revolter.png' width='140px;'>
|
41
|
+
</a>
|
42
|
+
<h4 align='center'><a href='https://twitter.com/Revolt666'>Iulian Onofrei</a></h4>
|
43
|
+
</td>
|
44
|
+
<td id='andrew-mcburney'>
|
45
|
+
<a href='https://github.com/armcburney'>
|
46
|
+
<img src='https://github.com/armcburney.png' width='140px;'>
|
47
|
+
</a>
|
48
|
+
<h4 align='center'><a href='https://twitter.com/armcburney'>Andrew McBurney</a></h4>
|
49
|
+
</td>
|
50
|
+
<td id='maksym-grebenets'>
|
51
|
+
<a href='https://github.com/mgrebenets'>
|
52
|
+
<img src='https://github.com/mgrebenets.png' width='140px;'>
|
53
|
+
</a>
|
54
|
+
<h4 align='center'><a href='https://twitter.com/mgrebenets'>Maksym Grebenets</a></h4>
|
55
|
+
</td>
|
56
|
+
<td id='joshua-liebowitz'>
|
57
|
+
<a href='https://github.com/taquitos'>
|
58
|
+
<img src='https://github.com/taquitos.png' width='140px;'>
|
59
|
+
</a>
|
60
|
+
<h4 align='center'><a href='https://twitter.com/taquitos'>Joshua Liebowitz</a></h4>
|
61
|
+
</td>
|
38
62
|
<td id='luka-mirosevic'>
|
39
63
|
<a href='https://github.com/lmirosevic'>
|
40
64
|
<img src='https://github.com/lmirosevic.png' width='140px;'>
|
41
65
|
</a>
|
42
66
|
<h4 align='center'><a href='https://twitter.com/lmirosevic'>Luka Mirosevic</a></h4>
|
43
67
|
</td>
|
44
|
-
|
45
|
-
<
|
46
|
-
<
|
47
|
-
|
48
|
-
<
|
49
|
-
</td>
|
50
|
-
<td id='fumiya-nakamura'>
|
51
|
-
<a href='https://github.com/nafu'>
|
52
|
-
<img src='https://github.com/nafu.png' width='140px;'>
|
68
|
+
</tr>
|
69
|
+
<tr>
|
70
|
+
<td id='olivier-halligon'>
|
71
|
+
<a href='https://github.com/AliSoftware'>
|
72
|
+
<img src='https://github.com/AliSoftware.png' width='140px;'>
|
53
73
|
</a>
|
54
|
-
<h4 align='center'><a href='https://twitter.com/
|
74
|
+
<h4 align='center'><a href='https://twitter.com/aligatr'>Olivier Halligon</a></h4>
|
55
75
|
</td>
|
56
76
|
<td id='satoshi-namai'>
|
57
77
|
<a href='https://github.com/ainame'>
|
@@ -59,19 +79,11 @@ If the above doesn't help, please [submit an issue](https://github.com/fastlane/
|
|
59
79
|
</a>
|
60
80
|
<h4 align='center'><a href='https://twitter.com/ainame'>Satoshi Namai</a></h4>
|
61
81
|
</td>
|
62
|
-
<td id='
|
63
|
-
<a href='https://github.com/
|
64
|
-
<img src='https://github.com/
|
65
|
-
</a>
|
66
|
-
<h4 align='center'><a href='https://twitter.com/iammanishrathi'>Manish Rathi</a></h4>
|
67
|
-
</td>
|
68
|
-
</tr>
|
69
|
-
<tr>
|
70
|
-
<td id='andrew-mcburney'>
|
71
|
-
<a href='https://github.com/armcburney'>
|
72
|
-
<img src='https://github.com/armcburney.png' width='140px;'>
|
82
|
+
<td id='stefan-natchev'>
|
83
|
+
<a href='https://github.com/snatchev'>
|
84
|
+
<img src='https://github.com/snatchev.png' width='140px;'>
|
73
85
|
</a>
|
74
|
-
<h4 align='center'><a href='https://twitter.com/
|
86
|
+
<h4 align='center'><a href='https://twitter.com/snatchev'>Stefan Natchev</a></h4>
|
75
87
|
</td>
|
76
88
|
<td id='kohki-miki'>
|
77
89
|
<a href='https://github.com/giginet'>
|
@@ -79,31 +91,19 @@ If the above doesn't help, please [submit an issue](https://github.com/fastlane/
|
|
79
91
|
</a>
|
80
92
|
<h4 align='center'><a href='https://twitter.com/giginet'>Kohki Miki</a></h4>
|
81
93
|
</td>
|
82
|
-
<td id='
|
83
|
-
<a href='https://github.com/
|
84
|
-
<img src='https://github.com/
|
85
|
-
</a>
|
86
|
-
<h4 align='center'><a href='https://twitter.com/aligatr'>Olivier Halligon</a></h4>
|
87
|
-
</td>
|
88
|
-
<td id='danielle-tomlinson'>
|
89
|
-
<a href='https://github.com/endocrimes'>
|
90
|
-
<img src='https://github.com/endocrimes.png' width='140px;'>
|
91
|
-
</a>
|
92
|
-
<h4 align='center'><a href='https://twitter.com/endocrimes'>Danielle Tomlinson</a></h4>
|
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='daniel-jankowski'>
|
95
|
+
<a href='https://github.com/mollyIV'>
|
96
|
+
<img src='https://github.com/mollyIV.png' width='140px;'>
|
97
97
|
</a>
|
98
|
-
<h4 align='center'><a href='https://twitter.com/
|
98
|
+
<h4 align='center'><a href='https://twitter.com/mollyIV'>Daniel Jankowski</a></h4>
|
99
99
|
</td>
|
100
100
|
</tr>
|
101
101
|
<tr>
|
102
|
-
<td id='
|
103
|
-
<a href='https://github.com/
|
104
|
-
<img src='https://github.com/
|
102
|
+
<td id='max-ott'>
|
103
|
+
<a href='https://github.com/max-ott'>
|
104
|
+
<img src='https://github.com/max-ott.png' width='140px;'>
|
105
105
|
</a>
|
106
|
-
<h4 align='center'><a href='https://twitter.com/
|
106
|
+
<h4 align='center'><a href='https://twitter.com/ott_max'>Max Ott</a></h4>
|
107
107
|
</td>
|
108
108
|
<td id='matthew-ellis'>
|
109
109
|
<a href='https://github.com/matthewellis'>
|
@@ -111,37 +111,31 @@ If the above doesn't help, please [submit an issue](https://github.com/fastlane/
|
|
111
111
|
</a>
|
112
112
|
<h4 align='center'><a href='https://twitter.com/mellis1995'>Matthew Ellis</a></h4>
|
113
113
|
</td>
|
114
|
-
<td id='
|
115
|
-
<a href='https://github.com/
|
116
|
-
<img src='https://github.com/
|
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;'>
|
114
|
+
<td id='jorge-revuelta-h'>
|
115
|
+
<a href='https://github.com/minuscorp'>
|
116
|
+
<img src='https://github.com/minuscorp.png' width='140px;'>
|
123
117
|
</a>
|
124
|
-
<h4 align='center'><a href='https://twitter.com/
|
118
|
+
<h4 align='center'><a href='https://twitter.com/minuscorp'>Jorge Revuelta H</a></h4>
|
125
119
|
</td>
|
126
|
-
<td id='
|
127
|
-
<a href='https://github.com/
|
128
|
-
<img src='https://github.com/
|
120
|
+
<td id='fumiya-nakamura'>
|
121
|
+
<a href='https://github.com/nafu'>
|
122
|
+
<img src='https://github.com/nafu.png' width='140px;'>
|
129
123
|
</a>
|
130
|
-
<h4 align='center'><a href='https://twitter.com/
|
124
|
+
<h4 align='center'><a href='https://twitter.com/nafu003'>Fumiya Nakamura</a></h4>
|
131
125
|
</td>
|
132
|
-
</tr>
|
133
|
-
<tr>
|
134
126
|
<td id='jimmy-dee'>
|
135
127
|
<a href='https://github.com/jdee'>
|
136
128
|
<img src='https://github.com/jdee.png' width='140px;'>
|
137
129
|
</a>
|
138
130
|
<h4 align='center'>Jimmy Dee</h4>
|
139
131
|
</td>
|
140
|
-
|
141
|
-
<
|
142
|
-
<
|
132
|
+
</tr>
|
133
|
+
<tr>
|
134
|
+
<td id='helmut-januschka'>
|
135
|
+
<a href='https://github.com/hjanuschka'>
|
136
|
+
<img src='https://github.com/hjanuschka.png' width='140px;'>
|
143
137
|
</a>
|
144
|
-
<h4 align='center'><a href='https://twitter.com/
|
138
|
+
<h4 align='center'><a href='https://twitter.com/hjanuschka'>Helmut Januschka</a></h4>
|
145
139
|
</td>
|
146
140
|
<td id='jérôme-lacoste'>
|
147
141
|
<a href='https://github.com/lacostej'>
|
@@ -149,31 +143,11 @@ If the above doesn't help, please [submit an issue](https://github.com/fastlane/
|
|
149
143
|
</a>
|
150
144
|
<h4 align='center'><a href='https://twitter.com/lacostej'>Jérôme Lacoste</a></h4>
|
151
145
|
</td>
|
152
|
-
<td id='
|
153
|
-
<a href='https://github.com/
|
154
|
-
<img src='https://github.com/
|
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
|
-
</tr>
|
165
|
-
<tr>
|
166
|
-
<td id='roger-oba'>
|
167
|
-
<a href='https://github.com/rogerluan'>
|
168
|
-
<img src='https://github.com/rogerluan.png' width='140px;'>
|
169
|
-
</a>
|
170
|
-
<h4 align='center'><a href='https://twitter.com/rogerluan_'>Roger Oba</a></h4>
|
171
|
-
</td>
|
172
|
-
<td id='daniel-jankowski'>
|
173
|
-
<a href='https://github.com/mollyIV'>
|
174
|
-
<img src='https://github.com/mollyIV.png' width='140px;'>
|
146
|
+
<td id='danielle-tomlinson'>
|
147
|
+
<a href='https://github.com/endocrimes'>
|
148
|
+
<img src='https://github.com/endocrimes.png' width='140px;'>
|
175
149
|
</a>
|
176
|
-
<h4 align='center'><a href='https://twitter.com/
|
150
|
+
<h4 align='center'><a href='https://twitter.com/endocrimes'>Danielle Tomlinson</a></h4>
|
177
151
|
</td>
|
178
152
|
<td id='josh-holtz'>
|
179
153
|
<a href='https://github.com/joshdholtz'>
|
@@ -181,11 +155,25 @@ If the above doesn't help, please [submit an issue](https://github.com/fastlane/
|
|
181
155
|
</a>
|
182
156
|
<h4 align='center'><a href='https://twitter.com/joshdholtz'>Josh Holtz</a></h4>
|
183
157
|
</td>
|
184
|
-
<td id='
|
185
|
-
<a href='https://github.com/
|
186
|
-
<img src='https://github.com/
|
158
|
+
<td id='felix-krause'>
|
159
|
+
<a href='https://github.com/KrauseFx'>
|
160
|
+
<img src='https://github.com/KrauseFx.png' width='140px;'>
|
187
161
|
</a>
|
188
|
-
<h4 align='center'><a href='https://twitter.com/
|
162
|
+
<h4 align='center'><a href='https://twitter.com/KrauseFx'>Felix Krause</a></h4>
|
163
|
+
</td>
|
164
|
+
</tr>
|
165
|
+
<tr>
|
166
|
+
<td id='aaron-brager'>
|
167
|
+
<a href='https://github.com/getaaron'>
|
168
|
+
<img src='https://github.com/getaaron.png' width='140px;'>
|
169
|
+
</a>
|
170
|
+
<h4 align='center'><a href='https://twitter.com/getaaron'>Aaron Brager</a></h4>
|
171
|
+
</td>
|
172
|
+
<td id='manu-wallner'>
|
173
|
+
<a href='https://github.com/milch'>
|
174
|
+
<img src='https://github.com/milch.png' width='140px;'>
|
175
|
+
</a>
|
176
|
+
<h4 align='center'><a href='https://twitter.com/acrooow'>Manu Wallner</a></h4>
|
189
177
|
</td>
|
190
178
|
<td id='jan-piotrowski'>
|
191
179
|
<a href='https://github.com/janpio'>
|
@@ -193,6 +181,18 @@ If the above doesn't help, please [submit an issue](https://github.com/fastlane/
|
|
193
181
|
</a>
|
194
182
|
<h4 align='center'><a href='https://twitter.com/Sujan'>Jan Piotrowski</a></h4>
|
195
183
|
</td>
|
184
|
+
<td id='manish-rathi'>
|
185
|
+
<a href='https://github.com/crazymanish'>
|
186
|
+
<img src='https://github.com/crazymanish.png' width='140px;'>
|
187
|
+
</a>
|
188
|
+
<h4 align='center'><a href='https://twitter.com/iammanishrathi'>Manish Rathi</a></h4>
|
189
|
+
</td>
|
190
|
+
<td id='roger-oba'>
|
191
|
+
<a href='https://github.com/rogerluan'>
|
192
|
+
<img src='https://github.com/rogerluan.png' width='140px;'>
|
193
|
+
</a>
|
194
|
+
<h4 align='center'><a href='https://twitter.com/rogerluan_'>Roger Oba</a></h4>
|
195
|
+
</td>
|
196
196
|
</tr>
|
197
197
|
</table>
|
198
198
|
|
@@ -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
|
-
|
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
|