From e45592ae0048cce1147860a224db719b717906e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Thu, 3 Sep 2026 18:44:19 +0000 Subject: [PATCH] :wrench: Unify lefthook and package.json scripts via LEFTHOOK_FILES env var lefthook and package.json had parallel command definitions for the same tools (oxlint, oxfmt, cspell). Consolidate by making lefthook call the npm scripts, with staged files passed via the LEFTHOOK_FILES env var. The scripts use ${LEFTHOOK_FILES:-} so they default to the full project when invoked manually and to the staged-files list when invoked from lefthook. Changes: - package.json#check:oxlint: `oxlint ${LEFTHOOK_FILES:-src}` (lints src/ manually; staged files from lefthook) - package.json#check:oxfmt: `oxfmt --check ${LEFTHOOK_FILES:-src}` - package.json#check:cspell: `cspell lint ${LEFTHOOK_FILES:-.}` (walks CWD manually; staged files from lefthook) - package.json#check:tsc, check:package: unchanged (no file args) - lefthook.yml: file-filtered hooks (oxlint, oxfmt, cspell) now use `sh -c 'LEFTHOOK_FILES="$0" npm run check:*' {staged_files}` to inject the staged-files list into the env. sort-package-json and typecheck call npm scripts directly (no file args). - project-specs.md: document the unification pattern Why sh -c + env var instead of the simpler 'npm run ... -- {staged_files}': 'oxlint src file.ts' lints the whole src/ tree *plus* file.ts (oxlint doesn't dedupe paths). Setting LEFTHOOK_FILES as an env var (which lefthook's 'env:' config does not template) requires the sh -c wrapper, but it gives the right semantics: when the var is set, only the explicit files are checked; when unset, the default (src/ or .) is used. Verified: - 'npm run check:oxlint' (no env) lints all of src/ - 'LEFTHOOK_FILES=src/match.ts npm run check:oxlint' lints only that file - 'npx lefthook run pre-commit' with a staged TS file: cspell output shows '1/1 src/match.ts' (only staged file, not whole project) - All 5 hooks pass on a real staged change - 'lefthook validate' reports 'All good' - 'npm run check' exits 0 --- lefthook.yml | 10 +++++----- package.json | 8 ++++---- project-specs.md | 41 +++++++++++++++++++++++++++++------------ 3 files changed, 38 insertions(+), 21 deletions(-) diff --git a/lefthook.yml b/lefthook.yml index 66f0edf..ce67689 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -5,13 +5,13 @@ pre-commit: commands: oxlint: glob: "*.{ts,tsx,js,jsx,mjs,cjs}" - run: npx oxlint {staged_files} + run: sh -c 'LEFTHOOK_FILES="$0" npm run check:oxlint' {staged_files} oxfmt: glob: "*.{ts,tsx,js,jsx,mjs,cjs}" - run: npx oxfmt --check {staged_files} + run: sh -c 'LEFTHOOK_FILES="$0" npm run check:oxfmt' {staged_files} cspell: - run: npx cspell {staged_files} + run: sh -c 'LEFTHOOK_FILES="$0" npm run check:cspell' {staged_files} sort-package-json: - run: npx sort-package-json --check + run: npm run check:package typecheck: - run: npx tsc --noEmit \ No newline at end of file + run: npm run check:tsc \ No newline at end of file diff --git a/package.json b/package.json index 14d44da..fb3eeb2 100644 --- a/package.json +++ b/package.json @@ -33,10 +33,10 @@ "scripts": { "build": "tsc -p tsconfig.build.json", "check": "npm run check:oxlint && npm run check:oxfmt && npm run check:tsc && npm run check:cspell && npm run check:package && npm run check:outdated", - "check:cspell": "cspell .", + "check:cspell": "cspell lint ${LEFTHOOK_FILES:-.}", "check:outdated": "check-outdated --ignore-pre-releases --ignore-packages @oxfmt/binding-darwin-arm64,@oxfmt/binding-darwin-x64,@oxfmt/binding-linux-arm64-gnu,@oxfmt/binding-linux-arm64-musl,@oxfmt/binding-linux-x64-gnu,@oxfmt/binding-linux-x64-musl,@oxfmt/binding-win32-x64-msvc,@oxlint/binding-darwin-arm64,@oxlint/binding-darwin-x64,@oxlint/binding-linux-arm64-gnu,@oxlint/binding-linux-arm64-musl,@oxlint/binding-linux-x64-gnu,@oxlint/binding-linux-x64-musl,@oxlint/binding-win32-x64-msvc", - "check:oxfmt": "oxfmt --check src", - "check:oxlint": "oxlint src", + "check:oxfmt": "oxfmt --check ${LEFTHOOK_FILES:-src}", + "check:oxlint": "oxlint ${LEFTHOOK_FILES:-src}", "check:package": "sort-package-json --check", "check:tsc": "tsc --noEmit", "clean": "node -e \"fs.rmSync('dist', { recursive: true, force: true })\"", @@ -84,4 +84,4 @@ "publishConfig": { "access": "public" } -} +} \ No newline at end of file diff --git a/project-specs.md b/project-specs.md index 7142c24..b4b41e7 100644 --- a/project-specs.md +++ b/project-specs.md @@ -38,15 +38,24 @@ typescript-lib-starter-tiny => https://github.com/tmueller/typescript-lib-starte `oxfmtrc`, `oxlintrc`, `sortpackagerc`, `EDITMSG`, `typescriptteam`, `gitmoji`, `dbaeumer`, `msvc`) and a few library names (`tslib`, `typefest`, `lefthook`). -- **Lefthook**: Pre-commit checks (run in parallel, lefthook v2 schema): - - `min_version: 2.0.0` - - `pre-commit.commands.oxlint` — `npx oxlint {staged_files}` on - staged `*.{ts,tsx,js,jsx,mjs,cjs}` files - - `pre-commit.commands.oxfmt` — `npx oxfmt --check {staged_files}` - on staged `*.{ts,tsx,js,jsx,mjs,cjs}` files - - `pre-commit.commands.cspell` — `npx cspell {staged_files}` - - `pre-commit.commands.sort-package-json` — `npx sort-package-json --check` - - `pre-commit.commands.typecheck` — `npx tsc --noEmit` +- **Lefthook**: Pre-commit checks (run in parallel, lefthook v2 schema). + Single source of truth for the underlying commands is in + `package.json#scripts`; the lefthook config only describes *what to + run on which files*. Hooks that should run on staged files + (`oxlint`, `oxfmt`, `cspell`) set the `LEFTHOOK_FILES` env var to + the staged-files list via a `sh -c` wrapper, and the npm script + uses `${LEFTHOOK_FILES:-}` to default to the full project + when invoked manually: + - `pre-commit.commands.oxlint` (glob `*.{ts,tsx,js,jsx,mjs,cjs}`): + `sh -c 'LEFTHOOK_FILES="$0" npm run check:oxlint' {staged_files}` + - `pre-commit.commands.oxfmt` (glob `*.{ts,tsx,js,jsx,mjs,cjs}`): + `sh -c 'LEFTHOOK_FILES="$0" npm run check:oxfmt' {staged_files}` + - `pre-commit.commands.cspell`: + `sh -c 'LEFTHOOK_FILES="$0" npm run check:cspell' {staged_files}` + - `pre-commit.commands.sort-package-json`: + `npm run check:package` (no file args needed) + - `pre-commit.commands.typecheck`: + `npm run check:tsc` (no file args needed) `outdated` is intentionally NOT a pre-commit check (it can flag upstream patch releases that aren't actionable locally); it runs in CI and via the explicit `lefthook run outdated` command. @@ -163,10 +172,18 @@ typescript-lib-starter-tiny => https://github.com/tmueller/typescript-lib-starte - `check`: Run all checks in order — `check:oxlint`, `check:oxfmt`, `check:tsc`, `check:cspell`, `check:package`, `check:outdated`. -- `check:oxlint`: `oxlint src`. -- `check:oxfmt`: `oxfmt --check src`. +- `check:oxlint`: `oxlint ${LEFTHOOK_FILES:-src}` — lints `src/` + by default; when invoked from the lefthook pre-commit hook with + `LEFTHOOK_FILES` set to the staged-files list, lints only those + files. This is the single source of truth for the oxlint command + and is shared between the manual `npm run check` and the pre-commit + hook. +- `check:oxfmt`: `oxfmt --check ${LEFTHOOK_FILES:-src}` — same + pattern as `check:oxlint` (whole `src/` by default, staged files + from lefthook). - `check:tsc`: `tsc --noEmit`. -- `check:cspell`: `cspell .`. +- `check:cspell`: `cspell lint ${LEFTHOOK_FILES:-.}` — walks the + project root by default; from lefthook, only the staged files. - `check:package`: `sort-package-json --check`. - `check:outdated`: `check-outdated --ignore-pre-releases --ignore-packages @oxfmt/binding-*,@oxlint/binding-*`. The oxc native bindings are declared as `optionalDependencies` so the correct one is selected per platform automatically; the `*`-platform bindings show as "not installed" on the current platform and are explicitly ignored here.