appstore-precheck 1.9.0 → 1.10.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.
- package/CHANGELOG.md +32 -0
- package/README.md +23 -0
- package/bin/cli.js +14 -2
- package/package.json +1 -1
- package/skills/appstore-precheck/SKILL.md +1 -1
- package/skills/appstore-precheck/references/methodology.md +13 -0
- package/skills/appstore-precheck/scripts/sarif.sh +40 -0
- package/skills/appstore-precheck/scripts/scan.sh +6 -4
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,38 @@ All notable changes to this project are documented here. Versioning follows
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [1.10.0] - 2026-07-02
|
|
9
|
+
|
|
10
|
+
Roadmap #4: **SARIF output + opt-in GitHub PR annotations** — read-only, no auto-fix.
|
|
11
|
+
|
|
12
|
+
The scanner can now emit its deterministic findings as **SARIF 2.1.0**
|
|
13
|
+
(`scan.sh --format sarif`, also `npx appstore-precheck --format sarif`), built from
|
|
14
|
+
the same structured findings as `--format json` with pure `jq` (no new dependency).
|
|
15
|
+
`results[]` contains the non-suppressed FAIL and WARN findings (FAIL → `error`,
|
|
16
|
+
WARN → `warning`); PASS and suppressed findings are excluded; findings that carry a
|
|
17
|
+
`file`/`line` become SARIF `physicalLocation`s so GitHub can anchor annotations. Only
|
|
18
|
+
the deterministic scan findings are included — the agent-mode Pierre deep-review
|
|
19
|
+
findings are not (SARIF is a deterministic CI artifact).
|
|
20
|
+
|
|
21
|
+
The GitHub Action gains two **opt-in** inputs (both default `false`, so existing
|
|
22
|
+
usage is unchanged): `sarif` uploads the SARIF to code-scanning via `upload-sarif`
|
|
23
|
+
(PR annotations + Security tab; needs `permissions: security-events: write`), and
|
|
24
|
+
`annotations` emits inline `::error`/`::warning` PR annotations. Both run even when
|
|
25
|
+
the scan verdict fails, and neither modifies the project.
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
- **`skills/appstore-precheck/scripts/sarif.sh`** — `render_sarif()`, a SARIF 2.1.0
|
|
29
|
+
writer over the findings buffer (pure `jq`).
|
|
30
|
+
- **`scan.sh --format sarif`** (and `bin/cli.js --format text|json|sarif`).
|
|
31
|
+
- **`action.yml`** opt-in inputs `sarif` and `annotations` (default off): SARIF
|
|
32
|
+
upload via `github/codeql-action/upload-sarif@v3` and inline PR annotations.
|
|
33
|
+
- Tests: `tests/test-sarif.sh` and `tests/test-action-sarif.sh`.
|
|
34
|
+
|
|
35
|
+
### Notes
|
|
36
|
+
- Read-only preserved: the scanner writes only to stdout; the Action redirects SARIF
|
|
37
|
+
to a CI workspace file. No auto-fix. Default `--format text`/`--format json` output
|
|
38
|
+
and the Action's default behavior are byte-identical to before.
|
|
39
|
+
|
|
8
40
|
## [1.9.0] - 2026-07-02
|
|
9
41
|
|
|
10
42
|
Smarter analysis (roadmap #2a): **screenshot vision**, in two layers.
|
package/README.md
CHANGED
|
@@ -264,6 +264,29 @@ job on a RED verdict; set `fail-on: YELLOW` to be stricter:
|
|
|
264
264
|
Both inputs are optional: with none set, the action scans the repo root and fails the job only on
|
|
265
265
|
a RED verdict. No App Store Connect credentials are needed; the action runs the static scan only.
|
|
266
266
|
|
|
267
|
+
### SARIF & PR annotations (opt-in)
|
|
268
|
+
|
|
269
|
+
The Action can surface findings as GitHub code-scanning results and inline PR annotations. Both are
|
|
270
|
+
off by default; enable either or both:
|
|
271
|
+
|
|
272
|
+
```yaml
|
|
273
|
+
permissions:
|
|
274
|
+
contents: read
|
|
275
|
+
security-events: write # required for SARIF upload
|
|
276
|
+
jobs:
|
|
277
|
+
precheck:
|
|
278
|
+
runs-on: ubuntu-latest
|
|
279
|
+
steps:
|
|
280
|
+
- uses: actions/checkout@v4
|
|
281
|
+
- uses: berkayturk/appstore-precheck@v1
|
|
282
|
+
with:
|
|
283
|
+
sarif: true # upload SARIF to the Security tab + PR annotations
|
|
284
|
+
annotations: true # also emit inline ::error/::warning annotations
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Locally / via npx: `npx appstore-precheck --format sarif > results.sarif`. The scan stays read-only;
|
|
288
|
+
nothing is auto-fixed.
|
|
289
|
+
|
|
267
290
|
## How it works
|
|
268
291
|
|
|
269
292
|
| Phase | Step |
|
package/bin/cli.js
CHANGED
|
@@ -36,6 +36,7 @@ function printHelp() {
|
|
|
36
36
|
`Options:\n` +
|
|
37
37
|
` --dir <path> Directory to scan (default: current directory)\n` +
|
|
38
38
|
` --fail-on <level> Exit non-zero at RED (default) or YELLOW\n` +
|
|
39
|
+
` --format <fmt> Output format: text (default), json, or sarif\n` +
|
|
39
40
|
` -v, --version Print the version and exit\n` +
|
|
40
41
|
` -h, --help Show this help and exit\n` +
|
|
41
42
|
`\n` +
|
|
@@ -53,7 +54,7 @@ function fail(message, code) {
|
|
|
53
54
|
}
|
|
54
55
|
|
|
55
56
|
function parseArgs(argv) {
|
|
56
|
-
const opts = { dir: process.cwd(), failOn: 'RED' };
|
|
57
|
+
const opts = { dir: process.cwd(), failOn: 'RED', format: 'text' };
|
|
57
58
|
for (let i = 0; i < argv.length; i++) {
|
|
58
59
|
const a = argv[i];
|
|
59
60
|
if (a === '-h' || a === '--help') { printHelp(); process.exit(0); }
|
|
@@ -69,6 +70,12 @@ function parseArgs(argv) {
|
|
|
69
70
|
opts.failOn = v;
|
|
70
71
|
continue;
|
|
71
72
|
}
|
|
73
|
+
if (a === '--format') {
|
|
74
|
+
const v = (argv[++i] || '').toLowerCase();
|
|
75
|
+
if (v !== 'text' && v !== 'json' && v !== 'sarif') fail('--format must be text, json, or sarif', 64);
|
|
76
|
+
opts.format = v;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
72
79
|
fail(`unknown option: ${a} (try --help)`, 64);
|
|
73
80
|
}
|
|
74
81
|
return opts;
|
|
@@ -84,7 +91,8 @@ function main() {
|
|
|
84
91
|
fail(`not a directory: ${opts.dir}`, 64);
|
|
85
92
|
}
|
|
86
93
|
|
|
87
|
-
const
|
|
94
|
+
const scanArgs = opts.format === 'text' ? [SCAN] : [SCAN, '--format', opts.format];
|
|
95
|
+
const scan = spawnSync('bash', scanArgs, {
|
|
88
96
|
cwd: opts.dir,
|
|
89
97
|
encoding: 'utf8',
|
|
90
98
|
maxBuffer: 32 * 1024 * 1024,
|
|
@@ -97,6 +105,10 @@ function main() {
|
|
|
97
105
|
const scanOut = scan.stdout || '';
|
|
98
106
|
process.stdout.write(scanOut);
|
|
99
107
|
|
|
108
|
+
if (opts.format !== 'text') {
|
|
109
|
+
process.exit(scan.status === 0 ? 0 : (scan.status || 0));
|
|
110
|
+
}
|
|
111
|
+
|
|
100
112
|
const verdict = spawnSync('bash', [VERDICT], { input: scanOut, encoding: 'utf8' });
|
|
101
113
|
if (verdict.error) fail(`failed to compute the verdict: ${verdict.error.message}`, 70);
|
|
102
114
|
const summary = verdict.stdout || '';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "appstore-precheck",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.0",
|
|
4
4
|
"description": "Read-only iOS App Store pre-submission check, packaged as a portable Agent Skill with native Claude Code, Cursor, and Codex plugins, plus an npx CLI.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Berkay Turk (https://github.com/berkayturk)",
|
|
@@ -11,6 +11,7 @@ whole file to run the skill.
|
|
|
11
11
|
- [Phase 4: Pierre deep review (28 checks)](#phase-4-pierre-deep-review-28-semantic-checks)
|
|
12
12
|
- [Auto-detection rules](#auto-detection-rules)
|
|
13
13
|
- [Verdict thresholds](#verdict-thresholds)
|
|
14
|
+
- [SARIF output](#sarif-output---format-sarif)
|
|
14
15
|
- [Pre-submit manual checklist](#pre-submit-manual-checklist)
|
|
15
16
|
|
|
16
17
|
---
|
|
@@ -221,6 +222,18 @@ blocks, but it can be the fifth WARN that tips GREEN into YELLOW.
|
|
|
221
222
|
|
|
222
223
|
---
|
|
223
224
|
|
|
225
|
+
## SARIF output (`--format sarif`)
|
|
226
|
+
|
|
227
|
+
`scan.sh --format sarif` emits a SARIF 2.1.0 log built from the same structured findings as
|
|
228
|
+
`--format json` (pure `jq`, no new dependency). `results[]` contains the non-suppressed FAIL and
|
|
229
|
+
WARN findings (FAIL → `error`, WARN → `warning`); PASS and suppressed findings are excluded. Findings
|
|
230
|
+
that carry a `file`/`line` become SARIF `physicalLocation`s so GitHub can anchor PR annotations. Only
|
|
231
|
+
the deterministic scan findings are included — the agent-mode Pierre deep-review findings are not
|
|
232
|
+
(SARIF is a deterministic CI artifact). The GitHub Action uploads this via `upload-sarif` and/or
|
|
233
|
+
emits inline `::error`/`::warning` annotations, both opt-in.
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
224
237
|
## Pre-submit manual checklist
|
|
225
238
|
|
|
226
239
|
Things the scanner cannot verify; confirm by hand before you submit:
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# sarif.sh — SARIF 2.1.0 output layer for scan.sh. Sourced; pure jq over the
|
|
3
|
+
# findings buffer. No side effects, no output on load. bash 3.2 compatible.
|
|
4
|
+
|
|
5
|
+
: "${PRECHECK_VERSION:=dev}"
|
|
6
|
+
|
|
7
|
+
# render_sarif -> prints a SARIF 2.1.0 log built from the FINDINGS_TMP buffer.
|
|
8
|
+
# results[] = non-suppressed FAIL/WARN only (PASS + suppressed excluded);
|
|
9
|
+
# level: FAIL->error, WARN->warning. rules[] = distinct non-empty ruleIds present.
|
|
10
|
+
render_sarif() {
|
|
11
|
+
local buf="${FINDINGS_TMP:-/dev/null}"
|
|
12
|
+
local uri="https://github.com/berkayturk/appstore-precheck"
|
|
13
|
+
local help="https://github.com/berkayturk/appstore-precheck/blob/main/skills/appstore-precheck/references/methodology.md"
|
|
14
|
+
if [[ ! -s "$buf" ]]; then
|
|
15
|
+
jq -nc --arg v "$PRECHECK_VERSION" --arg u "$uri" '
|
|
16
|
+
{"$schema":"https://json.schemastore.org/sarif-2.1.0.json", "version":"2.1.0",
|
|
17
|
+
"runs":[{"tool":{"driver":{"name":"appstore-precheck","version":$v,"informationUri":$u,"rules":[]}},"results":[]}]}'
|
|
18
|
+
return 0
|
|
19
|
+
fi
|
|
20
|
+
jq -s --arg v "$PRECHECK_VERSION" --arg u "$uri" --arg help "$help" '
|
|
21
|
+
(map(select(.suppressed==false and (.severity=="FAIL" or .severity=="WARN")))) as $issues
|
|
22
|
+
| ($issues
|
|
23
|
+
| map({id:.rule_id, text:.guideline})
|
|
24
|
+
| map(select(.id != "" and .id != null))
|
|
25
|
+
| unique_by(.id)
|
|
26
|
+
| map({id:.id, name:.id, shortDescription:{text:.text}, helpUri:$help})) as $rules
|
|
27
|
+
| ($issues | map(
|
|
28
|
+
( (if (.rule_id // "") == "" then {} else {ruleId:.rule_id} end)
|
|
29
|
+
+ {level:(if .severity=="FAIL" then "error" else "warning" end),
|
|
30
|
+
message:{text:.message}}
|
|
31
|
+
+ (if .file != null
|
|
32
|
+
then {locations:[{physicalLocation:(
|
|
33
|
+
{artifactLocation:{uri:.file}}
|
|
34
|
+
+ (if .line != null then {region:{startLine:.line}} else {} end))}]}
|
|
35
|
+
else {locations:[]} end) )
|
|
36
|
+
)) as $results
|
|
37
|
+
| {"$schema":"https://json.schemastore.org/sarif-2.1.0.json", "version":"2.1.0",
|
|
38
|
+
"runs":[{"tool":{"driver":{"name":"appstore-precheck","version":$v,"informationUri":$u,"rules":$rules}},"results":$results}]}
|
|
39
|
+
' "$buf"
|
|
40
|
+
}
|
|
@@ -16,6 +16,7 @@ source "$(dirname "${BASH_SOURCE[0]}")/findings.sh"
|
|
|
16
16
|
source "$(dirname "${BASH_SOURCE[0]}")/suppress.sh"
|
|
17
17
|
source "$(dirname "${BASH_SOURCE[0]}")/project-model.sh"
|
|
18
18
|
source "$(dirname "${BASH_SOURCE[0]}")/image-dims.sh"
|
|
19
|
+
source "$(dirname "${BASH_SOURCE[0]}")/sarif.sh"
|
|
19
20
|
FINDINGS_TMP="$(mktemp)"; export FINDINGS_TMP
|
|
20
21
|
trap 'rm -f "$FINDINGS_TMP"' EXIT
|
|
21
22
|
FORMAT="text"
|
|
@@ -28,13 +29,13 @@ PRECHECK_VERSION="$(grep -m1 -E '^[[:space:]]*version:' "$(dirname "${BASH_SOURC
|
|
|
28
29
|
while [[ $# -gt 0 ]]; do
|
|
29
30
|
case "$1" in
|
|
30
31
|
--format)
|
|
31
|
-
if [[ $# -lt 2 ]]; then echo "scan.sh: --format needs a value (text|json)" >&2; exit 64; fi
|
|
32
|
+
if [[ $# -lt 2 ]]; then echo "scan.sh: --format needs a value (text|json|sarif)" >&2; exit 64; fi
|
|
32
33
|
FORMAT="$2"; shift 2 ;;
|
|
33
34
|
--format=*) FORMAT="${1#*=}"; shift ;;
|
|
34
35
|
*) shift ;;
|
|
35
36
|
esac
|
|
36
37
|
done
|
|
37
|
-
[[ "$FORMAT" == json || "$FORMAT" == text ]] || { echo "scan.sh: --format must be text|json" >&2; exit 64; }
|
|
38
|
+
[[ "$FORMAT" == json || "$FORMAT" == text || "$FORMAT" == sarif ]] || { echo "scan.sh: --format must be text|json|sarif" >&2; exit 64; }
|
|
38
39
|
|
|
39
40
|
CONFIG="${APPSTORE_PRECHECK_CONFIG:-.appstore-precheck.json}"
|
|
40
41
|
have_jq() { command -v jq >/dev/null 2>&1; }
|
|
@@ -202,7 +203,7 @@ SUB_KEY="$(cfg '.disclosureKeys.subscription' 'subscription_disclosure')"
|
|
|
202
203
|
TRIAL_KEY="$(cfg '.disclosureKeys.trial' 'subscription_trial_disclosure')"
|
|
203
204
|
CHECK_FAMILY="$(cfg_bool '.optionalChecks.familyControls')"
|
|
204
205
|
|
|
205
|
-
if [[ "$FORMAT"
|
|
206
|
+
if [[ "$FORMAT" != text ]]; then exec 4>&1 1>/dev/null; fi
|
|
206
207
|
|
|
207
208
|
echo "PASS: layout — ios='${IOS_DIR:-?}' metadata='${META_DIR:-?}' xcstrings='${XCSTRINGS:-?}' locales=${#LOCALES[@]}"
|
|
208
209
|
|
|
@@ -1011,4 +1012,5 @@ echo "---END-OF-SCAN---"
|
|
|
1011
1012
|
if [[ "$FORMAT" == text && "${_SUPPRESSED_COUNT:-0}" -gt 0 ]]; then
|
|
1012
1013
|
printf '(%s finding(s) suppressed via .precheck-ignore)\n' "$_SUPPRESSED_COUNT"
|
|
1013
1014
|
fi
|
|
1014
|
-
if [[ "$FORMAT" == json ]]; then exec 1>&4 4>&-; render_json;
|
|
1015
|
+
if [[ "$FORMAT" == json ]]; then exec 1>&4 4>&-; render_json;
|
|
1016
|
+
elif [[ "$FORMAT" == sarif ]]; then exec 1>&4 4>&-; render_sarif; fi
|