diff --git a/README.md b/README.md index 9f63a60..092238c 100644 --- a/README.md +++ b/README.md @@ -187,7 +187,8 @@ on the repo. [`lefthook/site.yml`](lefthook/site.yml) is the shared hook set for the site stack, consumed as a lefthook remote. It calls the site's own `package.json` -scripts (`bun run --if-present`, so a missing script is skipped): +scripts (` run --if-present`, so a missing script is skipped; `` is pnpm +when the repo has a `pnpm-lock.yaml`, bun otherwise): - `pre-commit`: dependency guard, `format` (formatted files are re-staged), `lint:actions`, `check`, `test:unit`, and `check:licenses` / `audit` when the diff --git a/lefthook/site.yml b/lefthook/site.yml index b1a9c94..47dc8e6 100644 --- a/lefthook/site.yml +++ b/lefthook/site.yml @@ -1,11 +1,12 @@ -# Shared git hooks for the Astro/bun site stack. Sites pull this in as a -# lefthook remote and call their own package.json scripts, so a script a site -# does not define is skipped (`bun run --if-present`). +# Shared git hooks for the Astro site stack (bun, or pnpm when the repo has a +# pnpm-lock.yaml). Sites pull this in as a lefthook remote and call their own +# package.json scripts, so a script a site does not define is skipped +# (` run --if-present`). # # # lefthook.yml in the site # remotes: # - git_url: https://github.com/taraxvoid/voidflow -# ref: # pin; bump deliberately +# ref: # a tag or branch (not a SHA); bump deliberately # configs: # - lefthook/site.yml # @@ -14,6 +15,10 @@ # one defined here. Differences go through package.json scripts instead, e.g. # `test:unit:precommit` replaces `test:unit` for the pre-commit unit step. +templates: + # Package manager: pnpm when the repo is a pnpm project, bun otherwise. + pm: "$(if [ -f pnpm-lock.yaml ]; then echo pnpm; else echo bun; fi)" + pre-commit: skip: [merge, rebase] commands: @@ -22,39 +27,45 @@ pre-commit: run: | if [ ! -d node_modules ]; then echo "pre-commit: 'node_modules' is missing, dependencies aren't installed." - echo " Run 'bun install' before committing, then commit again." + echo " Run '{pm} install' before committing, then commit again." exit 1 fi format: priority: 2 - run: bun run --if-present format + run: "{pm} run --if-present format" stage_fixed: true lint-actions: priority: 3 - run: bun run --if-present lint:actions + run: "{pm} run --if-present lint:actions" typecheck: priority: 3 - run: bun run --if-present check + run: "{pm} run --if-present check" unit: priority: 3 # A site that can't run its whole suite pre-commit (e.g. tests that need # a built dist/) defines `test:unit:precommit`; otherwise test:unit runs. run: | if grep -q '"test:unit:precommit"' package.json; then - bun run test:unit:precommit + {pm} run test:unit:precommit else - bun run --if-present test:unit + {pm} run --if-present test:unit fi licenses: priority: 3 glob: "{package.json,bun.lock,pnpm-lock.yaml}" - run: bun run --if-present check:licenses + run: "{pm} run --if-present check:licenses" audit: # Local only: hits a remote vulnerability database and can hang offline, # so it runs when dependencies changed and fails closed. priority: 3 glob: "{package.json,bun.lock,pnpm-lock.yaml}" - run: bun run --if-present audit + # Checked by hand: under bun, `--if-present` falls through to + # the system `audit` binary (macOS /usr/sbin/audit) when the site has no + # audit script, and that exits non-zero. + run: | + if grep -q '"audit"' package.json; then + {pm} run audit + fi commit-msg: commands: @@ -109,8 +120,8 @@ pre-push: if [ ! -d node_modules ]; then echo "pre-push: 'node_modules' is missing, dependencies aren't installed." - echo " Run 'bun install' before pushing, then push again." + echo " Run '{pm} install' before pushing, then push again." exit 1 fi - bun run --if-present test:push + {pm} run --if-present test:push diff --git a/test/lefthook-site.test.ts b/test/lefthook-site.test.ts index 574c31d..f44c796 100644 --- a/test/lefthook-site.test.ts +++ b/test/lefthook-site.test.ts @@ -28,7 +28,19 @@ describe('lefthook/site.yml', () => { test('pre-commit unit step prefers test:unit:precommit when a site defines it', async () => { const text = await Bun.file(config).text() expect(text).toContain('grep -q \'"test:unit:precommit"\' package.json') - expect(text).toContain('bun run test:unit:precommit') - expect(text).toContain('bun run --if-present test:unit') + expect(text).toContain('{pm} run test:unit:precommit') + expect(text).toContain('{pm} run --if-present test:unit') + }) + + test('picks the package manager from the lockfile instead of hardcoding bun', async () => { + const text = await Bun.file(config).text() + expect(text).toContain('pnpm-lock.yaml ]; then echo pnpm; else echo bun') + expect(text).not.toMatch(/\bbun run\b/) + }) + + test('audit step only runs when the site defines an audit script', async () => { + const text = await Bun.file(config).text() + expect(text).toContain('grep -q \'"audit"\' package.json') + expect(text).not.toContain('--if-present audit') }) })