fastlane 2.187.0 → 2.191.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +94 -94
  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/app_store_connect_api_key.rb +1 -1
  11. data/fastlane/lib/fastlane/actions/appledoc.rb +45 -45
  12. data/fastlane/lib/fastlane/actions/automatic_code_signing.rb +1 -2
  13. data/fastlane/lib/fastlane/actions/bundle_install.rb +13 -1
  14. data/fastlane/lib/fastlane/actions/clean_cocoapods_cache.rb +25 -1
  15. data/fastlane/lib/fastlane/actions/docs/capture_android_screenshots.md +2 -2
  16. data/fastlane/lib/fastlane/actions/docs/upload_to_app_store.md.erb +2 -2
  17. data/fastlane/lib/fastlane/actions/gradle.rb +1 -1
  18. data/fastlane/lib/fastlane/actions/update_icloud_container_identifiers.rb +1 -4
  19. data/fastlane/lib/fastlane/actions/update_info_plist.rb +1 -1
  20. data/fastlane/lib/fastlane/actions/update_keychain_access_groups.rb +1 -4
  21. data/fastlane/lib/fastlane/actions/update_plist.rb +1 -1
  22. data/fastlane/lib/fastlane/actions/update_project_provisioning.rb +2 -2
  23. data/fastlane/lib/fastlane/actions/update_urban_airship_configuration.rb +0 -1
  24. data/fastlane/lib/fastlane/actions/update_url_schemes.rb +15 -26
  25. data/fastlane/lib/fastlane/actions/upload_app_privacy_details_to_app_store.rb +1 -2
  26. data/fastlane/lib/fastlane/actions/upload_symbols_to_sentry.rb +3 -10
  27. data/fastlane/lib/fastlane/actions/validate_play_store_json_key.rb +40 -44
  28. data/fastlane/lib/fastlane/actions/version_get_podspec.rb +1 -2
  29. data/fastlane/lib/fastlane/actions/xcode_server_get_assets.rb +3 -3
  30. data/fastlane/lib/fastlane/actions/zip.rb +86 -21
  31. data/fastlane/lib/fastlane/documentation/markdown_docs_generator.rb +1 -1
  32. data/fastlane/lib/fastlane/features.rb +3 -0
  33. data/fastlane/lib/fastlane/plugins/template/.circleci/config.yml +1 -1
  34. data/fastlane/lib/fastlane/plugins/template/.github/workflows/test.yml +1 -1
  35. data/fastlane/lib/fastlane/version.rb +1 -1
  36. data/fastlane/swift/Deliverfile.swift +1 -1
  37. data/fastlane/swift/DeliverfileProtocol.swift +5 -1
  38. data/fastlane/swift/Fastlane.swift +108 -46
  39. data/fastlane/swift/Gymfile.swift +1 -1
  40. data/fastlane/swift/GymfileProtocol.swift +1 -1
  41. data/fastlane/swift/Matchfile.swift +1 -1
  42. data/fastlane/swift/MatchfileProtocol.swift +1 -1
  43. data/fastlane/swift/Precheckfile.swift +1 -1
  44. data/fastlane/swift/PrecheckfileProtocol.swift +1 -1
  45. data/fastlane/swift/Scanfile.swift +1 -1
  46. data/fastlane/swift/ScanfileProtocol.swift +5 -1
  47. data/fastlane/swift/Screengrabfile.swift +1 -1
  48. data/fastlane/swift/ScreengrabfileProtocol.swift +1 -1
  49. data/fastlane/swift/Snapshotfile.swift +1 -1
  50. data/fastlane/swift/SnapshotfileProtocol.swift +1 -1
  51. data/fastlane/swift/formatting/Brewfile.lock.json +17 -9
  52. data/fastlane_core/lib/fastlane_core/keychain_importer.rb +11 -4
  53. data/fastlane_core/lib/fastlane_core/ui/disable_colors.rb +1 -0
  54. data/pilot/lib/pilot.rb +0 -1
  55. data/precheck/lib/precheck/module.rb +2 -0
  56. data/precheck/lib/precheck/options.rb +3 -3
  57. data/produce/lib/produce/commands_generator.rb +28 -0
  58. data/produce/lib/produce/service.rb +15 -0
  59. data/scan/lib/scan/detect_values.rb +22 -13
  60. data/scan/lib/scan/module.rb +1 -0
  61. data/scan/lib/scan/options.rb +12 -1
  62. data/scan/lib/scan/test_command_generator.rb +29 -6
  63. data/scan/lib/scan/xcpretty_reporter_options_generator.rb +1 -1
  64. data/screengrab/lib/screengrab/runner.rb +2 -2
  65. data/sigh/lib/sigh/options.rb +2 -1
  66. data/spaceship/lib/spaceship/client.rb +19 -3
  67. data/spaceship/lib/spaceship/connect_api.rb +1 -0
  68. data/spaceship/lib/spaceship/connect_api/models/app.rb +7 -0
  69. data/spaceship/lib/spaceship/connect_api/models/capabilities.rb +27 -0
  70. data/spaceship/lib/spaceship/connect_api/models/user.rb +17 -3
  71. data/spaceship/lib/spaceship/connect_api/models/user_invitation.rb +26 -5
  72. data/spaceship/lib/spaceship/connect_api/provisioning/provisioning.rb +5 -0
  73. data/spaceship/lib/spaceship/connect_api/testflight/client.rb +3 -0
  74. data/spaceship/lib/spaceship/connect_api/testflight/testflight.rb +39 -0
  75. data/spaceship/lib/spaceship/connect_api/token.rb +2 -1
  76. data/spaceship/lib/spaceship/connect_api/tunes/client.rb +3 -0
  77. data/spaceship/lib/spaceship/connect_api/users/client.rb +3 -0
  78. data/spaceship/lib/spaceship/connect_api/users/users.rb +58 -3
  79. data/spaceship/lib/spaceship/tunes/tunes_client.rb +3 -0
  80. data/supply/lib/supply/client.rb +7 -1
  81. data/supply/lib/supply/options.rb +5 -0
  82. data/supply/lib/supply/uploader.rb +1 -1
  83. metadata +26 -28
  84. data/fastlane/lib/fastlane/.erb_template_helper.rb.swp +0 -0
  85. data/gym/lib/gym/generators/.package_command_generator_xcode7.rb.swp +0 -0
  86. data/pilot/lib/pilot/features.rb +0 -0
  87. data/spaceship/lib/spaceship/.DS_Store +0 -0
  88. data/spaceship/lib/spaceship/connect_api/models/.app_store_version_submission.rb.swp +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d370ddf4cddc81061e1d5b91d5c00f7fc33a4c17e4fbfb756e7a66152e1299c9
4
- data.tar.gz: c7f56aef41b36eb80a7326e9e805a13e6b012b30c9711d1f6b51139e649d7f22
3
+ metadata.gz: 191c5f2f91fe567ba6f0c35848e69c0b921480ef1f2076bd531b774b6773bada
4
+ data.tar.gz: 6cf068574ea7ea72ac092791021fc78c2d1486d9e6f03772cc9564420e98a989
5
5
  SHA512:
6
- metadata.gz: 6e399e4e24a24504a7535d2fc566d1334eb11d4f215db34f8bf385f29432c1add2b14a9003858e9fbcf5bc8b5751b7d81eec8d9bf0b116bde934a8a52ae3117e
7
- data.tar.gz: 349ec0df013dcb650364be0cff77b86c38fe4bc31e811ebf5f878164ad0b4d8077be55c83578f4063d81ff99ebd9c8edb7061c0ceec23ae140d5c89805f3a848
6
+ metadata.gz: aacb03423627e0dc324915167515f0a4c8faebd6349c9a989320332baa668782f7dfbd0c20899e0905fcffc3d3ad3f8e254e05f75b30facff9744636b37c3c00
7
+ data.tar.gz: fa30c4044f3e3dffa7b53ed4a48d9e6fa2805649538077e2134f41eeada06e9bc66f4821124a5e59fb29542727aa8df9b278660ae522fbab134bee4427c6bc13
data/README.md CHANGED
@@ -35,43 +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='fumiya-nakamura'>
39
- <a href='https://github.com/nafu'>
40
- <img src='https://github.com/nafu.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/nafu003'>Fumiya Nakamura</a></h4>
42
+ <h4 align='center'><a href='https://twitter.com/joshdholtz'>Josh Holtz</a></h4>
43
43
  </td>
44
- <td id='joshua-liebowitz'>
45
- <a href='https://github.com/taquitos'>
46
- <img src='https://github.com/taquitos.png' width='140px;'>
44
+ <td id='aaron-brager'>
45
+ <a href='https://github.com/getaaron'>
46
+ <img src='https://github.com/getaaron.png' width='140px;'>
47
47
  </a>
48
- <h4 align='center'><a href='https://twitter.com/taquitos'>Joshua Liebowitz</a></h4>
48
+ <h4 align='center'><a href='https://twitter.com/getaaron'>Aaron Brager</a></h4>
49
49
  </td>
50
- <td id='jorge-revuelta-h'>
51
- <a href='https://github.com/minuscorp'>
52
- <img src='https://github.com/minuscorp.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/minuscorp'>Jorge Revuelta H</a></h4>
54
+ <h4 align='center'><a href='https://twitter.com/mollyIV'>Daniel Jankowski</a></h4>
55
55
  </td>
56
- <td id='felix-krause'>
57
- <a href='https://github.com/KrauseFx'>
58
- <img src='https://github.com/KrauseFx.png' width='140px;'>
56
+ <td id='luka-mirosevic'>
57
+ <a href='https://github.com/lmirosevic'>
58
+ <img src='https://github.com/lmirosevic.png' width='140px;'>
59
59
  </a>
60
- <h4 align='center'><a href='https://twitter.com/KrauseFx'>Felix Krause</a></h4>
60
+ <h4 align='center'><a href='https://twitter.com/lmirosevic'>Luka Mirosevic</a></h4>
61
61
  </td>
62
- <td id='andrew-mcburney'>
63
- <a href='https://github.com/armcburney'>
64
- <img src='https://github.com/armcburney.png' width='140px;'>
62
+ <td id='satoshi-namai'>
63
+ <a href='https://github.com/ainame'>
64
+ <img src='https://github.com/ainame.png' width='140px;'>
65
65
  </a>
66
- <h4 align='center'><a href='https://twitter.com/armcburney'>Andrew McBurney</a></h4>
66
+ <h4 align='center'><a href='https://twitter.com/ainame'>Satoshi Namai</a></h4>
67
67
  </td>
68
68
  </tr>
69
69
  <tr>
70
- <td id='matthew-ellis'>
71
- <a href='https://github.com/matthewellis'>
72
- <img src='https://github.com/matthewellis.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/mellis1995'>Matthew Ellis</a></h4>
74
+ <h4 align='center'><a href='https://twitter.com/ott_max'>Max Ott</a></h4>
75
75
  </td>
76
76
  <td id='maksym-grebenets'>
77
77
  <a href='https://github.com/mgrebenets'>
@@ -79,119 +79,119 @@ If the above doesn't help, please [submit an issue](https://github.com/fastlane/
79
79
  </a>
80
80
  <h4 align='center'><a href='https://twitter.com/mgrebenets'>Maksym Grebenets</a></h4>
81
81
  </td>
82
+ <td id='felix-krause'>
83
+ <a href='https://github.com/KrauseFx'>
84
+ <img src='https://github.com/KrauseFx.png' width='140px;'>
85
+ </a>
86
+ <h4 align='center'><a href='https://twitter.com/KrauseFx'>Felix Krause</a></h4>
87
+ </td>
82
88
  <td id='kohki-miki'>
83
89
  <a href='https://github.com/giginet'>
84
90
  <img src='https://github.com/giginet.png' width='140px;'>
85
91
  </a>
86
92
  <h4 align='center'><a href='https://twitter.com/giginet'>Kohki Miki</a></h4>
87
93
  </td>
88
- <td id='iulian-onofrei'>
89
- <a href='https://github.com/revolter'>
90
- <img src='https://github.com/revolter.png' width='140px;'>
91
- </a>
92
- <h4 align='center'><a href='https://twitter.com/Revolt666'>Iulian Onofrei</a></h4>
93
- </td>
94
- <td id='manish-rathi'>
95
- <a href='https://github.com/crazymanish'>
96
- <img src='https://github.com/crazymanish.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/iammanishrathi'>Manish Rathi</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='satoshi-namai'>
103
- <a href='https://github.com/ainame'>
104
- <img src='https://github.com/ainame.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/ainame'>Satoshi Namai</a></h4>
106
+ <h4 align='center'>Jimmy Dee</h4>
107
107
  </td>
108
- <td id='manu-wallner'>
109
- <a href='https://github.com/milch'>
110
- <img src='https://github.com/milch.png' width='140px;'>
108
+ <td id='roger-oba'>
109
+ <a href='https://github.com/rogerluan'>
110
+ <img src='https://github.com/rogerluan.png' width='140px;'>
111
111
  </a>
112
- <h4 align='center'><a href='https://twitter.com/acrooow'>Manu Wallner</a></h4>
112
+ <h4 align='center'><a href='https://twitter.com/rogerluan_'>Roger Oba</a></h4>
113
113
  </td>
114
- <td id='max-ott'>
115
- <a href='https://github.com/max-ott'>
116
- <img src='https://github.com/max-ott.png' width='140px;'>
114
+ <td id='matthew-ellis'>
115
+ <a href='https://github.com/matthewellis'>
116
+ <img src='https://github.com/matthewellis.png' width='140px;'>
117
117
  </a>
118
- <h4 align='center'><a href='https://twitter.com/ott_max'>Max Ott</a></h4>
118
+ <h4 align='center'><a href='https://twitter.com/mellis1995'>Matthew Ellis</a></h4>
119
119
  </td>
120
- <td id='josh-holtz'>
121
- <a href='https://github.com/joshdholtz'>
122
- <img src='https://github.com/joshdholtz.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/joshdholtz'>Josh Holtz</a></h4>
124
+ <h4 align='center'><a href='https://twitter.com/minuscorp'>Jorge Revuelta H</a></h4>
125
125
  </td>
126
- <td id='jimmy-dee'>
127
- <a href='https://github.com/jdee'>
128
- <img src='https://github.com/jdee.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'>Jimmy Dee</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='helmut-januschka'>
135
- <a href='https://github.com/hjanuschka'>
136
- <img src='https://github.com/hjanuschka.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'><a href='https://twitter.com/hjanuschka'>Helmut Januschka</a></h4>
138
+ <h4 align='center'><a href='https://twitter.com/iammanishrathi'>Manish Rathi</a></h4>
139
139
  </td>
140
- <td id='luka-mirosevic'>
141
- <a href='https://github.com/lmirosevic'>
142
- <img src='https://github.com/lmirosevic.png' width='140px;'>
140
+ <td id='danielle-tomlinson'>
141
+ <a href='https://github.com/endocrimes'>
142
+ <img src='https://github.com/endocrimes.png' width='140px;'>
143
143
  </a>
144
- <h4 align='center'><a href='https://twitter.com/lmirosevic'>Luka Mirosevic</a></h4>
144
+ <h4 align='center'><a href='https://twitter.com/endocrimes'>Danielle Tomlinson</a></h4>
145
145
  </td>
146
- <td id='roger-oba'>
147
- <a href='https://github.com/rogerluan'>
148
- <img src='https://github.com/rogerluan.png' width='140px;'>
146
+ <td id='joshua-liebowitz'>
147
+ <a href='https://github.com/taquitos'>
148
+ <img src='https://github.com/taquitos.png' width='140px;'>
149
149
  </a>
150
- <h4 align='center'><a href='https://twitter.com/rogerluan_'>Roger Oba</a></h4>
150
+ <h4 align='center'><a href='https://twitter.com/taquitos'>Joshua Liebowitz</a></h4>
151
151
  </td>
152
- <td id='olivier-halligon'>
153
- <a href='https://github.com/AliSoftware'>
154
- <img src='https://github.com/AliSoftware.png' width='140px;'>
152
+ <td id='fumiya-nakamura'>
153
+ <a href='https://github.com/nafu'>
154
+ <img src='https://github.com/nafu.png' width='140px;'>
155
155
  </a>
156
- <h4 align='center'><a href='https://twitter.com/aligatr'>Olivier Halligon</a></h4>
156
+ <h4 align='center'><a href='https://twitter.com/nafu003'>Fumiya Nakamura</a></h4>
157
157
  </td>
158
- <td id='stefan-natchev'>
159
- <a href='https://github.com/snatchev'>
160
- <img src='https://github.com/snatchev.png' width='140px;'>
158
+ <td id='jérôme-lacoste'>
159
+ <a href='https://github.com/lacostej'>
160
+ <img src='https://github.com/lacostej.png' width='140px;'>
161
161
  </a>
162
- <h4 align='center'><a href='https://twitter.com/snatchev'>Stefan Natchev</a></h4>
162
+ <h4 align='center'><a href='https://twitter.com/lacostej'>Jérôme Lacoste</a></h4>
163
163
  </td>
164
164
  </tr>
165
165
  <tr>
166
- <td id='aaron-brager'>
167
- <a href='https://github.com/getaaron'>
168
- <img src='https://github.com/getaaron.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/getaaron'>Aaron Brager</a></h4>
170
+ <h4 align='center'><a href='https://twitter.com/acrooow'>Manu Wallner</a></h4>
171
171
  </td>
172
- <td id='jérôme-lacoste'>
173
- <a href='https://github.com/lacostej'>
174
- <img src='https://github.com/lacostej.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/lacostej'>Jérôme Lacoste</a></h4>
176
+ <h4 align='center'><a href='https://twitter.com/armcburney'>Andrew McBurney</a></h4>
177
177
  </td>
178
- <td id='jan-piotrowski'>
179
- <a href='https://github.com/janpio'>
180
- <img src='https://github.com/janpio.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/Sujan'>Jan Piotrowski</a></h4>
182
+ <h4 align='center'><a href='https://twitter.com/aligatr'>Olivier Halligon</a></h4>
183
183
  </td>
184
- <td id='danielle-tomlinson'>
185
- <a href='https://github.com/endocrimes'>
186
- <img src='https://github.com/endocrimes.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/endocrimes'>Danielle Tomlinson</a></h4>
188
+ <h4 align='center'><a href='https://twitter.com/Revolt666'>Iulian Onofrei</a></h4>
189
189
  </td>
190
- <td id='daniel-jankowski'>
191
- <a href='https://github.com/mollyIV'>
192
- <img src='https://github.com/mollyIV.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/mollyIV'>Daniel Jankowski</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