diff --git a/.husky/pre-commit b/.husky/pre-commit index e6a4980b..127cb2d5 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -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 diff --git a/server/lint-staged.config.js b/server/lint-staged.config.js new file mode 100644 index 00000000..66e9ebaa --- /dev/null +++ b/server/lint-staged.config.js @@ -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", +};