Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"name": "command-config-replace-husky-hookspath"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
> git init
> git config core.hooksPath .husky/_
> vp config --hooks-only # should replace .husky/_ with .vite-hooks/_
> git config --local core.hooksPath # should be .vite-hooks/_
.vite-hooks/_
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"commands": [
{ "command": "git init", "ignoreOutput": true },
{ "command": "git config core.hooksPath .husky/_", "ignoreOutput": true },
"vp config --hooks-only # should replace .husky/_ with .vite-hooks/_",
"git config --local core.hooksPath # should be .vite-hooks/_"
]
}
7 changes: 6 additions & 1 deletion packages/cli/src/config/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,12 @@ export function install(dir = '.vite-hooks'): InstallResult {
const target = rel ? `${rel}/${dir}/_` : `${dir}/_`;
const checkResult = spawnSync('git', ['config', '--local', 'core.hooksPath']);
const existingHooksPath = checkResult.status === 0 ? checkResult.stdout?.toString().trim() : '';
if (existingHooksPath && existingHooksPath !== target) {
if (
existingHooksPath &&
existingHooksPath !== target &&
existingHooksPath !== '.husky' &&
!existingHooksPath.startsWith('.husky/')
) {
return {
message: `core.hooksPath is already set to "${existingHooksPath}", skipping`,
isError: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Summary

Add `vp config` and `vp staged` as built-in commands. `vp config` is a unified configuration command that sets up git hooks (husky-compatible reimplementation, not a bundled dependency) and agent integration. `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.
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.

## Motivation

Expand All @@ -21,50 +21,129 @@ Pain points:

By building these capabilities into vite-plus, projects get pre-commit hooks with zero extra devDependencies. Both `vp create` and `vp migrate` set this up automatically.

## Command Syntax
## User Workflows

There are three distinct entry points, each with a different responsibility:

### New projects: `vp create`

`vp create` scaffolds a new project and optionally sets up the full hooks pipeline:

1. Prompts "Set up pre-commit hooks?" (default: yes)
2. If accepted, calls `installGitHooks()` which:
- Adds `"prepare": "vp config"` to `package.json`
- Adds `staged` config to `vite.config.ts`
- Creates `.vite-hooks/pre-commit` containing `vp staged`
- Runs `vp config --hooks-only` to install hook shims and set `core.hooksPath`

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

### Existing projects: `vp migrate`

`vp migrate` migrates from husky/lint-staged and sets up the full hooks pipeline:

1. Runs pre-flight checks (husky version, other tools, subdirectory detection)
2. Prompts "Set up pre-commit hooks?" (default: yes)
3. If accepted, after migration rewrite, calls `installGitHooks()` which:
- Detects old husky directory from `scripts.prepare`
- Rewrites `"prepare": "husky"` → `"prepare": "vp config"` via `rewritePrepareScript()`
- Migrates `lint-staged` config from `package.json` to `staged` in `vite.config.ts`
- Copies `.husky/` hooks to `.vite-hooks/` (or preserves custom dir)
- Creates `.vite-hooks/pre-commit` containing `vp staged`
- Runs `vp config --hooks-only` to install hook shims and set `core.hooksPath`
- Removes husky and lint-staged from `devDependencies`

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

### Ongoing use: `vp config` (prepare lifecycle)

`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`.

```json
{ "scripts": { "prepare": "vp config" } }
```

When `npm_lifecycle_event=prepare` (set by npm/pnpm/yarn during `npm install`), agent setup is skipped automatically — only hooks are reinstalled.

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

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

1. **Add prepare script** to `package.json`:
```json
{ "scripts": { "prepare": "vp config" } }
```
2. **Add staged config** to `vite.config.ts`:
```typescript
export default defineConfig({
staged: { '*': 'vp check --fix' },
});
```
3. **Create pre-commit hook** at `.vite-hooks/pre-commit`:
```sh
vp staged
```
4. **Run `vp config`** to install hook shims and set `core.hooksPath`

## Commands

### `vp config`

```bash
# Configure project (hooks + agent integration)
vp config
vp config # Configure project (hooks + agent integration)
vp config -h # Show help
vp config --hooks-dir .husky # Custom hooks directory (default: .vite-hooks)
```

# Run staged linters on staged files (runs bundled lint-staged with staged config)
vp staged
Behavior:

# Control hooks setup during create/migrate
vp create --hooks # Force hooks setup
vp create --no-hooks # Skip hooks setup
vp migrate --hooks # Force hooks setup
vp migrate --no-hooks # Skip hooks setup
1. Built-in husky-compatible install logic (reimplementation of husky v9, not a bundled dependency)
2. Sets `core.hooksPath` to `<hooks-dir>/_` (default: `.vite-hooks/_`)
3. Creates hook scripts in `<hooks-dir>/_/` that source the user-defined hooks in `<hooks-dir>/`
4. Agent integration: injects agent instructions and MCP config (skipped during `prepare` lifecycle — see point 9)
5. Safe to run multiple times (idempotent)
6. Exits 0 and skips hooks if `VITE_GIT_HOOKS=0` or `HUSKY=0` environment variable is set (backwards compatible)
7. Exits 0 and skips hooks if `.git` directory doesn't exist (safe during `npm install` in consumer projects)
8. Exits 1 on real errors (git command not found, `git config` failed)
9. `prepare` lifecycle detection: when `npm_lifecycle_event=prepare`, agent setup is skipped. This ensures `"prepare": "vp config"` only installs hooks during install — agent setup is handled by `vp create`/`vp migrate`
10. Interactive mode: prompts on first run for hooks and agent setup; updates silently on subsequent runs
11. Non-interactive mode: runs everything by default

### `vp staged`

```bash
vp staged # Run staged linters on staged files
```

Behavior:

1. Reads config from `staged` key in `vite.config.ts` via `resolveConfig()`
2. If `staged` key not found, exits with a warning and setup instructions
3. Passes config to bundled lint-staged via its programmatic API
4. Runs configured commands on git-staged files only
5. Exits with non-zero code if any command fails
6. Does not support custom config file paths — config must be in `vite.config.ts`

Both commands are listed under "Core Commands" in `vp -h` (global and local CLI).

## User-Facing Configuration
## Configuration

### vite.config.ts + package.json (zero extra devDependencies)
### `vite.config.ts`

```typescript
// vite.config.ts
export default defineConfig({
staged: {
'*': 'vp check --fix',
},
});
```

```json
// package.json (new project)
{
"scripts": {
"prepare": "vp config"
}
}
```
`vp staged` reads config from the `staged` key via Vite's `resolveConfig()`. If no `staged` key is found, it exits with a warning and instructions to add the config. Standalone config files (`.lintstagedrc.*`, `lint-staged.config.*`) are not supported by the migration — projects using those formats are warned to migrate manually.

### `package.json`

```json
// package.json (migrated from husky — default .husky dir migrates to .vite-hooks)
// New project
{
"scripts": {
"prepare": "vp config"
Expand All @@ -73,7 +152,7 @@ export default defineConfig({
```

```json
// package.json (migrated from husky with custom dir — dir is preserved)
// Migrated from husky with custom dir — dir is preserved
{
"scripts": {
"prepare": "vp config --hooks-dir .config/husky"
Expand All @@ -91,7 +170,7 @@ If the project already has a prepare script, `vp config` is prepended:
}
```

### .vite-hooks/pre-commit (or custom dir for projects with non-default husky dir)
### `.vite-hooks/pre-commit`

```
vp staged
Expand All @@ -101,36 +180,7 @@ vp staged

`vp check --fix` already handles unsupported file types gracefully (it only processes files that match known extensions). Using `*` simplifies the configuration — no need to maintain a list of extensions.

### Config Discovery

`vp staged` reads config from the `staged` key in `vite.config.ts` via Vite's `resolveConfig()`. If no `staged` key is found, it exits with a warning and instructions to add the config. Standalone config files (`.lintstagedrc.*`, `lint-staged.config.*`) are not supported by the migration — projects using those formats are warned to migrate manually.

## Behavior

### `vp config`

1. Built-in husky-compatible install logic (reimplementation of husky v9, not a bundled dependency)
2. Sets `core.hooksPath` to `<hooks-dir>/_` (default: `.vite-hooks/_`)
3. Creates hook scripts in `<hooks-dir>/_/` that source the user-defined hooks in `<hooks-dir>/`
4. Agent integration: injects agent instructions and MCP config (skipped during `prepare` lifecycle — see point 11)
5. Safe to run multiple times (idempotent)
6. Exits 0 and skips hooks if `VITE_GIT_HOOKS=0` or `HUSKY=0` environment variable is set (backwards compatible)
7. Exits 0 and skips hooks if `.git` directory doesn't exist (safe during `npm install` in consumer projects)
8. Exits 1 on real errors (git command not found, `git config` failed)
9. Interactive mode: prompts on first run for hooks and agent setup; updates silently on subsequent runs
10. Non-interactive mode: runs everything by default
11. `prepare` lifecycle detection: when `npm_lifecycle_event=prepare` (set by npm/pnpm/yarn during `npm install`), agent setup is skipped automatically. This ensures `"prepare": "vp config"` only installs hooks during install — agent setup is a one-time operation handled by `vp create`/`vp migrate`, not repeated on every `npm install`

### `vp staged`

1. Reads config from `staged` key in `vite.config.ts` via `resolveConfig()`
2. If `staged` key not found, exits with a warning and setup instructions
3. Passes config to bundled lint-staged via its programmatic API
4. Runs configured commands on git-staged files only
5. Exits with non-zero code if any command fails
6. Does not support custom config file paths — config must be in vite.config.ts

### Automatic Setup
## Automatic Setup

Both `vp create` and `vp migrate` prompt the user before setting up pre-commit hooks:

Expand All @@ -139,14 +189,21 @@ Both `vp create` and `vp migrate` prompt the user before setting up pre-commit h
- **`--hooks` flag**: Force hooks setup (no prompt)
- **`--no-hooks` flag**: Skip hooks setup entirely (no prompt)

#### `vp create`
```bash
vp create --hooks # Force hooks setup
vp create --no-hooks # Skip hooks setup
vp migrate --hooks # Force hooks setup
vp migrate --no-hooks # Skip hooks setup
```

### `vp create`

- After project creation and migration rewrite, prompts for hooks setup
- If accepted, calls `rewritePrepareScript()` then `setupGitHooks()` — same as `vp migrate`
- `rewritePrepareScript()` rewrites any template-provided `"prepare": "husky"` to `"prepare": "vp config"` before `setupGitHooks()` runs
- Creates `.vite-hooks/pre-commit` with `vp staged`

#### `vp migrate`
### `vp migrate`

Migration rewrite (`rewritePackageJson`) uses `vite-tools.yml` rules to rewrite tool commands (vite, oxlint, vitest, etc.) in all scripts. Crucially, the husky rule is **not** in `vite-tools.yml` — it lives in a separate `vite-prepare.yml` and is only applied to `scripts.prepare` via `rewritePrepareScript()`. This ensures husky is never accidentally rewritten in non-prepare scripts.

Expand All @@ -163,10 +220,14 @@ Hook setup behavior:
- **Has `husky install`** — `rewritePrepareScript()` collapses `"husky install"` → `"husky"` before applying the ast-grep rule, so `"husky install .hooks"` becomes `"vp config --hooks-dir .hooks"` (custom dir preserved)
- **Has existing prepare script** (e.g. `"npm run build"`) — composes as `"vp config && npm run build"` (prepend so hooks are active before other prepare tasks; idempotent if already contains `vp config`)
- **Has lint-staged** — migrates `"lint-staged"` key to `staged` in vite.config.ts, keeps existing config (already rewritten by migration rules), removes lint-staged from devDeps

## Migration Edge Cases

- **Has husky <9.0.0** — detected **before** migration rewrite. Warns "please upgrade to husky v9+ first", skips hooks setup, and also skips lint-staged migration (`skipStagedMigration` flag). This preserves the `lint-staged` config in package.json and standalone config files, since `.husky/pre-commit` still references `npx lint-staged`.
- **Has other tool (simple-git-hooks, lefthook, yorkie)** — warns and skips
- **Subdirectory project** (e.g. `vp migrate foo`) — if the project path differs from the git root, warns "Subdirectory project detected" and skips hooks setup entirely. This prevents `vp config` from setting `core.hooksPath` to a subdirectory path, which would hijack the repo-wide hooks.
- **No .git directory** — adds package.json config and creates hook pre-commit file, but skips `vp config` hook install (no `core.hooksPath` to set)
- **Standalone lint-staged config** (`.lintstagedrc.*`, `lint-staged.config.*`) — not supported by auto-migration. Projects using those formats are warned to migrate manually.
- After creating the pre-commit hook, runs `vp config` directly to install hook shims (does not rely on npm install lifecycle, which may not run in CI or snap test contexts)

## Implementation Architecture
Expand Down Expand Up @@ -218,7 +279,7 @@ Husky <9.0.0 is not supported by auto migration — `vp migrate` detects unsuppo
| ---------------- | -------------------------------------- | --------------------------- |
| `vp check` | Format + lint + type check | Manual or CI |
| `vp check --fix` | Auto-fix format + lint issues | Manual or pre-commit |
| **`vp config`** | **Configure project (hooks + agent)** | **npm `prepare` lifecycle** |
| **`vp config`** | **Reinstall hook shims + agent setup** | **npm `prepare` lifecycle** |
| **`vp staged`** | **Run staged linters on staged files** | **Pre-commit hook** |

## Comparison with Other Tools
Expand Down
Loading