Skip to content

Commit 71b90b4

Browse files
authored
fix(migrate): warn on uncoercible husky version instead of false positive (#1124)
## Summary - Fix false positive "husky <9.0.0" warning in `vp migrate` when husky version is a dist-tag (`"latest"`, `"next"`) or other non-semver string - When `semver.coerce()` returns `null`, warn the user to specify a semver-compatible version instead of silently falling back to `'0.0.0'` - Change `checkUnsupportedHuskyVersion` return type from `boolean` to `string | null` to co-locate message logic with version checking Closes #1114
1 parent 01693e3 commit 71b90b4

10 files changed

Lines changed: 124 additions & 7 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pnpm lint-staged

packages/cli/snap-tests-global/migration-husky-latest-dist-tag-v9-installed/node_modules/husky/package.json

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"name": "migration-husky-latest-dist-tag-v9-installed",
3+
"scripts": {
4+
"prepare": "husky"
5+
},
6+
"devDependencies": {
7+
"husky": "latest",
8+
"lint-staged": "^16.2.6",
9+
"vite": "^7.0.0"
10+
},
11+
"lint-staged": {
12+
"*.js": "oxlint --fix"
13+
}
14+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
> git init
2+
> vp migrate --no-interactive # should resolve husky v9 from node_modules, no warning
3+
VITE+ - The Unified Toolchain for the Web
4+
5+
◇ Migrated . to Vite+<repeat>
6+
• Node <semver> pnpm <semver>
7+
• 2 config updates applied
8+
• Git hooks configured
9+
10+
> cat package.json # husky and lint-staged should be removed
11+
{
12+
"name": "migration-husky-latest-dist-tag-v9-installed",
13+
"scripts": {
14+
"prepare": "vp config"
15+
},
16+
"devDependencies": {
17+
"vite": "npm:@voidzero-dev/vite-plus-core@latest",
18+
"vite-plus": "latest"
19+
},
20+
"pnpm": {
21+
"overrides": {
22+
"vite": "npm:@voidzero-dev/vite-plus-core@latest",
23+
"vitest": "npm:@voidzero-dev/vite-plus-test@latest"
24+
}
25+
},
26+
"packageManager": "pnpm@<semver>"
27+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"commands": [
3+
{ "command": "git init", "ignoreOutput": true },
4+
"vp migrate --no-interactive # should resolve husky v9 from node_modules, no warning",
5+
"cat package.json # husky and lint-staged should be removed"
6+
]
7+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pnpm lint-staged
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"name": "migration-husky-latest-dist-tag",
3+
"scripts": {
4+
"prepare": "husky"
5+
},
6+
"devDependencies": {
7+
"husky": "latest",
8+
"vite": "^7.0.0"
9+
}
10+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
> git init
2+
> vp migrate --no-interactive # should warn about uncoercible husky version
3+
VITE+ - The Unified Toolchain for the Web
4+
5+
6+
⚠ Could not determine husky version from "latest" — please specify a semver-compatible version (e.g., "^9.0.0") and re-run migration.
7+
◇ Migrated . to Vite+<repeat>
8+
• Node <semver> pnpm <semver>
9+
• 1 config update applied
10+
11+
> cat package.json # husky should still be in devDeps
12+
{
13+
"name": "migration-husky-latest-dist-tag",
14+
"scripts": {
15+
"prepare": "husky"
16+
},
17+
"devDependencies": {
18+
"husky": "latest",
19+
"vite": "npm:@voidzero-dev/vite-plus-core@latest",
20+
"vite-plus": "latest"
21+
},
22+
"pnpm": {
23+
"overrides": {
24+
"vite": "npm:@voidzero-dev/vite-plus-core@latest",
25+
"vitest": "npm:@voidzero-dev/vite-plus-test@latest"
26+
}
27+
},
28+
"packageManager": "pnpm@<semver>"
29+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"commands": [
3+
{ "command": "git init", "ignoreOutput": true },
4+
"vp migrate --no-interactive # should warn about uncoercible husky version",
5+
"cat package.json # husky should still be in devDeps"
6+
]
7+
}

packages/cli/src/migration/migrator.ts

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1510,18 +1510,34 @@ function rewriteAllImports(projectPath: string, silent = false, report?: Migrati
15101510
/**
15111511
* Check if the project has an unsupported husky version (<9.0.0).
15121512
* Uses `semver.coerce` to handle ranges like `^8.0.0` → `8.0.0`.
1513-
* Accepts pre-loaded deps to avoid re-reading package.json when called
1514-
* from contexts that already parsed it.
1513+
* When the specifier is not coercible (e.g. `"latest"`), falls back to
1514+
* the installed version in node_modules via `detectPackageMetadata`.
1515+
* Returns a reason string if hooks migration should be skipped, or null
1516+
* if husky is absent or compatible.
15151517
*/
15161518
function checkUnsupportedHuskyVersion(
1519+
projectPath: string,
15171520
deps: Record<string, string> | undefined,
15181521
prodDeps: Record<string, string> | undefined,
1519-
): boolean {
1522+
): string | null {
15201523
const huskyVersion = deps?.husky ?? prodDeps?.husky;
15211524
if (!huskyVersion) {
1522-
return false;
1525+
return null;
1526+
}
1527+
let coerced = semver.coerce(huskyVersion);
1528+
if (coerced == null) {
1529+
const installed = detectPackageMetadata(projectPath, 'husky');
1530+
if (installed) {
1531+
coerced = semver.coerce(installed.version);
1532+
}
1533+
if (coerced == null) {
1534+
return `Could not determine husky version from "${huskyVersion}" — please specify a semver-compatible version (e.g., "^9.0.0") and re-run migration.`;
1535+
}
1536+
}
1537+
if (semver.satisfies(coerced, '<9.0.0')) {
1538+
return 'Detected husky <9.0.0 — please upgrade to husky v9+ first, then re-run migration.';
15231539
}
1524-
return semver.satisfies(semver.coerce(huskyVersion) ?? '0.0.0', '<9.0.0');
1540+
return null;
15251541
}
15261542

15271543
const OTHER_HOOK_TOOLS = ['simple-git-hooks', 'lefthook', 'yorkie'] as const;
@@ -1635,8 +1651,9 @@ export function preflightGitHooksSetup(projectPath: string): string | null {
16351651
return `Detected ${tool} — skipping git hooks setup. Please configure git hooks manually.`;
16361652
}
16371653
}
1638-
if (checkUnsupportedHuskyVersion(deps, prodDeps)) {
1639-
return 'Detected husky <9.0.0 — please upgrade to husky v9+ first, then re-run migration.';
1654+
const huskyReason = checkUnsupportedHuskyVersion(projectPath, deps, prodDeps);
1655+
if (huskyReason) {
1656+
return huskyReason;
16401657
}
16411658
if (hasUnsupportedLintStagedConfig(projectPath)) {
16421659
return 'Unsupported lint-staged config format — skipping git hooks setup. Please configure git hooks manually.';

0 commit comments

Comments
 (0)