Skip to content

feat(hooks): add built-in pre-commit hook via vp config + vp staged - #674

Merged
fengmk2 merged 70 commits into
mainfrom
lint-staged-pre-commit-hook
Mar 5, 2026
Merged

feat(hooks): add built-in pre-commit hook via vp config + vp staged#674
fengmk2 merged 70 commits into
mainfrom
lint-staged-pre-commit-hook

Conversation

@fengmk2

@fengmk2 fengmk2 commented Mar 3, 2026

Copy link
Copy Markdown
Member

Bundle husky and lint-staged functionality into vite-plus so projects get
a zero-config pre-commit hook with no extra devDependencies.

  • vp config merges hooks setup (vp prepare) and agent integration (vp init) into a single command
  • vp staged delegates to bundled lint-staged, reading config from staged key in vite.config.ts
  • Default hooks directory is .vite-hooks; migration from husky preserves .husky via --hooks-dir
  • Migration auto-configures hooks, rewrites existing husky/lint-staged setups, and skips when other hook tools are detected
  • Monorepo template and create flow include hooks by default

closes VP-30

@fengmk2 fengmk2 self-assigned this Mar 3, 2026

fengmk2 commented Mar 3, 2026

Copy link
Copy Markdown
Member Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@fengmk2
fengmk2 force-pushed the lint-staged-pre-commit-hook branch 4 times, most recently from 1be9cef to 8c52fb7 Compare March 3, 2026 11:19
@linear

linear Bot commented Mar 3, 2026

Copy link
Copy Markdown

@fengmk2
fengmk2 force-pushed the lint-staged-pre-commit-hook branch from 710944d to c6fa1ea Compare March 4, 2026 04:53
@netlify

netlify Bot commented Mar 4, 2026

Copy link
Copy Markdown

Deploy Preview for viteplus-staging canceled.

Name Link
🔨 Latest commit 12bf2bb
🔍 Latest deploy log https://app.netlify.com/projects/viteplus-staging/deploys/69a9b08454a1e600081b110e

@fengmk2
fengmk2 marked this pull request as ready for review March 4, 2026 05:20
Comment thread packages/cli/src/utils/husky.ts Outdated
Comment thread packages/cli/src/utils/prompts.ts Outdated
Comment thread packages/cli/src/migration/migrator.ts

@cpojer cpojer left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love it. Only comment is whether it should be check-staged instead, see https://discord.com/channels/1138376133969203262/1478557890574024736/1478625219248787499

Comment thread rfcs/lint-staged-pre-commit-hook.md Outdated
@fengmk2
fengmk2 force-pushed the lint-staged-pre-commit-hook branch 2 times, most recently from 1f1ad34 to f476500 Compare March 4, 2026 10:05
Comment thread packages/cli/src/migration/bin.ts Outdated
Comment thread packages/cli/src/staged/bin.ts Outdated
Comment thread packages/cli/src/staged/bin.ts Outdated
@fengmk2
fengmk2 force-pushed the lint-staged-pre-commit-hook branch 2 times, most recently from a955dc3 to 7153742 Compare March 4, 2026 15:45
Comment thread packages/cli/src/bin.ts Outdated
@fengmk2
fengmk2 force-pushed the lint-staged-pre-commit-hook branch 3 times, most recently from 1d7d3b9 to 63481bb Compare March 5, 2026 01:57
fengmk2 added 19 commits March 5, 2026 20:00
…r-facing

`vite-staged` was an internal intermediate state during refactoring, never
a real user config key. Remove the migration branch and rename all snap
test fixtures from `vite-staged` to `lint-staged`.
…n setupGitHooks

Instead of threading an `unsupported` flag through the rest of the
function, return immediately after logging the warning. This removes
all `if (!unsupported)` guards and flattens the logic.
Three P1 fixes:

1. vp create: call rewritePrepareScript() before setupGitHooks() so
   template "prepare": "husky" scripts are rewritten to "vp prepare"
   before hook setup runs.

2. vp migrate: move hasUnsupportedHuskyVersion() check before the
   rewrite block and thread skipStagedMigration flag through rewrite
   functions. This preserves lint-staged config when Husky v8 is
   detected, instead of deleting it while .husky/pre-commit still
   references npx lint-staged.

3. setupGitHooks(): detect subdirectory projects by comparing
   projectPath with git root at function entry. When they differ,
   warn and skip to prevent vp prepare from hijacking the repo-wide
   core.hooksPath.
- migration-subpath: assert core.hooksPath is NOT set (no hijack)
- migration-add-git-hooks: assert core.hooksPath IS set to .husky/_
Merge the hooks-only `vp prepare` and agent-only `vp init` commands into
a single `vp config` command that handles both hooks setup and agent
integration in one step.

Key changes:
- New `vp config` command with `--hooks-dir` flag (default: `.vite-hooks`)
- `--hooks-only` internal flag for migration subprocess calls
- Default hooks directory changed from `.husky` to `.vite-hooks`
- Migration from husky preserves `.husky` via `--hooks-dir .husky`
- AST-grep post-processing converts positional dir args to `--hooks-dir`
- `createHuskyPreCommitHook()` renamed to `createPreCommitHook()`
- RFC updated to reflect the new command design
Three bugs fixed:

1. skipStagedMigration not forwarded to root monorepo rewrite —
   rewriteRootWorkspacePackageJson called rewriteMonorepoProject
   without passing skipStagedMigration, so husky v8 detection
   didn't prevent root package.json lint-staged deletion.

2. .lintstagedrc files deleted even when merge into vite.config.ts
   failed — now preserved when mergeStagedConfigToViteConfig
   returns false.

3. package.json lint-staged key deleted before merge confirmation —
   extraction now defers deletion to callers, which only remove
   the key after a successful merge. Also fixed setupGitHooks
   which had a second unconditional deletion path.

Also adds config to the interactive command picker and snap tests
verifying .vite-hooks/_/ internal hook shim contents.
Guard `createPreCommitHook` on successful staged config availability —
either merged into vite.config.ts or present as a standalone lint-staged
config file. Previously, the hook was created unconditionally, causing
`vp staged` to fail on every commit when no config existed.

Also fix `--concurrent` bare flag parsing: `Number('')` returns 0 (not
NaN), so bare `--concurrent` incorrectly set serial mode instead of
unlimited concurrency.
…patcher

- Copy old husky hooks dir contents to .vite-hooks/ during migration
- Rebrand hook dispatcher from "husky" to "Vite+" in error messages
- Add VITE_GIT_HOOKS env var support alongside HUSKY for backwards compat
- Look for init.sh in ~/.config/vite-plus/ first, falling back to ~/.config/husky/
- Remove --hooks-dir flag from migrated prepare scripts (always use .vite-hooks)
- Use explicit shell grouping for || && precedence in hook dispatcher
- Update snap tests to verify new .vite-hooks/ paths
…to .vite-hooks

Only the default .husky dir is migrated to .vite-hooks/. Custom dirs
(e.g. .config/husky) keep their --hooks-dir flag and hooks stay in place.
Update RFC to document default vs custom dir migration behavior.
The snapshot was stale — rewritePackageJson no longer deletes the
lint-staged key (deletion moved to caller after vite.config.ts merge).
1. Pre-commit hook no longer created when standalone lint-staged config
   exists but staged merge into vite.config.ts failed — vp staged only
   reads from vite.config.ts so the hook would fail on every commit.

2. Prepare script no longer rewritten before hook setup can abort —
   rewritePrepareScript now runs after setupGitHooks succeeds, preventing
   orphaned "vp config" when hooks are skipped (unsupported config, old
   husky, etc.). Extracted read-only getOldHooksDir() probe and changed
   setupGitHooks to return boolean.

3. Standalone lint-staged config (.lintstagedrc.json) no longer deleted
   when "staged" already exists in vite.config.ts — warns user to merge
   manually instead of silently dropping config.
Split the monolithic config/bin.ts (438 lines) into three focused files:
- config/hooks.ts: git hooks constants and install()
- config/agent.ts: agent detection, instruction injection, MCP config
- config/bin.ts: CLI arg parsing and main() orchestration only
Rename $HOME/.config/vite-plus/init.sh to hooks-init.sh for clarity.
…rn failure on skip

Two fixes:

1. Hook dispatcher generated incorrect PATH for nested custom dirs like
   .config/husky. The dirname depth was hardcoded to 3 levels, which only
   works for single-segment dirs (.vite-hooks). Now computes depth
   dynamically from the dir's segment count.

2. setupGitHooks returned true even when vp config reported a skip or
   failure, causing rewritePrepareScript to rewrite prepare from "husky"
   to "vp config" while hooks remained unconfigured.
…indows

Linux CI runners lack global git user.name/user.email, causing git commit
to fail silently in snap tests. Windows CI returns MSYS-style paths from
git rev-parse that break path.relative() for core.hooksPath computation.
On Windows CI, git rev-parse returns long path names while process.cwd()
may return 8.3 short names (e.g. RUNNER~1 vs runner), causing
path.relative() to produce incorrect core.hooksPath values. Use
realpathSync to canonicalize both paths before comparison.
@fengmk2
fengmk2 force-pushed the lint-staged-pre-commit-hook branch from 57b92e8 to cec82af Compare March 5, 2026 12:46
fengmk2 added 2 commits March 5, 2026 21:11
Replace --show-toplevel + path.relative() with --show-prefix which
returns the relative path from git root to cwd directly. This avoids
all Windows path normalization issues (MSYS paths, 8.3 short names)
that made path.relative() produce incorrect core.hooksPath values.
…skip staged migration with --no-hooks

Two migration bugs fixed:
- setupGitHooks() removed husky/lint-staged from devDependencies before
  confirming hooks were installed, leaving projects with a broken
  "prepare": "husky" script when core.hooksPath was already set
- --no-hooks still ran staged migration, removing lint-staged config
  from package.json while existing .husky/pre-commit scripts depend on it
@fengmk2
fengmk2 force-pushed the lint-staged-pre-commit-hook branch from 12bf70b to 65311d3 Compare March 5, 2026 14:28
fengmk2 added 3 commits March 5, 2026 22:51
…e lint-staged scripts

Skip agent setup in `vp config` when `npm_lifecycle_event=prepare` so that
`npm install` only installs hooks without triggering agent setup on every install.

Skip lint-staged script rewrites (e.g. `lint-staged` → `vp staged`) when
`skipStagedMigration` is true, preserving scripts like `"check-staged": "lint-staged"`
when hooks migration is skipped via --no-hooks.
…t code

Unify the three-step hook setup pattern (getOldHooksDir → setupGitHooks →
rewritePrepareScript) into a single installGitHooks() helper, replacing
three identical copies in migration/bin.ts and create/bin.ts. Also remove
redundant `hooks: undefined` from promptGitHooks call in config/bin.ts.
…oks dir, surface staged errors

- Extract preflightGitHooksSetup() from setupGitHooks() to run
  deterministic checks before migration rewrites lint-staged config.
  When hooks setup would fail (other hook tools, subdirectory project,
  unsupported lint-staged config), migration now preserves lint-staged
  scripts and config in package.json instead of rewriting them.

- Fix hookScript() path segment counting: filter out '.' segments so
  paths like "./.config/husky" produce the same depth as ".config/husky".

- Surface real resolveViteConfig errors in `vp staged` instead of
  masking them as "No staged config found".
@fengmk2
fengmk2 merged commit 29c41a6 into main Mar 5, 2026
16 of 18 checks passed

fengmk2 commented Mar 5, 2026

Copy link
Copy Markdown
Member Author

Merge activity

@fengmk2
fengmk2 deleted the lint-staged-pre-commit-hook branch March 5, 2026 17:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants