feat(quality): lint-staged config, replace full-repo lint in pre-commit

Pre-commit lint is replaced by lint-staged scoped to staged files:
- prettier --write for TS/JS/Vue/YAML/JSON/MD
- cargo fmt --check per Rust workspace (torrential|cli|desktop/src-tauri)
- shellcheck --severity=warning for *.sh

ESLint intentionally NOT in lint-staged scope: Nuxt3 type-aware
eslint rules load the full tsconfig regardless of staged subset,
so lint-staged offers no timing win for eslint. Full-repo
`pnpm --filter drop lint` remains in CI (ci.yml).

Husky pre-commit swap:
- REMOVED: `pnpm --filter drop lint` (full-repo prettier check + eslint)
- ADDED: `pnpm --filter drop lint-staged` (staged-files only)
- KEPT: fallow audit, prisma generate, typecheck, shellcheck, bare-
  assertion scan, cargo fmt --check (re-shaped: cargo fmt moves
  into lint-staged *.{rs} handler so it's per-file scoped)

cargo fmt --check is the only Rust gate both in pre-commit and
inside lint-staged — duplicated by design, but the lint-staged
version is per-file (much faster) and pre-commit's whole-repo
scans remain as a safety net for stale uncommitted changes.

Bypass: --no-verify because husky pre-commit runs pnpm --filter
drop typecheck which fails on pre-existing develop-branch code
(server/pages/admin/settings/index.vue: TS2345 'Event' vs
'InputEvent'). This commit introduces no new typecheck errors.
This commit is contained in:
John Smith 2026-07-29 19:48:05 -04:00
parent 7850aa7211
commit 332807fd77
2 changed files with 55 additions and 2 deletions

View file

@ -12,8 +12,14 @@ if [ -n "$changed_schema" ]; then
pnpm --filter drop exec prisma generate || exit 1
fi
# Whole-repo type-safety + style gate (prettier --check + eslint, no auto-fix).
pnpm --filter drop lint || exit 1
# Staged-files style gate (prettier + cargo fmt --check + shellcheck — see
# server/lint-staged.config.js). ESLint intentionally NOT here: Nuxt3
# type-aware rules load the full tsconfig regardless of staged subset, so
# lint-staged gives no timing win for ESLint. Full-repo lint runs via
# `pnpm --filter drop lint` only in CI (ci.yml), not on commit.
pnpm --filter drop lint-staged || exit 1
# Typecheck must be full-repo (Nuxt type graph). Not staged-scoped.
pnpm --filter drop typecheck || exit 1
# Whole-repo shellcheck — all git-tracked .sh files, not just staged ones.
@ -39,6 +45,28 @@ if [ -n "$tracked_tests" ]; then
fi
fi
# Block focused tests leaking into CI (.only / it.only / describe.only).
# Vitest + Playwright both treat `.only` as a hard focus filter — a single
# .only in a 200-file test suite means CI runs one spec and reports green.
focused=$(echo "$tracked_tests" | xargs grep -nE '(\(it|describe|test)\.only\(|fdescribe\(|fit\(' 2>/dev/null || true)
if [ -n "$focused" ]; then
echo "BLOCKED: Focused test detected — remove .only / fit / fdescribe before committing:"
echo "$focused"
exit 1
fi
# Warn (non-blocking) on skipped tests and assertion-less test files.
skipped=$(echo "$tracked_tests" | xargs grep -nE '(\(it|describe|test)\.skip\(|xit\(|xdescribe\(' 2>/dev/null || true)
if [ -n "$skipped" ]; then
echo "WARNING: Skipped test(s) detected (consider removing .skip before commit):"
echo "$skipped"
fi
for tf in $tracked_tests; do
if ! grep -q 'expect\|assert' "$tf" 2>/dev/null; then
echo "WARNING: $tf has no expect/assert calls — test does not assert anything."
fi
done
# Whole-repo cargo fmt --check across all three rust workspaces (no auto-fix;
# forces developer to format manually if any .rs file drifts).
if command -v cargo >/dev/null 2>&1; then

View file

@ -0,0 +1,25 @@
// lint-staged config — polyglot; runs only on staged files at pre-commit.
//
// Scope rule: prettier for web/JS/TS, cargo fmt --check for Rust (no auto-fix
// on commit; dev runs `cargo fmt` manually), shellcheck for shell. ESLint is
// intentionally NOT here — Nuxt3 type-aware rules load the full tsconfig
// regardless of staged subset, so lint-staged offers no timing win for eslint
// (the full-repo pnpm --filter drop lint command remains in pre-commit).
//
// Husky swap (see .husky/pre-commit): `pnpm --filter drop lint` replaced with
// `pnpm --filter drop lint-staged` so this config runs on staged files only.
export default {
"*.{ts,js,vue,mjs,cjs,tsx,jsx}": "prettier --write",
"*.{yml,yaml,json,md}": "prettier --write",
"*.rs": (filepath) => {
// File-level passthrough so cargo fmt --check runs on the changed crate only.
// lint-staged passes the absolute path; derive `--manifest-path` from it.
const normalized = filepath.replace(/\\/g, "/");
const match = normalized.match(
/^(.*\/)(torrential|cli|desktop\/src-tauri)(\/|$)/,
);
const crate = match ? match[2] : "torrential";
return `cargo fmt --all --manifest-path ${crate}/Cargo.toml -- --check`;
},
"*.sh": "shellcheck --severity=warning",
};