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-prepare-auto-hooks"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
> git init
> vp config # should install hooks automatically without prompting
> git config --local core.hooksPath # should be .vite-hooks/_
.vite-hooks/_

> cat .vite-hooks/pre-commit # should have vp staged
vp staged

> cat vite.config.ts # should have staged config
import { defineConfig } from 'vite-plus';

export default defineConfig({
staged: {
"*": "vp check --fix"
},

});

> vp config # run again to ensure idempotent
> cat .vite-hooks/pre-commit # should remain unchanged
vp staged

> cat vite.config.ts # should remain unchanged
import { defineConfig } from 'vite-plus';

export default defineConfig({
staged: {
"*": "vp check --fix"
},

});
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"env": {
"npm_lifecycle_event": "prepare"
},
"commands": [
{ "command": "git init", "ignoreOutput": true },
"vp config # should install hooks automatically without prompting",
"git config --local core.hooksPath # should be .vite-hooks/_",
"cat .vite-hooks/pre-commit # should have vp staged",
"cat vite.config.ts # should have staged config",
"vp config # run again to ensure idempotent",
"cat .vite-hooks/pre-commit # should remain unchanged",
"cat vite.config.ts # should remain unchanged"
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,19 @@ VITE+ - The Unified Toolchain for the Web

◇ Migrated . to Vite+<repeat>
• Node <semver> pnpm <semver>
• Git hooks configured
! Warnings:
- Failed to merge staged config into vite.config.ts
- Git hooks not configured — Failed to merge staged config into vite.config.ts

Please add staged config to vite.config.ts manually, see https://viteplus.dev/guide/migrate#lint-staged
→ Manual follow-up:
- Please add staged config to vite.config.ts manually, see https://viteplus.dev/guide/migrate#lint-staged

> cat package.json # lint-staged config should be preserved when merge fails
{
"name": "migration-lint-staged-merge-fail",
"devDependencies": {
"lint-staged": "^16.2.6",
"vite": "npm:@voidzero-dev/vite-plus-core@latest",
"vite-plus": "latest"
},
Expand All @@ -37,4 +40,4 @@ const config = { plugins: [] };
module.exports = config;

> test -f .vite-hooks/pre-commit && echo 'pre-commit hook exists' || echo 'no pre-commit hook' # should NOT exist when merge fails
no pre-commit hook
pre-commit hook exists
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,19 @@ VITE+ - The Unified Toolchain for the Web

◇ Migrated . to Vite+<repeat>
• Node <semver> pnpm <semver>
• Git hooks configured
! Warnings:
- Failed to merge staged config into vite.config.ts
- Git hooks not configured — Failed to merge staged config into vite.config.ts

Please add staged config to vite.config.ts manually, see https://viteplus.dev/guide/migrate#lint-staged
→ Manual follow-up:
- Please add staged config to vite.config.ts manually, see https://viteplus.dev/guide/migrate#lint-staged

> cat package.json # check package.json
{
"name": "migration-lintstagedrc-merge-fail",
"devDependencies": {
"lint-staged": "^16.2.6",
"vite": "npm:@voidzero-dev/vite-plus-core@latest",
"vite-plus": "latest"
},
Expand All @@ -39,4 +42,4 @@ const config = { plugins: [] };
module.exports = config;

> test -f .vite-hooks/pre-commit && echo 'pre-commit hook exists' || echo 'no pre-commit hook' # should NOT exist when merge fails
no pre-commit hook
pre-commit hook exists
11 changes: 10 additions & 1 deletion packages/cli/src/config/bin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { join } from 'node:path';
import mri from 'mri';

import { vitePlusHeader } from '../../binding/index.js';
import { ensurePreCommitHook } from '../migration/migrator.js';
import { updateExistingAgentInstructions } from '../utils/agent.js';
import { renderCliDoc } from '../utils/help.js';
import { defaultInteractive, promptGitHooks } from '../utils/prompts.js';
Expand Down Expand Up @@ -53,15 +54,17 @@ async function main() {
const dir = args['hooks-dir'] as string | undefined;
const hooksOnly = args['hooks-only'] as boolean;
const interactive = defaultInteractive();
const isPrepareScript = process.env.npm_lifecycle_event === 'prepare';
const root = process.cwd();

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

let shouldSetupHooks = true;
if (interactive && isFirstHooksRun && !dir) {
if (interactive && isFirstHooksRun && !dir && !isPrepareScript) {
// --hooks-dir implies agreement; only prompt when using default dir on first run
// prepare script implies the project opted into hooks — install automatically
shouldSetupHooks = await promptGitHooks({ interactive });
}

Expand All @@ -73,6 +76,12 @@ async function main() {
process.exit(1);
}
}

// Only create pre-commit hook when install() succeeded (empty message).
// Skip when hooks were disabled or git is unavailable.
if (!message) {
ensurePreCommitHook(root, hooksDir);
}
}

// --- Step 2: Update agent instructions if Vite+ header exists and is outdated ---
Expand Down
20 changes: 17 additions & 3 deletions packages/cli/src/migration/migrator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1394,7 +1394,7 @@ function mergeAndRemoveJsonConfig(
* Merge a staged config object into vite.config.ts as `staged: { ... }`.
* Writes the config to a temp JSON file, calls mergeJsonConfig NAPI, then cleans up.
*/
function mergeStagedConfigToViteConfig(
export function mergeStagedConfigToViteConfig(
projectPath: string,
stagedConfig: Record<string, string | string[]>,
silent = false,
Expand Down Expand Up @@ -1440,7 +1440,7 @@ function mergeStagedConfigToViteConfig(
/**
* Check if vite.config.ts already has a `staged` config key.
*/
function hasStagedConfigInViteConfig(projectPath: string): boolean {
export function hasStagedConfigInViteConfig(projectPath: string): boolean {
const configs = detectConfigs(projectPath);
if (!configs.viteConfig) {
return false;
Expand Down Expand Up @@ -1689,7 +1689,7 @@ export function setupGitHooks(
const pkgData = readJsonFile<{ 'lint-staged'?: Record<string, string | string[]> }>(
packageJsonPath,
);
const stagedConfig = pkgData?.['lint-staged'] ?? { '*': 'vp check --fix' };
const stagedConfig = pkgData?.['lint-staged'] ?? DEFAULT_STAGED_CONFIG;
const updated = rewriteScripts(JSON.stringify(stagedConfig), readRulesYaml());
const finalConfig: Record<string, string | string[]> = updated
? JSON.parse(updated)
Expand Down Expand Up @@ -1822,6 +1822,20 @@ const STALE_LINT_STAGED_PATTERNS = [
/^((?:[A-Z_][A-Z0-9_]*(?:=\S*)?\s+)*)lint-staged\b/,
];

const DEFAULT_STAGED_CONFIG: Record<string, string> = { '*': 'vp check --fix' };

/**
* Ensure the pre-commit hook exists with `vp staged`, and that
* vite.config.ts contains a `staged` block (using the default config
* if none is present). Called by `vp config` after hook installation.
*/
export function ensurePreCommitHook(projectPath: string, dir = '.vite-hooks'): void {
if (!hasStagedConfigInViteConfig(projectPath)) {
mergeStagedConfigToViteConfig(projectPath, DEFAULT_STAGED_CONFIG, true);
}
createPreCommitHook(projectPath, dir);
}

export function createPreCommitHook(projectPath: string, dir = '.vite-hooks'): void {
const huskyDir = path.join(projectPath, dir);
fs.mkdirSync(huskyDir, { recursive: true });
Expand Down
Loading