Skip to content

Commit 1d873f3

Browse files
committed
fix(config): auto-set git core.hooksPath without prompting (#1157)
## Summary - Skip the git-hook prompt in `vp config` when `staged` config already exists in `vite.config.ts` — the project has already opted into hooks - Recognize `postinstall` lifecycle in addition to `prepare` — projects that call `vp config` from `postinstall` (e.g. [npmx.dev](https://github.com/npmx-dev/npmx.dev/blob/main/package.json#L26)) now auto-install hooks without prompting - Add `vp config` hooks setup flow diagram and prompt decision table to RFC ## Test plan - [x] New snap test `command-config-auto-hooks` — verifies auto-install when staged config exists (no lifecycle env) - [x] New snap test `command-config-postinstall-auto-hooks` — verifies auto-install from postinstall lifecycle - [x] All existing `command-config-*` snap tests pass unchanged Closes #1154
1 parent 99bb918 commit 1d873f3

9 files changed

Lines changed: 139 additions & 18 deletions

File tree

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"name": "command-config-auto-hooks"
3+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
> git init
2+
> vp config # should install hooks automatically without prompting (staged config exists)
3+
> git config --local core.hooksPath # should be .vite-hooks/_
4+
.vite-hooks/_
5+
6+
> cat .vite-hooks/pre-commit # should have vp staged
7+
vp staged
8+
9+
> cat vite.config.ts # should remain unchanged
10+
import { defineConfig } from 'vite-plus';
11+
12+
export default defineConfig({
13+
staged: {
14+
'*': 'vp check --fix',
15+
},
16+
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"commands": [
3+
{ "command": "git init", "ignoreOutput": true },
4+
"vp config # should install hooks automatically without prompting (staged config exists)",
5+
"git config --local core.hooksPath # should be .vite-hooks/_",
6+
"cat .vite-hooks/pre-commit # should have vp staged",
7+
"cat vite.config.ts # should remain unchanged"
8+
]
9+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { defineConfig } from 'vite-plus';
2+
3+
export default defineConfig({
4+
staged: {
5+
'*': 'vp check --fix',
6+
},
7+
});
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"name": "command-config-postinstall-auto-hooks"
3+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
> git init
2+
> vp config # should install hooks automatically without prompting
3+
> git config --local core.hooksPath # should be .vite-hooks/_
4+
.vite-hooks/_
5+
6+
> cat .vite-hooks/pre-commit # should have vp staged
7+
vp staged
8+
9+
> cat vite.config.ts # should have staged config
10+
import { defineConfig } from 'vite-plus';
11+
12+
export default defineConfig({
13+
staged: {
14+
"*": "vp check --fix"
15+
},
16+
17+
});
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"env": {
3+
"npm_lifecycle_event": "postinstall"
4+
},
5+
"commands": [
6+
{ "command": "git init", "ignoreOutput": true },
7+
"vp config # should install hooks automatically without prompting",
8+
"git config --local core.hooksPath # should be .vite-hooks/_",
9+
"cat .vite-hooks/pre-commit # should have vp staged",
10+
"cat vite.config.ts # should have staged config"
11+
]
12+
}

packages/cli/src/config/bin.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { join } from 'node:path';
1010
import mri from 'mri';
1111

1212
import { vitePlusHeader } from '../../binding/index.js';
13-
import { ensurePreCommitHook } from '../migration/migrator.js';
13+
import { ensurePreCommitHook, hasStagedConfigInViteConfig } from '../migration/migrator.js';
1414
import { updateExistingAgentInstructions } from '../utils/agent.js';
1515
import { renderCliDoc } from '../utils/help.js';
1616
import { defaultInteractive, promptGitHooks } from '../utils/prompts.js';
@@ -54,17 +54,25 @@ async function main() {
5454
const dir = args['hooks-dir'] as string | undefined;
5555
const hooksOnly = args['hooks-only'] as boolean;
5656
const interactive = defaultInteractive();
57-
const isPrepareScript = process.env.npm_lifecycle_event === 'prepare';
57+
const lifecycleEvent = process.env.npm_lifecycle_event;
58+
const isLifecycleScript = lifecycleEvent === 'prepare' || lifecycleEvent === 'postinstall';
5859
const root = process.cwd();
5960

6061
// --- Step 1: Hooks setup ---
6162
const hooksDir = dir ?? '.vite-hooks';
6263
const isFirstHooksRun = !existsSync(join(root, hooksDir, '_', 'pre-commit'));
6364

6465
let shouldSetupHooks = true;
65-
if (interactive && isFirstHooksRun && !dir && !isPrepareScript) {
66+
if (
67+
interactive &&
68+
isFirstHooksRun &&
69+
!dir &&
70+
!isLifecycleScript &&
71+
!hasStagedConfigInViteConfig(root)
72+
) {
6673
// --hooks-dir implies agreement; only prompt when using default dir on first run
67-
// prepare script implies the project opted into hooks — install automatically
74+
// lifecycle script (prepare/postinstall) implies the project opted into hooks — install automatically
75+
// existing staged config in vite.config.ts implies the project already opted in
6876
shouldSetupHooks = await promptGitHooks({ interactive });
6977
}
7078

rfcs/config-and-staged-commands.md

Lines changed: 60 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## Summary
44

5-
Add `vp config` and `vp staged` as built-in commands. `vp config` is a `prepare`-lifecycle command that installs git hook shims (husky-compatible reimplementation, not a bundled dependency). `vp staged` bundles lint-staged and reads config from the `staged` key in `vite.config.ts`. Projects get a zero-config pre-commit hook that runs `vp check --fix` on staged files — no extra devDependencies needed.
5+
Add `vp config` and `vp staged` as built-in commands. `vp config` is a lifecycle command (`prepare` or `postinstall`) that installs git hook shims (husky-compatible reimplementation, not a bundled dependency). `vp staged` bundles lint-staged and reads config from the `staged` key in `vite.config.ts`. Projects get a zero-config pre-commit hook that runs `vp check --fix` on staged files — no extra devDependencies needed.
66

77
## Motivation
88

@@ -55,24 +55,27 @@ Flags: `--hooks` (force), `--no-hooks` (skip)
5555

5656
Flags: `--hooks` (force), `--no-hooks` (skip)
5757

58-
### Ongoing use: `vp config` (prepare lifecycle)
58+
### Ongoing use: `vp config` (lifecycle script)
5959

60-
`vp config` is the command that runs on every `npm install` via the `prepare` script. It reinstalls hook shims — it does **not** create the `staged` config or the pre-commit hook file. Those are created by `vp create`/`vp migrate`.
60+
`vp config` is the command that runs on every `npm install` via the `prepare` or `postinstall` script. It reinstalls hook shims — it does **not** create the `staged` config or the pre-commit hook file. Those are created by `vp create`/`vp migrate`.
6161

6262
```json
6363
{ "scripts": { "prepare": "vp config" } }
64+
// or
65+
{ "scripts": { "postinstall": "vp config" } }
6466
```
6567

66-
When `npm_lifecycle_event=prepare` (set by npm/pnpm/yarn during `npm install`), agent setup is skipped automatically — only hooks are reinstalled.
68+
When running from a lifecycle script (`npm_lifecycle_event` is `prepare` or `postinstall`), hooks are installed automatically without prompting.
6769

6870
### Manual setup (without `vp create`/`vp migrate`)
6971

7072
For users who want to set up hooks manually, four steps are required:
7173

72-
1. **Add prepare script** to `package.json`:
74+
1. **Add lifecycle script** to `package.json`:
7375
```json
7476
{ "scripts": { "prepare": "vp config" } }
7577
```
78+
Or use `postinstall` if `prepare` is not suitable for your project.
7679
2. **Add staged config** to `vite.config.ts`:
7780
```typescript
7881
export default defineConfig({
@@ -105,9 +108,10 @@ Behavior:
105108
6. Exits 0 and skips hooks if `VITE_GIT_HOOKS=0` or `HUSKY=0` environment variable is set (backwards compatible)
106109
7. Exits 0 and skips hooks if `.git` directory doesn't exist (safe during `npm install` in consumer projects)
107110
8. Exits 1 on real errors (git command not found, `git config` failed)
108-
9. Agent update runs uniformly in all modes (`prepare`, interactive, non-interactive). New agent file creation is handled by `vp create`/`vp migrate`.
109-
10. Interactive mode: prompts on first run for hooks setup
110-
11. Non-interactive mode: sets up hooks by default
111+
9. Agent update runs uniformly in all modes (lifecycle script, interactive, non-interactive). New agent file creation is handled by `vp create`/`vp migrate`.
112+
10. Lifecycle script mode (`prepare`/`postinstall`): sets up hooks automatically without prompting
113+
11. Interactive mode: prompts on first run — unless the project already has `staged` config in `vite.config.ts` (which implies prior opt-in)
114+
12. Non-interactive mode: sets up hooks by default
111115

112116
### `vp staged`
113117

@@ -275,12 +279,54 @@ Husky <9.0.0 is not supported by auto migration — `vp migrate` detects unsuppo
275279

276280
## Relationship to Existing Commands
277281

278-
| Command | Purpose | When |
279-
| ---------------- | -------------------------------------- | --------------------------- |
280-
| `vp check` | Format + lint + type check | Manual or CI |
281-
| `vp check --fix` | Auto-fix format + lint issues | Manual or pre-commit |
282-
| **`vp config`** | **Reinstall hook shims + agent setup** | **npm `prepare` lifecycle** |
283-
| **`vp staged`** | **Run staged linters on staged files** | **Pre-commit hook** |
282+
| Command | Purpose | When |
283+
| ---------------- | -------------------------------------- | ------------------------------------------- |
284+
| `vp check` | Format + lint + type check | Manual or CI |
285+
| `vp check --fix` | Auto-fix format + lint issues | Manual or pre-commit |
286+
| **`vp config`** | **Reinstall hook shims + agent setup** | **npm lifecycle (`prepare`/`postinstall`)** |
287+
| **`vp staged`** | **Run staged linters on staged files** | **Pre-commit hook** |
288+
289+
## `vp config` Hooks Setup Flow
290+
291+
```
292+
vp config
293+
294+
├─ VITE_GIT_HOOKS=0 or HUSKY=0? ──→ Skip hooks (exit 0)
295+
296+
├─ Not inside a git repo? ──→ Skip hooks (exit 0)
297+
298+
├─ Should prompt user?
299+
│ Prompt ONLY when ALL of these are true:
300+
│ • Interactive terminal (not CI, not piped)
301+
│ • First run (hook shims don't exist yet)
302+
│ • No --hooks-dir flag
303+
│ • Not running from lifecycle script (prepare/postinstall)
304+
│ • No staged config in vite.config.ts
305+
306+
│ YES → Prompt "Set up pre-commit hooks?"
307+
│ User declines → skip hooks
308+
│ NO → Auto-install hooks
309+
310+
├─ core.hooksPath already set to a custom path?
311+
│ (not .vite-hooks/_, not .husky)
312+
│ └─ YES → Skip hooks, preserve custom config
313+
314+
├─ Set core.hooksPath → .vite-hooks/_
315+
├─ Create hook shims in .vite-hooks/_/
316+
├─ Ensure staged config in vite.config.ts
317+
└─ Ensure .vite-hooks/pre-commit contains "vp staged"
318+
```
319+
320+
### When does the prompt appear?
321+
322+
| Caller | Prompts? | Why |
323+
| ------------------------------------- | -------- | ---------------------------------- |
324+
| `npm install` → prepare/postinstall | No | lifecycle script = auto-install |
325+
| Manual, project has `staged` config | No | staged config = already opted in |
326+
| Manual, no `staged` config, first run | **Yes** | No signal that project wants hooks |
327+
| Manual, already ran before | No | Hook shims exist = not first run |
328+
| CI / non-interactive | No | Non-interactive = auto-install |
329+
| `--hooks-dir` flag | No | Explicit flag = intent to install |
284330

285331
## Comparison with Other Tools
286332

0 commit comments

Comments
 (0)