diff --git a/packages/cli/snap-tests-global/migration-husky-latest-dist-tag-v9-installed/.husky/pre-commit b/packages/cli/snap-tests-global/migration-husky-latest-dist-tag-v9-installed/.husky/pre-commit new file mode 100644 index 0000000000..cb2c84d5c3 --- /dev/null +++ b/packages/cli/snap-tests-global/migration-husky-latest-dist-tag-v9-installed/.husky/pre-commit @@ -0,0 +1 @@ +pnpm lint-staged diff --git a/packages/cli/snap-tests-global/migration-husky-latest-dist-tag-v9-installed/node_modules/husky/package.json b/packages/cli/snap-tests-global/migration-husky-latest-dist-tag-v9-installed/node_modules/husky/package.json new file mode 100644 index 0000000000..6f2ba0e24a --- /dev/null +++ b/packages/cli/snap-tests-global/migration-husky-latest-dist-tag-v9-installed/node_modules/husky/package.json @@ -0,0 +1,4 @@ +{ + "name": "husky", + "version": "9.1.7" +} diff --git a/packages/cli/snap-tests-global/migration-husky-latest-dist-tag-v9-installed/package.json b/packages/cli/snap-tests-global/migration-husky-latest-dist-tag-v9-installed/package.json new file mode 100644 index 0000000000..0b66c6d1d5 --- /dev/null +++ b/packages/cli/snap-tests-global/migration-husky-latest-dist-tag-v9-installed/package.json @@ -0,0 +1,14 @@ +{ + "name": "migration-husky-latest-dist-tag-v9-installed", + "scripts": { + "prepare": "husky" + }, + "devDependencies": { + "husky": "latest", + "lint-staged": "^16.2.6", + "vite": "^7.0.0" + }, + "lint-staged": { + "*.js": "oxlint --fix" + } +} diff --git a/packages/cli/snap-tests-global/migration-husky-latest-dist-tag-v9-installed/snap.txt b/packages/cli/snap-tests-global/migration-husky-latest-dist-tag-v9-installed/snap.txt new file mode 100644 index 0000000000..3ca53f7df1 --- /dev/null +++ b/packages/cli/snap-tests-global/migration-husky-latest-dist-tag-v9-installed/snap.txt @@ -0,0 +1,27 @@ +> git init +> vp migrate --no-interactive # should resolve husky v9 from node_modules, no warning +VITE+ - The Unified Toolchain for the Web + +◇ Migrated . to Vite+ +• Node pnpm +• 2 config updates applied +• Git hooks configured + +> cat package.json # husky and lint-staged should be removed +{ + "name": "migration-husky-latest-dist-tag-v9-installed", + "scripts": { + "prepare": "vp config" + }, + "devDependencies": { + "vite": "npm:@voidzero-dev/vite-plus-core@latest", + "vite-plus": "latest" + }, + "pnpm": { + "overrides": { + "vite": "npm:@voidzero-dev/vite-plus-core@latest", + "vitest": "npm:@voidzero-dev/vite-plus-test@latest" + } + }, + "packageManager": "pnpm@" +} diff --git a/packages/cli/snap-tests-global/migration-husky-latest-dist-tag-v9-installed/steps.json b/packages/cli/snap-tests-global/migration-husky-latest-dist-tag-v9-installed/steps.json new file mode 100644 index 0000000000..1d17f7cb11 --- /dev/null +++ b/packages/cli/snap-tests-global/migration-husky-latest-dist-tag-v9-installed/steps.json @@ -0,0 +1,7 @@ +{ + "commands": [ + { "command": "git init", "ignoreOutput": true }, + "vp migrate --no-interactive # should resolve husky v9 from node_modules, no warning", + "cat package.json # husky and lint-staged should be removed" + ] +} diff --git a/packages/cli/snap-tests-global/migration-husky-latest-dist-tag/.husky/pre-commit b/packages/cli/snap-tests-global/migration-husky-latest-dist-tag/.husky/pre-commit new file mode 100755 index 0000000000..cb2c84d5c3 --- /dev/null +++ b/packages/cli/snap-tests-global/migration-husky-latest-dist-tag/.husky/pre-commit @@ -0,0 +1 @@ +pnpm lint-staged diff --git a/packages/cli/snap-tests-global/migration-husky-latest-dist-tag/package.json b/packages/cli/snap-tests-global/migration-husky-latest-dist-tag/package.json new file mode 100644 index 0000000000..33842ad2e1 --- /dev/null +++ b/packages/cli/snap-tests-global/migration-husky-latest-dist-tag/package.json @@ -0,0 +1,10 @@ +{ + "name": "migration-husky-latest-dist-tag", + "scripts": { + "prepare": "husky" + }, + "devDependencies": { + "husky": "latest", + "vite": "^7.0.0" + } +} diff --git a/packages/cli/snap-tests-global/migration-husky-latest-dist-tag/snap.txt b/packages/cli/snap-tests-global/migration-husky-latest-dist-tag/snap.txt new file mode 100644 index 0000000000..116f8a51c1 --- /dev/null +++ b/packages/cli/snap-tests-global/migration-husky-latest-dist-tag/snap.txt @@ -0,0 +1,29 @@ +> git init +> vp migrate --no-interactive # should warn about uncoercible husky version +VITE+ - The Unified Toolchain for the Web + + +⚠ Could not determine husky version from "latest" — please specify a semver-compatible version (e.g., "^9.0.0") and re-run migration. +◇ Migrated . to Vite+ +• Node pnpm +• 1 config update applied + +> cat package.json # husky should still be in devDeps +{ + "name": "migration-husky-latest-dist-tag", + "scripts": { + "prepare": "husky" + }, + "devDependencies": { + "husky": "latest", + "vite": "npm:@voidzero-dev/vite-plus-core@latest", + "vite-plus": "latest" + }, + "pnpm": { + "overrides": { + "vite": "npm:@voidzero-dev/vite-plus-core@latest", + "vitest": "npm:@voidzero-dev/vite-plus-test@latest" + } + }, + "packageManager": "pnpm@" +} diff --git a/packages/cli/snap-tests-global/migration-husky-latest-dist-tag/steps.json b/packages/cli/snap-tests-global/migration-husky-latest-dist-tag/steps.json new file mode 100644 index 0000000000..33fe726efc --- /dev/null +++ b/packages/cli/snap-tests-global/migration-husky-latest-dist-tag/steps.json @@ -0,0 +1,7 @@ +{ + "commands": [ + { "command": "git init", "ignoreOutput": true }, + "vp migrate --no-interactive # should warn about uncoercible husky version", + "cat package.json # husky should still be in devDeps" + ] +} diff --git a/packages/cli/src/migration/migrator.ts b/packages/cli/src/migration/migrator.ts index 0932320f27..7d55e73501 100644 --- a/packages/cli/src/migration/migrator.ts +++ b/packages/cli/src/migration/migrator.ts @@ -1510,18 +1510,34 @@ function rewriteAllImports(projectPath: string, silent = false, report?: Migrati /** * Check if the project has an unsupported husky version (<9.0.0). * Uses `semver.coerce` to handle ranges like `^8.0.0` → `8.0.0`. - * Accepts pre-loaded deps to avoid re-reading package.json when called - * from contexts that already parsed it. + * When the specifier is not coercible (e.g. `"latest"`), falls back to + * the installed version in node_modules via `detectPackageMetadata`. + * Returns a reason string if hooks migration should be skipped, or null + * if husky is absent or compatible. */ function checkUnsupportedHuskyVersion( + projectPath: string, deps: Record | undefined, prodDeps: Record | undefined, -): boolean { +): string | null { const huskyVersion = deps?.husky ?? prodDeps?.husky; if (!huskyVersion) { - return false; + return null; + } + let coerced = semver.coerce(huskyVersion); + if (coerced == null) { + const installed = detectPackageMetadata(projectPath, 'husky'); + if (installed) { + coerced = semver.coerce(installed.version); + } + if (coerced == null) { + return `Could not determine husky version from "${huskyVersion}" — please specify a semver-compatible version (e.g., "^9.0.0") and re-run migration.`; + } + } + if (semver.satisfies(coerced, '<9.0.0')) { + return 'Detected husky <9.0.0 — please upgrade to husky v9+ first, then re-run migration.'; } - return semver.satisfies(semver.coerce(huskyVersion) ?? '0.0.0', '<9.0.0'); + return null; } const OTHER_HOOK_TOOLS = ['simple-git-hooks', 'lefthook', 'yorkie'] as const; @@ -1635,8 +1651,9 @@ export function preflightGitHooksSetup(projectPath: string): string | null { return `Detected ${tool} — skipping git hooks setup. Please configure git hooks manually.`; } } - if (checkUnsupportedHuskyVersion(deps, prodDeps)) { - return 'Detected husky <9.0.0 — please upgrade to husky v9+ first, then re-run migration.'; + const huskyReason = checkUnsupportedHuskyVersion(projectPath, deps, prodDeps); + if (huskyReason) { + return huskyReason; } if (hasUnsupportedLintStagedConfig(projectPath)) { return 'Unsupported lint-staged config format — skipping git hooks setup. Please configure git hooks manually.';