fastlane 2.187.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 (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