Skip to content

feat(cli): add vp hooks command for managing Git hooks (#2219) - #2341

Open
dennybiasiolli wants to merge 10 commits into
voidzero-dev:mainfrom
dennybiasiolli:feat/2219-remove-git-hooks
Open

feat(cli): add vp hooks command for managing Git hooks (#2219)#2341
dennybiasiolli wants to merge 10 commits into
voidzero-dev:mainfrom
dennybiasiolli:feat/2219-remove-git-hooks

Conversation

@dennybiasiolli

@dennybiasiolli dennybiasiolli commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Closes #2219

Adds a dedicated vp hooks command so users can manage the Vite+ Git hook
dispatcher without following the manual steps from the commit-hooks guide:

  • vp hooks setup — install or refresh the dispatcher (core.hooksPath + <dir>/_)
  • vp hooks disable — tear down the dispatcher and persist the decision in local
    git config so prepare / vp config do not reinstall it
  • vp hooks enable — re-enable after disable (same as setup)
  • vp hooks status — show preference, core.hooksPath, dispatcher, and project hooks

Project-owned hooks (e.g. .vite-hooks/pre-commit), staged config, and
package.json lifecycle scripts are left alone. Custom directories work via
--hooks-dir and are remembered in local git config for later commands and
vp config.

Why

#2219: once hooks are set up there was no first-class way to remove them, so people
had to hand-edit core.hooksPath and delete .vite-hooks/_. Maintainers preferred
a vp hooks surface (setup / disable / enable / status) over a
vp config --uninstall-hooks flag, with the disable decision persisted.


Disclaimer: assisted by Grok 4.5 (xAI) while implementing and reviewing this change.

@netlify

netlify Bot commented Aug 5, 2026

Copy link
Copy Markdown

Deploy Preview for viteplus-preview canceled.

Name Link
🔨 Latest commit ca54fec
🔍 Latest deploy log https://app.netlify.com/projects/viteplus-preview/deploys/6a760a63f477c500082e7ba5

@dennybiasiolli
dennybiasiolli force-pushed the feat/2219-remove-git-hooks branch 4 times, most recently from 9ac52bd to db4b05e Compare August 6, 2026 11:44
@fengmk2 fengmk2 self-assigned this Aug 7, 2026
@fengmk2
fengmk2 requested a review from TheAlexLichter August 7, 2026 14:05
@fengmk2

fengmk2 commented Aug 7, 2026

Copy link
Copy Markdown
Member

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d1d530ca12

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +239 to +243
export function resolveHooksDir(dir?: string): string {
if (dir) {
return dir;
}
return getStoredHooksDir() ?? DEFAULT_HOOKS_DIR;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Anchor remembered hook directories to their setup location

When setup runs at the repository root and a later vp hooks disable, enable, or status runs from a nested directory without --hooks-dir, this returns the stored .vite-hooks string relative to the new working directory. computeTarget() consequently looks for <nested-prefix>/.vite-hooks/_, treats the actual root .vite-hooks/_ as foreign, and disable can report success while leaving the root dispatcher and core.hooksPath active. Persist or reconstruct the setup-time Git prefix so remembered directories identify the same repository path regardless of the caller's current directory.

Useful? React with 👍 / 👎.

@dennybiasiolli dennybiasiolli Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 613f68b. We now persist vp.hooks.prefix (git rev-parse --show-prefix at enable/disable time; . means worktree root) alongside vp.hooks.dir. Later commands without --hooks-dir resolve files and core.hooksPath from <toplevel>/<prefix>/<dir>, so disable from a nested cwd still tears down the root dispatcher. --hooks-dir stays cwd-relative, same as today’s install-from-subdir behavior. status/disable with nothing stored bind to the worktree root (and adopt an existing Vite+ core.hooksPath dispatcher) so a nested cwd does not poison the prefix.

Comment thread packages/cli/src/config/hooks.ts Outdated
Comment on lines +493 to +494
if (ownsHooksPath) {
const unsetError = unsetOwnedHooksPath(target);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Clear owned hook paths hidden by a worktree override

When extensions.worktreeConfig is enabled, a repository-local Vite+ path can be hidden by a foreign worktree path; git config -h distinguishes --local as the repository config and --worktree as the per-worktree config. Because this condition only invokes unsetOwnedHooksPath() when the effective value is owned, disable leaves the hidden local .vite-hooks/_ value in place while deleting its dispatcher. If the worktree override is later removed, Git falls back to that now-broken Vite+ path. Inspect and unset owned values at each scope even when a foreign higher-precedence value is effective; unsetOwnedHooksPath() already preserves foreign scoped values.

Useful? React with 👍 / 👎.

@dennybiasiolli dennybiasiolli Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 9d43c1a. disable always calls unsetOwnedHooksPath(), which already inspects local and worktree scopes and only unsets owned values. A foreign effective worktree path no longer skips cleanup of a hidden local Vite+ core.hooksPath.

Comment thread packages/cli/src/config/hooks.ts Outdated
const target = rel ? `${rel}/${hooksDir}/_` : `${hooksDir}/_`;
const existingHooksPath = getEffectiveHooksPath();
const userDisabled = isHooksUserDisabled();
const dispatcherInstalled = existsSync(join(hooksDir, '_', 'h'));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Verify hook shims before reporting the dispatcher installed

If _ /h remains but one or more generated per-hook shims have been deleted during a partial cleanup, this still reports Dispatcher: installed. With core.hooksPath pointing at the target and a project-owned .vite-hooks/pre-commit present, vp hooks status therefore makes the setup appear active even though Git has no .vite-hooks/_/pre-commit entry point to invoke. Check the generated shim files, at least for each detected project hook, rather than using the helper file alone as proof that the dispatcher is installed.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Leaving this as-is. _/h is the dispatcher helper we own; enable always rewrites the full shim set, and disable removes the whole _/ tree. A missing named shim is a tampered/partial tree, not a state status needs to diagnose. vp config already uses _/pre-commit only as a first-run hint.

Comment on lines +206 to +207
// status 5 = key not found
if (result.status !== 0 && result.status !== 5) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3 Badge Clear every duplicate disable-preference value

When the local Git config contains multiple vp.hooks.disabled values, git config --local --unset exits with status 5 because multiple values match, not only when the key is missing. Treating that status as success leaves the values intact, so vp hooks enable can install the dispatcher and report success while status still says disabled and later vp config skips installation. Use --unset-all for this preference or explicitly distinguish the multiple-value failure; git config -h documents --unset-all as removing all matches.

Useful? React with 👍 / 👎.

@dennybiasiolli dennybiasiolli Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 196a281. Preference and scoped core.hooksPath now use --unset-all. Git status 5 is still treated as success (missing key). Added a regression test with duplicate vp.hooks.disabled values.

Comment on lines +110 to +114
const args = mri(raw.slice(1), {
boolean: ['help'],
string: ['hooks-dir'],
alias: { h: 'help' },
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject unused operands before mutating hook state

The parser never validates args._, so an invocation such as vp hooks disable .custom-hooks silently ignores the directory operand and disables whichever stored or default directory resolveHooksDir() selects. This can remove the wrong dispatcher while leaving the intended custom hooks active, even though the command exits successfully. Reject unexpected positional operands and unknown options before calling setup, enable, or disable so malformed destructive commands cannot act on a fallback directory.

Useful? React with 👍 / 👎.

@dennybiasiolli dennybiasiolli Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in ca54fec. After help parsing, leftover args._ or unknown flags fail before enable/disable/status run. vp hooks disable .custom-hooks now errors and points at --hooks-dir.

Comment thread packages/cli/src/config/hooks.ts Outdated
Comment on lines +472 to +476
const existingHooksPath = getEffectiveHooksPath();
const ownsHooksPath =
!!existingHooksPath && normalizeHooksPath(existingHooksPath) === normalizeHooksPath(target);
const foreignHooksPath =
!!existingHooksPath && normalizeHooksPath(existingHooksPath) !== normalizeHooksPath(target);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Recognize absolute spellings of the owned hooks path

When core.hooksPath is an absolute path to this same dispatcher, such as /repo/.vite-hooks/_, comparing its normalized string with the generated relative target .vite-hooks/_ classifies it as foreign. vp hooks disable then leaves core.hooksPath unchanged but removes .vite-hooks/_, so Git is left pointing at a nonexistent dispatcher and status incorrectly labels the path as non-Vite+. Resolve both spellings against the Git worktree root before deciding ownership.

Useful? React with 👍 / 👎.

@dennybiasiolli dennybiasiolli Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 44080a9. Ownership compares paths after resolving against the worktree root (and realpath, so /var vs /private/var matches). An absolute spelling of the same dispatcher is treated as owned for install refresh, status, and disable.

Comment thread packages/cli/src/hooks/bin.ts Outdated
description: 'Disable hooks: unset core.hooksPath, remove <dir>/_, persist preference',
},
{
label: 'enable',

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.

If enable is the same as setup, then there is no need to keep setup command. This way, vp hooks enable and vp hooks disable can correspond one-to-one.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I kept also setup because of the list in #2219 , but I'm super-happy to drop setup so we can slightly reduce the surface of changes, working on it, and double checking all the other reviews from codex

@dennybiasiolli dennybiasiolli Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Dropped setup in 66af7c2. Surface is now enable / disable / status; enable both installs the first time and clears a disable preference.

@dennybiasiolli
dennybiasiolli force-pushed the feat/2219-remove-git-hooks branch from d1d530c to f96ecef Compare August 7, 2026 15:14
@dennybiasiolli

Copy link
Copy Markdown
Contributor Author

Rebased on top of the main branch, so the commit history is clear(er).
I applied the suggested changes in separate commits, let me know if you prefer to have a clear commit history and I can squash them in a single one or in the previous commits in this same branch.
I personally prefer to have a clean commit history without too many "fix" commits before merging, but that is just my personal taste for my repos, I'm open to suggestions here :)

Implement pure hook lifecycle helpers on top of install(): user preference
persistence (local git config), safe per-scope core.hooksPath unset, and
status reporting. Teach install() and vp config to honor a disable preference
and resolve the last-used hooks directory. Cover lifecycle, foreign path,
worktree, stored custom dir, and unsafe-tree cases in unit tests; update the
config help snapshot for the remembered hooks-dir default.
Add the hooks bin entry and register it from bin.ts so local vp can run
setup/disable/enable/status. Bundle the entry with tsdown, document it in
the CLI package build notes, and add a PTY lifecycle fixture for
setup/status/disable/enable plus prepare-style config skip after disable.
Delegate hooks from the Rust global CLI to the JS implementation, and list
the command in global help, the interactive picker, and the local NAPI
help surface. Update top-level help snapshots and add vp hooks --help
coverage for local and global flavors.
Add setup/disable/enable/status to the commit-hooks guide with a quick
start, and point create, migrate, troubleshooting, and the guide index at
the new commands so users can discover and operate them easily.
Keep enable/disable/status as the public surface. enable installs or
refreshes the dispatcher and clears a disable preference.
Resolve relative and absolute hooksPath spellings against the git
worktree root (via realpath) before deciding ownership, so disable
and install do not treat /repo/.vite-hooks/_ as a foreign path.
@dennybiasiolli
dennybiasiolli force-pushed the feat/2219-remove-git-hooks branch from f96ecef to c088f14 Compare August 7, 2026 15:19
Persist the setup-time git prefix with the hooks directory so enable,
disable, status, and vp config resolve the same dispatcher from a nested
cwd instead of treating root .vite-hooks/_ as foreign.
disable now always walks local and worktree scopes instead of only the
effective value, so a foreign worktree path cannot leave a stale local
Vite+ hooksPath behind after the dispatcher is removed.
git config --unset exits 5 when a key has multiple values, which we
treated as success. Use --unset-all for the disable preference and
scoped core.hooksPath so enable cannot leave a stale disabled state.
Positional directories and unknown flags were ignored, so
`vp hooks disable .custom-hooks` could tear down the default dispatcher.
Fail fast and point at --hooks-dir instead.
@dennybiasiolli
dennybiasiolli force-pushed the feat/2219-remove-git-hooks branch from c088f14 to ca54fec Compare August 7, 2026 16:40
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.

Command for removing git hooks

2 participants