fastlane-plugin-appstore_precheck 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: b6ff666c33d11e9de5fa7a86e07792dca8200c2104de91553939f9a2ce73da4f
4
+ data.tar.gz: c06fa42006b34cb39dc19a97c60c38f472af0cbc3c20b5cc0952b8cfd407b335
5
+ SHA512:
6
+ metadata.gz: 8e7f8a474699c8aa263e58617ec0f73e09627d267bfab3101e9fdb1c101b44e41f28360c533252e1f47c8d71d117f5a876757b7b2ba391930bfb9a14bbdb072a
7
+ data.tar.gz: 4d6d9f2d80f50ea3924592cf402143e425093458bedc9a773fbf0911486a5eba496f775a89475b320ff3fd17886c03aba48f73f8b92f30e49b570bfba039d556
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Berkay Turk
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,82 @@
1
+ # fastlane-plugin-appstore_precheck
2
+
3
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
4
+
5
+ Run [`appstore-precheck`](https://github.com/berkayturk/appstore-precheck) — a read-only iOS
6
+ App Store pre-submission scan over 42 common rejection vectors — as a fastlane lane gate.
7
+ GREEN passes, RED fails the lane before you waste an upload and a review cycle.
8
+
9
+ ## Getting started
10
+
11
+ ```bash
12
+ fastlane add_plugin appstore_precheck
13
+ ```
14
+
15
+ The plugin shells out to the `appstore-precheck` scanner. Install it once:
16
+
17
+ ```bash
18
+ brew install berkayturk/tap/appstore-precheck # or: npm i -g appstore-precheck
19
+ ```
20
+
21
+ No install needed if `npx` is on PATH — the plugin falls back to `npx --yes appstore-precheck`.
22
+
23
+ ## Usage
24
+
25
+ ```ruby
26
+ lane :beta do
27
+ appstore_precheck # fails the lane on a RED verdict
28
+ build_app
29
+ upload_to_testflight
30
+ end
31
+ ```
32
+
33
+ Options:
34
+
35
+ ```ruby
36
+ appstore_precheck(
37
+ dir: '.', # directory to scan (default: current directory)
38
+ fail_on: 'YELLOW', # gate at RED (default) or YELLOW
39
+ fail_build: true, # false: warn instead of raising, return the verdict
40
+ cli_path: '/opt/homebrew/bin/appstore-precheck' # explicit binary (optional)
41
+ )
42
+ ```
43
+
44
+ The action returns the verdict as a `String`, so you can route it:
45
+
46
+ ```ruby
47
+ verdict = appstore_precheck(fail_build: false)
48
+ slack(message: "Precheck verdict: #{verdict}") unless verdict == 'GREEN'
49
+ ```
50
+
51
+ ## What it checks
52
+
53
+ 42 static rejection vectors: Restore Purchases / Terms / Privacy on the paywall (3.1.2),
54
+ tracking SDKs without ATT (5.1.2), purpose strings (5.1.1), privacy-manifest ↔ Required Reason
55
+ API parity, account deletion (5.1.1(v)), metadata placeholders and length limits (2.1 / 2.3.1),
56
+ export compliance, and more. Full list and methodology:
57
+ [berkayturk/appstore-precheck](https://github.com/berkayturk/appstore-precheck).
58
+
59
+ The scan is **read-only**, needs **no credentials and no network**, and never edits your files.
60
+ Exit-code contract: `0` ok, `1` gate hit, `64` bad usage, `70` environment error.
61
+
62
+ ## Run tests for this plugin
63
+
64
+ ```bash
65
+ ruby test/runner_test.rb # dependency-free unit tests for the core logic
66
+ bundle install && bundle exec rspec # full action specs (needs fastlane)
67
+ ```
68
+
69
+ ## Issues and feedback
70
+
71
+ For scanner findings (false positives, missing checks) file issues on the
72
+ [main repo](https://github.com/berkayturk/appstore-precheck/issues). For plugin/action
73
+ problems, use this repo's issues.
74
+
75
+ ## Using _fastlane_ Plugins
76
+
77
+ For more information about how the fastlane plugin system works, check out the
78
+ [Plugins documentation](https://docs.fastlane.tools/plugins/create-plugin/).
79
+
80
+ ## License
81
+
82
+ [MIT](LICENSE) © Berkay Turk
@@ -0,0 +1,120 @@
1
+ require 'fastlane/action'
2
+ require 'open3'
3
+ require_relative '../helper/appstore_precheck_runner'
4
+
5
+ module Fastlane
6
+ module Actions
7
+ class AppstorePrecheckAction < Action
8
+ def self.run(params)
9
+ runner = Fastlane::AppstorePrecheck::Runner
10
+
11
+ unless runner.valid_fail_on?(params[:fail_on])
12
+ UI.user_error!("appstore_precheck: fail_on must be RED or YELLOW (got '#{params[:fail_on]}')")
13
+ end
14
+
15
+ command = runner.resolve_command(cli_path: params[:cli_path])
16
+ if command.nil?
17
+ UI.user_error!(
18
+ 'appstore_precheck: scanner not found. Install it with ' \
19
+ '`brew install berkayturk/tap/appstore-precheck` or `npm i -g appstore-precheck`, ' \
20
+ 'or pass cli_path.'
21
+ )
22
+ end
23
+
24
+ argv = command + ['--dir', params[:dir], '--fail-on', params[:fail_on].to_s.upcase]
25
+ UI.message("appstore_precheck: running #{argv.join(' ')}")
26
+
27
+ output, status = Open3.capture2e(*argv)
28
+ output.each_line { |line| UI.message(line.chomp) }
29
+
30
+ verdict = runner.parse_verdict(output)
31
+ UI.user_error!('appstore_precheck: scanner produced no verdict (see output above)') if verdict.nil?
32
+
33
+ # Exit codes: 0 ok, 1 gate hit, 64 bad usage, 70 environment error.
34
+ if !status.exitstatus.nil? && status.exitstatus > 1
35
+ UI.user_error!("appstore_precheck: scanner failed (exit #{status.exitstatus}, see output above)")
36
+ end
37
+
38
+ if runner.gate_hit?(verdict, params[:fail_on])
39
+ message = "appstore_precheck: verdict is #{verdict} (gate: #{params[:fail_on]}) — submission blocked"
40
+ if params[:fail_build]
41
+ UI.user_error!(message)
42
+ else
43
+ UI.important("#{message} (fail_build: false, continuing)")
44
+ end
45
+ else
46
+ UI.success("appstore_precheck: verdict is #{verdict} 🟢")
47
+ end
48
+
49
+ verdict
50
+ end
51
+
52
+ def self.description
53
+ 'Read-only iOS App Store pre-submission scan (42 rejection vectors) as a lane gate'
54
+ end
55
+
56
+ def self.details
57
+ 'Runs the appstore-precheck static scanner (https://github.com/berkayturk/appstore-precheck) ' \
58
+ 'over the project and fails the lane when the GREEN/YELLOW/RED verdict is at or past fail_on. ' \
59
+ 'The scan is read-only, needs no credentials, and never edits your files.'
60
+ end
61
+
62
+ def self.return_value
63
+ "The verdict as a String: 'GREEN', 'YELLOW', or 'RED'."
64
+ end
65
+
66
+ def self.authors
67
+ ['berkayturk']
68
+ end
69
+
70
+ def self.available_options
71
+ [
72
+ FastlaneCore::ConfigItem.new(
73
+ key: :dir,
74
+ env_name: 'APPSTORE_PRECHECK_DIR',
75
+ description: 'Directory to scan',
76
+ type: String,
77
+ default_value: '.'
78
+ ),
79
+ FastlaneCore::ConfigItem.new(
80
+ key: :fail_on,
81
+ env_name: 'APPSTORE_PRECHECK_FAIL_ON',
82
+ description: 'Fail the lane at RED (default) or YELLOW',
83
+ type: String,
84
+ default_value: 'RED'
85
+ ),
86
+ FastlaneCore::ConfigItem.new(
87
+ key: :fail_build,
88
+ env_name: 'APPSTORE_PRECHECK_FAIL_BUILD',
89
+ description: 'Raise when the gate is hit; set false to only warn and return the verdict',
90
+ type: Boolean,
91
+ default_value: true
92
+ ),
93
+ FastlaneCore::ConfigItem.new(
94
+ key: :cli_path,
95
+ env_name: 'APPSTORE_PRECHECK_CLI_PATH',
96
+ description: 'Explicit path to the appstore-precheck executable (default: PATH, then npx)',
97
+ type: String,
98
+ optional: true
99
+ )
100
+ ]
101
+ end
102
+
103
+ def self.example_code
104
+ [
105
+ 'appstore_precheck',
106
+ "appstore_precheck(fail_on: 'YELLOW')",
107
+ "verdict = appstore_precheck(fail_build: false)\n slack(message: \"Precheck verdict: \#{verdict}\") if verdict != 'GREEN'"
108
+ ]
109
+ end
110
+
111
+ def self.category
112
+ :testing
113
+ end
114
+
115
+ def self.is_supported?(platform)
116
+ [:ios, :mac].include?(platform)
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,51 @@
1
+ # Pure logic for the appstore_precheck action, kept free of fastlane
2
+ # dependencies so it can be unit-tested with plain Ruby.
3
+ module Fastlane
4
+ module AppstorePrecheck
5
+ module Runner
6
+ VERDICTS = %w[GREEN YELLOW RED].freeze
7
+ FAIL_ON_LEVELS = %w[RED YELLOW].freeze
8
+
9
+ module_function
10
+
11
+ # Resolve the scanner command as an argv array, or nil if none is available.
12
+ # Preference order: explicit cli_path, `appstore-precheck` on PATH
13
+ # (brew / npm -g), then `npx --yes appstore-precheck`.
14
+ def resolve_command(cli_path: nil, which: ->(bin) { system_which(bin) })
15
+ return [cli_path] if cli_path && !cli_path.to_s.empty?
16
+ return ['appstore-precheck'] if which.call('appstore-precheck')
17
+ return ['npx', '--yes', 'appstore-precheck'] if which.call('npx')
18
+
19
+ nil
20
+ end
21
+
22
+ def system_which(bin)
23
+ ENV.fetch('PATH', '').split(File::PATH_SEPARATOR).any? do |dir|
24
+ candidate = File.join(dir, bin)
25
+ File.executable?(candidate) && !File.directory?(candidate)
26
+ end
27
+ end
28
+
29
+ # Extract the verdict from scanner output; nil if absent.
30
+ def parse_verdict(output)
31
+ match = output.to_s.match(/^VERDICT:\s*(GREEN|YELLOW|RED)/)
32
+ match && match[1]
33
+ end
34
+
35
+ # True when the verdict is at or past the gate level.
36
+ def gate_hit?(verdict, fail_on)
37
+ return false unless VERDICTS.include?(verdict.to_s)
38
+
39
+ if fail_on.to_s.upcase == 'YELLOW'
40
+ verdict != 'GREEN'
41
+ else
42
+ verdict == 'RED'
43
+ end
44
+ end
45
+
46
+ def valid_fail_on?(level)
47
+ FAIL_ON_LEVELS.include?(level.to_s.upcase)
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,5 @@
1
+ module Fastlane
2
+ module AppstorePrecheck
3
+ VERSION = '0.1.0'.freeze
4
+ end
5
+ end
@@ -0,0 +1,16 @@
1
+ require 'fastlane/plugin/appstore_precheck/version'
2
+
3
+ module Fastlane
4
+ module AppstorePrecheck
5
+ # Return all .rb files inside the "actions" and "helper" directories
6
+ def self.all_classes
7
+ Dir[File.expand_path('**/{actions,helper}/*.rb', File.dirname(__FILE__))]
8
+ end
9
+ end
10
+ end
11
+
12
+ # By default we want to import all available actions and helpers
13
+ # A plugin can contain any number of actions and plugins
14
+ Fastlane::AppstorePrecheck.all_classes.each do |current|
15
+ require current
16
+ end
metadata ADDED
@@ -0,0 +1,106 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fastlane-plugin-appstore_precheck
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Berkay Turk
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-07-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: fastlane
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 2.220.0
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 2.220.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description:
70
+ email: berkaytrk6@gmail.com
71
+ executables: []
72
+ extensions: []
73
+ extra_rdoc_files: []
74
+ files:
75
+ - LICENSE
76
+ - README.md
77
+ - lib/fastlane/plugin/appstore_precheck.rb
78
+ - lib/fastlane/plugin/appstore_precheck/actions/appstore_precheck_action.rb
79
+ - lib/fastlane/plugin/appstore_precheck/helper/appstore_precheck_runner.rb
80
+ - lib/fastlane/plugin/appstore_precheck/version.rb
81
+ homepage: https://github.com/berkayturk/fastlane-plugin-appstore_precheck
82
+ licenses:
83
+ - MIT
84
+ metadata:
85
+ rubygems_mfa_required: 'true'
86
+ post_install_message:
87
+ rdoc_options: []
88
+ require_paths:
89
+ - lib
90
+ required_ruby_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: '2.6'
95
+ required_rubygems_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ requirements: []
101
+ rubygems_version: 3.0.3.1
102
+ signing_key:
103
+ specification_version: 4
104
+ summary: Read-only iOS App Store pre-submission scan (42 rejection vectors) as a fastlane
105
+ lane gate
106
+ test_files: []