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 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