From 66181d1394cb701325560e83bdeede353afc8571 Mon Sep 17 00:00:00 2001 From: npeter83 Date: Sat, 17 Oct 2026 15:31:34 +0200 Subject: [PATCH] =?UTF-8?q?chore(gate):=20shellcheck=20joins=20the=20gate?= =?UTF-8?q?=20=E2=80=94=20every=20tracked=20shell=20script,=20the=20host?= =?UTF-8?q?=20binary=20held=20to=20a=20pin?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CLAUDE.md | 2 +- docs/development.md | 7 +++++++ frontend/src/lib/gates.snapshot.json | 13 ++++++++++++- scripts/check.sh | 18 +++++++++++++++++- 4 files changed, 37 insertions(+), 3 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 68667f25..2acf4a0d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -12,7 +12,7 @@ router-free navigation provider. ## The gate — never merge or ship red -The local gate is **ruff (`check` + `format --check`) + basedpyright (backend) + the lock check + typecheck (3 tsc projects) + +The local gate is **ruff (`check` + `format --check`) + shellcheck + basedpyright (backend) + the lock check + typecheck (3 tsc projects) + eslint + prettier --check + knip + vitest + vite build + pytest + pip-audit + npm audit**. It runs before every `release` merges and on every `publish`, which **refuses to build on failure — there is deliberately no `--skip` flag**; a release also requires the Playwright suite, and the image must pass a Trivy scan before it is pushed. Run it and get it diff --git a/docs/development.md b/docs/development.md index 13dd22f4..8bdd79b2 100644 --- a/docs/development.md +++ b/docs/development.md @@ -87,6 +87,13 @@ docker compose -f backend/docker-compose.test.yml run --rm --no-deps test \ pip-audit -r requirements.lock --no-deps --disable-pip ``` +**Shell scripts** + +```bash +# every tracked *.sh at shellcheck's default bar (info included); the version is pinned in scripts/check.sh +git ls-files -z -- '*.sh' | xargs -0 shellcheck +``` + A missing database or ffmpeg is a hard error, not a skip — so the gate can't print green while the DB-backed tests were quietly not run (`SIFTLODE_TEST_REQUIRE_DB=0` is the explicit "pure-logic only" run for a machine without Postgres). Tests that need a database take the `db` fixture diff --git a/frontend/src/lib/gates.snapshot.json b/frontend/src/lib/gates.snapshot.json index d6230d64..f6a147c5 100644 --- a/frontend/src/lib/gates.snapshot.json +++ b/frontend/src/lib/gates.snapshot.json @@ -1377,6 +1377,16 @@ "}", "ruff_check() { ruff_pinned && python -m ruff check --config backend/ruff.toml backend scripts; }", "ruff_format() { ruff_pinned && python -m ruff format --check --config backend/ruff.toml backend scripts; }", + "SHELLCHECK_PIN=0.11.0", + "shellcheck_lane() {", + " local host", + " host=\"$(shellcheck --version 2>/dev/null | sed -n 's/^version: //p')\"", + " if [ \"$host\" != \"$SHELLCHECK_PIN\" ]; then", + " echo \"shellcheck ${host:-missing} on the host, $SHELLCHECK_PIN pinned — https://github.com/koalaman/shellcheck/releases\" >&2", + " return 1", + " fi", + " git ls-files -z -- '*.sh' | xargs -0 shellcheck", + "}", "basedpyright() {", " local before", " before=\"$(git hash-object backend/.basedpyright/baseline.json)\"", @@ -1394,6 +1404,7 @@ "gate_t0=\"$(ms)\"", "lane \"ruff\" ruff_check", "lane \"ruff format --check\" ruff_format", + "lane \"shellcheck (every shell script)\" shellcheck_lane", "lane \"basedpyright (new errors only)\" basedpyright", "lane \"lock check (frontend/node_modules == package-lock.json)\" node scripts/check-node-modules.mjs", "lane \"tsc (src + configs + e2e)\" frontend npm run --silent typecheck", @@ -1405,7 +1416,7 @@ " local rc=0", " docker compose -f \"$TESTCOMPOSE\" up --build --abort-on-container-exit --exit-code-from test || rc=1", " docker compose -f \"$TESTCOMPOSE\" down -v >/dev/null 2>&1", - " return $rc", + " return \"$rc\"", "}", "pip_audit() {", " docker compose -f \"$TESTCOMPOSE\" run --rm --no-deps test \\", diff --git a/scripts/check.sh b/scripts/check.sh index 626255be..4a64f0d9 100644 --- a/scripts/check.sh +++ b/scripts/check.sh @@ -20,6 +20,7 @@ # stops at the first failure hides the second until the first is fixed. The backend lanes run in the # test image (backend/Dockerfile.test): requirements.lock will not install on Windows (uvloop), and a # type checker or auditor without the real packages reports nonsense. +# shellcheck disable=SC2329 # the lane functions below are called by name, through lane() set -u cd "$(dirname "$0")/.." || exit 2 mode=all @@ -121,6 +122,20 @@ ruff_pinned() { ruff_check() { ruff_pinned && python -m ruff check --config backend/ruff.toml backend scripts; } ruff_format() { ruff_pinned && python -m ruff format --check --config backend/ruff.toml backend scripts; } +# Every tracked shell script (the gate itself, backup/restore, the installer, the image's entrypoint) at +# the linter's full default bar, info included. Host shellcheck, held to this pin like ruff: a newer one +# adds checks, and a gate must not change under a green commit. +SHELLCHECK_PIN=0.11.0 +shellcheck_lane() { + local host + host="$(shellcheck --version 2>/dev/null | sed -n 's/^version: //p')" + if [ "$host" != "$SHELLCHECK_PIN" ]; then + echo "shellcheck ${host:-missing} on the host, $SHELLCHECK_PIN pinned — https://github.com/koalaman/shellcheck/releases" >&2 + return 1 + fi + git ls-files -z -- '*.sh' | xargs -0 shellcheck +} + # New errors only: the pre-existing ones are in backend/.basedpyright/baseline.json. basedpyright # TIGHTENS that file by itself when errors go, and the tree is bind-mounted read-write, so a green run # can leave it rewritten: that fails here until it is committed, or the next run's stack reads stale. @@ -143,6 +158,7 @@ frontend() { (cd frontend && "$@"); } gate_t0="$(ms)" lane "ruff" ruff_check lane "ruff format --check" ruff_format +lane "shellcheck (every shell script)" shellcheck_lane lane "basedpyright (new errors only)" basedpyright # every frontend lane judges the HOST node_modules: this proves it is the tree the lock pins lane "lock check (frontend/node_modules == package-lock.json)" node scripts/check-node-modules.mjs @@ -158,7 +174,7 @@ pytest_lane() { # the full suite against a throwaway Postgres; --pull keeps the local rc=0 docker compose -f "$TESTCOMPOSE" up --build --abort-on-container-exit --exit-code-from test || rc=1 docker compose -f "$TESTCOMPOSE" down -v >/dev/null 2>&1 - return $rc + return "$rc" } # Every pinned backend package against the advisory databases, at any severity — the lock is a full # `pip freeze`, so it is audited as written, without a resolver. An advisory with no fix is answered