fastlane-plugin-store_screenshot_check 0.1.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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +120 -0
- data/THIRD_PARTY_LICENSES.txt +49 -0
- data/lib/fastlane/plugin/store_screenshot_check/actions/store_screenshot_check_action.rb +74 -0
- data/lib/fastlane/plugin/store_screenshot_check/helper/validate.cjs +5928 -0
- data/lib/fastlane/plugin/store_screenshot_check/version.rb +5 -0
- data/lib/fastlane/plugin/store_screenshot_check.rb +2 -0
- metadata +69 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 39b9ce01745b8ea131d9492c960d34c66e4aff76fb7b74fa82b2b466bc273c54
|
|
4
|
+
data.tar.gz: 8e45cba9c4706d5b90f0d1409706b25d71cc25544999375727e494ef692dec1c
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 4850003b14ce13a0a91b346e9b8919f8e2a990993905d6acf126fc94608ed865967f3a3af40216158a6bc0f09e36f14920c9978963b8f28b5a0a1a762713dd10
|
|
7
|
+
data.tar.gz: c06003e8400e5604476c78183e5560a6fd00ce93090cea1d2112d542891ec53d8c9ebc0a8a389d8aa22286b49961e7f80241b84ef0dc86bae6faa3a1b2fa830b
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Martin Grüner
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Store Screenshot Check for Fastlane
|
|
2
|
+
|
|
3
|
+
Check a screenshot folder before a lane uploads it to App Store Connect or Google
|
|
4
|
+
Play. The `store_screenshot_check` action checks image data, dimensions,
|
|
5
|
+
transparency and set counts using `@grunersoftware/store-screenshot-specs` 0.1.0.
|
|
6
|
+
It returns a report and, by default, stops the lane when a file or set fails.
|
|
7
|
+
|
|
8
|
+
Version 0.1.0 is available from its public GitHub release. Until the RubyGem is
|
|
9
|
+
published, add the tagged repository to your project's `fastlane/Pluginfile`:
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
gem 'fastlane-plugin-store_screenshot_check',
|
|
13
|
+
git: 'https://github.com/MartinGryner/fastlane-plugin-store_screenshot_check.git',
|
|
14
|
+
tag: 'v0.1.0'
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Run `bundle install` in your project. The lane host needs Node.js 22 or newer,
|
|
18
|
+
Ruby 3.1 or newer, and Fastlane 2.220 or newer (2.x). The gem includes its
|
|
19
|
+
JavaScript validator and dependencies; no npm install is needed to run it.
|
|
20
|
+
|
|
21
|
+
## Use before upload
|
|
22
|
+
|
|
23
|
+
```ruby
|
|
24
|
+
lane :check_screenshots do
|
|
25
|
+
root = File.expand_path('..', __dir__) # Fastfile is in the fastlane directory
|
|
26
|
+
report = store_screenshot_check(
|
|
27
|
+
workspace: root,
|
|
28
|
+
directory: 'screenshots/en-US/iphone',
|
|
29
|
+
target: 'apple-iphone-69'
|
|
30
|
+
)
|
|
31
|
+
UI.message("Checked #{report['files']} screenshots")
|
|
32
|
+
# Put your existing upload action after this check.
|
|
33
|
+
end
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
For a Google Play phone set, use `target: 'google-phone'`. Call the action once
|
|
37
|
+
for each locale/device directory; it does not recursively classify an entire
|
|
38
|
+
Fastlane screenshot tree. Each directory should contain screenshot files only.
|
|
39
|
+
|
|
40
|
+
| Parameter | Meaning |
|
|
41
|
+
| --- | --- |
|
|
42
|
+
| `directory` | Required. One screenshot directory inside `workspace`. |
|
|
43
|
+
| `target` | Required. Exact target ID from the supported list below. |
|
|
44
|
+
| `workspace` | Root used to resolve and constrain the screenshot path. Defaults to the process working directory. Prefer an explicit absolute path. |
|
|
45
|
+
| `node` | Node.js executable name or path. Defaults to `node`. |
|
|
46
|
+
| `fail_on_error` | Default `true`. Use `false` to return invalid-file/set findings without stopping the lane. |
|
|
47
|
+
| `fail_on_review` | Default `false`. Use `true` to also stop for manual-review warnings. |
|
|
48
|
+
|
|
49
|
+
Invalid configuration, missing Node.js, unsupported targets, empty directories,
|
|
50
|
+
and directories outside the workspace always stop the lane, even when
|
|
51
|
+
`fail_on_error` is false. A failed lane exits unsuccessfully unless your Fastfile
|
|
52
|
+
explicitly catches the exception. No upload action is called by this plugin.
|
|
53
|
+
|
|
54
|
+
The returned Hash has string keys: `target`, `specsVersion`, `files`, `failures`,
|
|
55
|
+
`reviews`, `passes`, `results`, `errors` and `countCheck`. `results` contains image
|
|
56
|
+
metadata and checks with `tone`, `label`, and `detail`; `errors` contains files
|
|
57
|
+
that could not be inspected. `failures` counts failed files plus a failed set-count
|
|
58
|
+
check. `reviews` counts files needing review plus a set-count warning. The report
|
|
59
|
+
contains filenames and metadata, not image pixels.
|
|
60
|
+
|
|
61
|
+
## Supported targets and limits
|
|
62
|
+
|
|
63
|
+
| Target IDs | Checks |
|
|
64
|
+
| --- | --- |
|
|
65
|
+
| `apple-iphone-duo`, `apple-iphone-69`, `apple-iphone-65`, `apple-iphone-63`, `apple-iphone-61`, `apple-iphone-55`, `apple-iphone-47`, `apple-iphone-4`, `apple-iphone-35` | Exact supported sizes, PNG/JPEG, transparency, 1–10 screenshots. |
|
|
66
|
+
| `apple-ipad-13`, `apple-ipad-129`, `apple-ipad-11`, `apple-ipad-105`, `apple-ipad-97`, `apple-mac` | Exact supported sizes, PNG/JPEG, transparency, 1–10 screenshots. |
|
|
67
|
+
| `google-phone` | JPEG or 24-bit PNG without alpha, edges 320–3840px, long edge at most twice the short edge, at most 8 screenshots. |
|
|
68
|
+
|
|
69
|
+
Google requires two screenshots across device types, so a phone set with only one
|
|
70
|
+
image receives a review warning. Promotional dimension guidance also produces
|
|
71
|
+
warnings; passing those checks does not establish promotional eligibility.
|
|
72
|
+
Apple/Google file-size limits are not asserted. Only the target IDs in the table
|
|
73
|
+
are supported. Google tablet/TV/watch sets and Microsoft Store are unsupported
|
|
74
|
+
by this first plugin release.
|
|
75
|
+
|
|
76
|
+
PNG and JPEG files are decoded; a renamed extension or a plausible header is not
|
|
77
|
+
enough. PNG checks include CRCs, alpha/transparency markers, bit depth and color
|
|
78
|
+
type. Hidden files and subdirectories are ignored. Other visible entries count
|
|
79
|
+
toward the set; symlinks, special files, unsupported data and corrupt images fail.
|
|
80
|
+
The decoder limits are 64 MiB per file, 24 megapixels per decoded image and 100
|
|
81
|
+
visible non-directory entries per folder. These are validator limits, not store
|
|
82
|
+
requirements.
|
|
83
|
+
|
|
84
|
+
This checks one image set, not screenshot content, text overflow, required
|
|
85
|
+
languages, complete device coverage, account eligibility, or store approval.
|
|
86
|
+
Current store references:
|
|
87
|
+
[Apple](https://developer.apple.com/help/app-store-connect/reference/app-information/screenshot-specifications/)
|
|
88
|
+
and [Google Play](https://support.google.com/googleplay/android-developer/answer/9866151?hl=en).
|
|
89
|
+
|
|
90
|
+
## Privacy and screenshot creation
|
|
91
|
+
|
|
92
|
+
The validator processes files on the machine running the lane, makes no network
|
|
93
|
+
requests, uploads no screenshots, and sends no data to an AI model. On CI, that
|
|
94
|
+
machine is the CI runner; filenames and findings appear in its logs. Your other
|
|
95
|
+
Fastlane actions and CI provider have their own data handling.
|
|
96
|
+
|
|
97
|
+
[Screen Studio Kit](https://martingruner.com/projects/screenshot-studio) is an
|
|
98
|
+
optional app for creating screenshot sets. It is not required to run this check.
|
|
99
|
+
You can also use the [browser validator](https://martingruner.com/tools/store-screenshot-validator).
|
|
100
|
+
|
|
101
|
+
## Build and test in this workspace
|
|
102
|
+
|
|
103
|
+
The runtime bundle imports the existing validator from
|
|
104
|
+
`../validate-store-screenshots/src/validate.mjs` and the shared specification
|
|
105
|
+
package. There is no independently maintained rule table in this plugin.
|
|
106
|
+
|
|
107
|
+
```sh
|
|
108
|
+
npm --prefix ../store-screenshot-specs run build
|
|
109
|
+
npm --prefix ../validate-store-screenshots ci
|
|
110
|
+
node scripts/build.mjs
|
|
111
|
+
node --test test/validator.test.mjs
|
|
112
|
+
ruby test/action_test.rb
|
|
113
|
+
gem build fastlane-plugin-store_screenshot_check.gemspec
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
The Ruby integration test requires Fastlane to be installed. Rebuild the bundle
|
|
117
|
+
whenever the shared validator or specifications change. Keep `LICENSE` and
|
|
118
|
+
`THIRD_PARTY_LICENSES.txt` with the bundle. The public standalone repository
|
|
119
|
+
includes the shared build inputs and pinned dependency manifests needed to rebuild
|
|
120
|
+
the helper. Repeat these checks before each tagged release.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
pngjs
|
|
2
|
+
pngjs original work Copyright (c) 2015 Luke Page & Original Contributors
|
|
3
|
+
pngjs derived work Copyright (c) 2012 Kuba Niegowski
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
jpeg-js
|
|
25
|
+
Copyright (c) 2014, Eugene Ware
|
|
26
|
+
All rights reserved.
|
|
27
|
+
|
|
28
|
+
Redistribution and use in source and binary forms, with or without
|
|
29
|
+
modification, are permitted provided that the following conditions are met:
|
|
30
|
+
|
|
31
|
+
1. Redistributions of source code must retain the above copyright
|
|
32
|
+
notice, this list of conditions and the following disclaimer.
|
|
33
|
+
2. Redistributions in binary form must reproduce the above copyright
|
|
34
|
+
notice, this list of conditions and the following disclaimer in the
|
|
35
|
+
documentation and/or other materials provided with the distribution.
|
|
36
|
+
3. Neither the name of Eugene Ware nor the names of its contributors
|
|
37
|
+
may be used to endorse or promote products derived from this software
|
|
38
|
+
without specific prior written permission.
|
|
39
|
+
|
|
40
|
+
THIS SOFTWARE IS PROVIDED BY EUGENE WARE ''AS IS'' AND ANY
|
|
41
|
+
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
42
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
43
|
+
DISCLAIMED. IN NO EVENT SHALL EUGENE WARE BE LIABLE FOR ANY
|
|
44
|
+
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
45
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
46
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
47
|
+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
48
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
49
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
require 'fastlane/action'
|
|
2
|
+
require 'json'
|
|
3
|
+
require 'open3'
|
|
4
|
+
|
|
5
|
+
module Fastlane
|
|
6
|
+
module Actions
|
|
7
|
+
class StoreScreenshotCheckAction < Action
|
|
8
|
+
def self.run(params)
|
|
9
|
+
helper = File.expand_path('../helper/validate.cjs', __dir__)
|
|
10
|
+
request = {
|
|
11
|
+
directory: params[:directory], target: params[:target],
|
|
12
|
+
workspace: File.expand_path(params[:workspace]),
|
|
13
|
+
fail_on_error: params[:fail_on_error], fail_on_review: params[:fail_on_review]
|
|
14
|
+
}
|
|
15
|
+
# Separate arguments avoid interpreting directory names or executable paths as shell code.
|
|
16
|
+
stdout, stderr, status = Open3.capture3(
|
|
17
|
+
[params[:node], params[:node]], helper, stdin_data: JSON.generate(request)
|
|
18
|
+
)
|
|
19
|
+
begin
|
|
20
|
+
report = JSON.parse(stdout)
|
|
21
|
+
rescue JSON::ParserError
|
|
22
|
+
UI.user_error!("Screenshot validator did not return JSON. #{stderr.strip}")
|
|
23
|
+
end
|
|
24
|
+
UI.user_error!(report['error'] || "Screenshot validator failed: #{stderr.strip}") unless [0, 1].include?(status.exitstatus)
|
|
25
|
+
UI.message("Screenshot check: #{report['files']} files, #{report['failures']} failures, #{report['reviews']} review warnings (#{report['target']})")
|
|
26
|
+
report.fetch('errors', []).each { |error| UI.error("#{error['name']}: #{error['message']}") }
|
|
27
|
+
report.fetch('results', []).each do |result|
|
|
28
|
+
result.fetch('checks').each do |check|
|
|
29
|
+
next if check['tone'] == 'pass'
|
|
30
|
+
message = "#{result['name']}: #{check['label']}: #{check['detail']}"
|
|
31
|
+
check['tone'] == 'fail' ? UI.error(message) : UI.important(message)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
count = report.fetch('countCheck')
|
|
35
|
+
UI.important(count['detail']) unless count['tone'] == 'pass'
|
|
36
|
+
UI.user_error!('Screenshot validation failed. See the file and set diagnostics above.') unless status.success?
|
|
37
|
+
report
|
|
38
|
+
rescue Errno::ENOENT => error
|
|
39
|
+
UI.user_error!("Unable to start screenshot validator. Install Node.js 22 or newer and set node to its executable if needed. #{error.message}")
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def self.description
|
|
43
|
+
'Validate one Apple or Google Play phone screenshot folder before upload'
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def self.details
|
|
47
|
+
'Uses bundled shared store-screenshot-specs rules and PNG/JPEG decoders. Requires Node.js 22+. Runs locally on the lane host. Does not upload files or guarantee store approval.'
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def self.authors
|
|
51
|
+
['Martin Grüner']
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def self.return_value
|
|
55
|
+
'Hash with target, files, failures, reviews, passes, results, errors, countCheck and specsVersion (string keys)'
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def self.available_options
|
|
59
|
+
[
|
|
60
|
+
FastlaneCore::ConfigItem.new(key: :directory, env_name: 'STORE_SCREENSHOT_CHECK_DIRECTORY', description: 'One non-recursive screenshot directory inside workspace', type: String),
|
|
61
|
+
FastlaneCore::ConfigItem.new(key: :target, env_name: 'STORE_SCREENSHOT_CHECK_TARGET', description: 'Supported apple-* target ID or google-phone; see README', type: String),
|
|
62
|
+
FastlaneCore::ConfigItem.new(key: :workspace, env_name: 'STORE_SCREENSHOT_CHECK_WORKSPACE', description: 'Root that screenshot directory must be inside', type: String, optional: true, default_value: Dir.pwd),
|
|
63
|
+
FastlaneCore::ConfigItem.new(key: :node, env_name: 'STORE_SCREENSHOT_CHECK_NODE', description: 'Node.js 22+ executable name or path', type: String, optional: true, default_value: 'node'),
|
|
64
|
+
FastlaneCore::ConfigItem.new(key: :fail_on_error, env_name: 'STORE_SCREENSHOT_CHECK_FAIL_ON_ERROR', description: 'Stop the lane for invalid screenshot files or set counts', type: Boolean, optional: true, default_value: true),
|
|
65
|
+
FastlaneCore::ConfigItem.new(key: :fail_on_review, env_name: 'STORE_SCREENSHOT_CHECK_FAIL_ON_REVIEW', description: 'Also stop the lane for manual-review warnings', type: Boolean, optional: true, default_value: false)
|
|
66
|
+
]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def self.is_supported?(platform)
|
|
70
|
+
[:ios, :mac, :android].include?(platform)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|