From 059ead8a2c1b1c9d9b032d66f86edcf01ec8b463 Mon Sep 17 00:00:00 2001 From: Liang Date: Mon, 19 Oct 2026 22:49:14 +0800 Subject: [PATCH] fix(migrate): keep existing .nvmrc pins --- .../fixtures/migration_nvmrc/.nvmrc | 1 - .../fixtures/migration_nvmrc/package.json | 6 - .../fixtures/migration_nvmrc/snapshots.toml | 8 - .../snapshots/migration_nvmrc.md | 30 --- .../fixtures/migration_nvmrc_lts/.nvmrc | 1 - .../fixtures/migration_nvmrc_lts/package.json | 6 - .../migration_nvmrc_lts/snapshots.toml | 8 - .../snapshots/migration_nvmrc_lts.md | 36 ---- .../migration_nvmrc_node_alias/.nvmrc | 1 - .../migration_nvmrc_node_alias/package.json | 6 - .../migration_nvmrc_node_alias/snapshots.toml | 8 - .../snapshots/migration_nvmrc_node_alias.md | 31 ---- .../migration_upgrade_hooks_flag_pnpm/.nvmrc | 1 - .../package.json | 3 + .../snapshots.toml | 3 +- .../migration_upgrade_hooks_flag_pnpm.md | 10 +- .../migration_upgrade_setup_full_pnpm/.nvmrc | 1 - .../package.json | 3 + .../snapshots.toml | 3 +- .../migration_upgrade_setup_full_pnpm.md | 15 +- .../.nvmrc | 1 - .../package.json | 3 + .../snapshots.toml | 3 +- ...tion_upgrade_setup_skipped_default_pnpm.md | 13 +- .../migration_volta_with_nvmrc/snapshots.toml | 6 +- .../snapshots/migration_volta_with_nvmrc.md | 17 +- docs/guide/migrate-rules.md | 7 +- .../src/migration/__tests__/migrator.spec.ts | 175 +----------------- packages/cli/src/migration/bin.ts | 1 - packages/cli/src/migration/migrator/README.md | 2 +- packages/cli/src/migration/migrator/setup.ts | 172 ++--------------- 31 files changed, 56 insertions(+), 525 deletions(-) delete mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_nvmrc/.nvmrc delete mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_nvmrc/package.json delete mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_nvmrc/snapshots.toml delete mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_nvmrc/snapshots/migration_nvmrc.md delete mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_nvmrc_lts/.nvmrc delete mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_nvmrc_lts/package.json delete mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_nvmrc_lts/snapshots.toml delete mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_nvmrc_lts/snapshots/migration_nvmrc_lts.md delete mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_nvmrc_node_alias/.nvmrc delete mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_nvmrc_node_alias/package.json delete mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_nvmrc_node_alias/snapshots.toml delete mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_nvmrc_node_alias/snapshots/migration_nvmrc_node_alias.md delete mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_upgrade_hooks_flag_pnpm/.nvmrc delete mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_upgrade_setup_full_pnpm/.nvmrc delete mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_upgrade_setup_skipped_default_pnpm/.nvmrc diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_nvmrc/.nvmrc b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_nvmrc/.nvmrc deleted file mode 100644 index c519bf5baa..0000000000 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_nvmrc/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -v24.11.0 diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_nvmrc/package.json b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_nvmrc/package.json deleted file mode 100644 index da19acdd77..0000000000 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_nvmrc/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "migration-nvmrc", - "devDependencies": { - "vite": "^7.0.0" - } -} diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_nvmrc/snapshots.toml b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_nvmrc/snapshots.toml deleted file mode 100644 index 818ac8fe3a..0000000000 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_nvmrc/snapshots.toml +++ /dev/null @@ -1,8 +0,0 @@ -[[case]] -name = "migration_nvmrc" -vp = "global" -steps = [ - { argv = ["vp", "migrate", "--no-interactive"], comment = "migration should detect .nvmrc and auto-migrate to .node-version", continue-on-failure = true }, - { argv = ["vpt", "print-file", ".node-version"], comment = "check .node-version is created with v prefix stripped", continue-on-failure = true }, - { argv = ["vpt", "stat-file", ".nvmrc", "--assert-not", "file"], comment = "check .nvmrc is removed", continue-on-failure = true }, -] diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_nvmrc/snapshots/migration_nvmrc.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_nvmrc/snapshots/migration_nvmrc.md deleted file mode 100644 index a45814eb79..0000000000 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_nvmrc/snapshots/migration_nvmrc.md +++ /dev/null @@ -1,30 +0,0 @@ -# migration_nvmrc - -## `vp migrate --no-interactive` - -migration should detect .nvmrc and auto-migrate to .node-version - -``` -VITE+ - The Unified Toolchain for the Web - -◇ Migrated . to Vite+ -• Node pnpm -• 2 config updates applied -• Node version manager file migrated to .node-version -``` - -## `vpt print-file .node-version` - -check .node-version is created with v prefix stripped - -``` -24.11.0 -``` - -## `vpt stat-file .nvmrc --assert-not file` - -check .nvmrc is removed - -``` -.nvmrc: missing -``` diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_nvmrc_lts/.nvmrc b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_nvmrc_lts/.nvmrc deleted file mode 100644 index 9de2256827..0000000000 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_nvmrc_lts/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -lts/iron diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_nvmrc_lts/package.json b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_nvmrc_lts/package.json deleted file mode 100644 index 9f77e80435..0000000000 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_nvmrc_lts/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "migration-nvmrc-lts", - "devDependencies": { - "vite": "^7.0.0" - } -} diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_nvmrc_lts/snapshots.toml b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_nvmrc_lts/snapshots.toml deleted file mode 100644 index 7cc714fdbb..0000000000 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_nvmrc_lts/snapshots.toml +++ /dev/null @@ -1,8 +0,0 @@ -[[case]] -name = "migration_nvmrc_lts" -vp = "global" -steps = [ - { argv = ["vp", "migrate", "--no-interactive"], comment = "migration should detect .nvmrc with lts alias and auto-migrate", continue-on-failure = true }, - { argv = ["vpt", "print-file", ".node-version"], comment = "check lts alias is preserved as-is", continue-on-failure = true }, - { argv = ["vpt", "stat-file", ".nvmrc", "--assert-not", "file"], comment = "check .nvmrc is removed", continue-on-failure = true }, -] diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_nvmrc_lts/snapshots/migration_nvmrc_lts.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_nvmrc_lts/snapshots/migration_nvmrc_lts.md deleted file mode 100644 index c76ae3d7fb..0000000000 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_nvmrc_lts/snapshots/migration_nvmrc_lts.md +++ /dev/null @@ -1,36 +0,0 @@ -# migration_nvmrc_lts - -## `vp migrate --no-interactive` - -migration should detect .nvmrc with lts alias and auto-migrate - -``` -VITE+ - The Unified Toolchain for the Web - -◇ Migrated . to Vite+ -• Node pnpm -• 2 config updates applied -• Node version manager file migrated to .node-version -! Warnings: - - Vitest v5: 1 review item - -.node-version - 1:1 REVIEW [node-runtime] Resolve .node-version (lts/iron) and select Node ^22.18.0 || ^24.11.0 || >=26.0.0. - Docs: https://viteplus.dev/guide/vitest-v5#node-runtime -``` - -## `vpt print-file .node-version` - -check lts alias is preserved as-is - -``` -lts/iron -``` - -## `vpt stat-file .nvmrc --assert-not file` - -check .nvmrc is removed - -``` -.nvmrc: missing -``` diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_nvmrc_node_alias/.nvmrc b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_nvmrc_node_alias/.nvmrc deleted file mode 100644 index 64f5a0a681..0000000000 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_nvmrc_node_alias/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -node diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_nvmrc_node_alias/package.json b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_nvmrc_node_alias/package.json deleted file mode 100644 index aff9054c01..0000000000 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_nvmrc_node_alias/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "migration-nvmrc-node-alias", - "devDependencies": { - "vite": "^7.0.0" - } -} diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_nvmrc_node_alias/snapshots.toml b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_nvmrc_node_alias/snapshots.toml deleted file mode 100644 index c98411c0ec..0000000000 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_nvmrc_node_alias/snapshots.toml +++ /dev/null @@ -1,8 +0,0 @@ -[[case]] -name = "migration_nvmrc_node_alias" -vp = "global" -steps = [ - { argv = ["vp", "migrate", "--no-interactive"], comment = "'node' alias should be mapped to lts/* with an info message", continue-on-failure = true }, - { argv = ["vpt", "print-file", ".node-version"], comment = "check node alias is mapped to lts/*", continue-on-failure = true }, - { argv = ["vpt", "stat-file", ".nvmrc", "--assert-not", "file"], comment = "check .nvmrc is removed", continue-on-failure = true }, -] diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_nvmrc_node_alias/snapshots/migration_nvmrc_node_alias.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_nvmrc_node_alias/snapshots/migration_nvmrc_node_alias.md deleted file mode 100644 index bd6ead3196..0000000000 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_nvmrc_node_alias/snapshots/migration_nvmrc_node_alias.md +++ /dev/null @@ -1,31 +0,0 @@ -# migration_nvmrc_node_alias - -## `vp migrate --no-interactive` - -'node' alias should be mapped to lts/* with an info message - -``` -VITE+ - The Unified Toolchain for the Web - -● "node" in .nvmrc is not a specific version; automatically mapping to "lts/*" -◇ Migrated . to Vite+ -• Node pnpm -• 2 config updates applied -• Node version manager file migrated to .node-version -``` - -## `vpt print-file .node-version` - -check node alias is mapped to lts/* - -``` -lts/* -``` - -## `vpt stat-file .nvmrc --assert-not file` - -check .nvmrc is removed - -``` -.nvmrc: missing -``` diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_upgrade_hooks_flag_pnpm/.nvmrc b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_upgrade_hooks_flag_pnpm/.nvmrc deleted file mode 100644 index 0a492611a0..0000000000 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_upgrade_hooks_flag_pnpm/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -24.11.0 diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_upgrade_hooks_flag_pnpm/package.json b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_upgrade_hooks_flag_pnpm/package.json index 28c178d013..b30662e750 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_upgrade_hooks_flag_pnpm/package.json +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_upgrade_hooks_flag_pnpm/package.json @@ -11,5 +11,8 @@ "version": "10.33.0", "onFail": "download" } + }, + "volta": { + "node": "24.11.0" } } diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_upgrade_hooks_flag_pnpm/snapshots.toml b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_upgrade_hooks_flag_pnpm/snapshots.toml index c921f501bf..224d31d546 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_upgrade_hooks_flag_pnpm/snapshots.toml +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_upgrade_hooks_flag_pnpm/snapshots.toml @@ -6,6 +6,5 @@ steps = [ { argv = ["vp", "migrate", "--hooks", "--no-interactive"], comment = "existing Vite+ project: upgrade plus only the git hooks action", continue-on-failure = true }, { argv = ["git", "config", "--local", "core.hooksPath"], comment = "hooks configured to .vite-hooks/_", continue-on-failure = true }, { argv = ["vpt", "print-file", ".vite-hooks/pre-commit"], comment = "pre-commit hook runs vp staged", continue-on-failure = true }, - { argv = ["vpt", "stat-file", ".nvmrc", "--assert", "file"], comment = "node version file is not migrated without --full", continue-on-failure = true }, - { argv = ["vpt", "stat-file", ".node-version", "--assert-not", "file"], comment = "only the hooks action ran", continue-on-failure = true }, + { argv = ["vpt", "stat-file", ".node-version", "--assert-not", "file"], comment = "volta.node is not migrated without --full", continue-on-failure = true }, ] diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_upgrade_hooks_flag_pnpm/snapshots/migration_upgrade_hooks_flag_pnpm.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_upgrade_hooks_flag_pnpm/snapshots/migration_upgrade_hooks_flag_pnpm.md index 9686b0da9e..6974febf41 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_upgrade_hooks_flag_pnpm/snapshots/migration_upgrade_hooks_flag_pnpm.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_upgrade_hooks_flag_pnpm/snapshots/migration_upgrade_hooks_flag_pnpm.md @@ -36,17 +36,9 @@ pre-commit hook runs vp staged vp staged ``` -## `vpt stat-file .nvmrc --assert file` - -node version file is not migrated without --full - -``` -.nvmrc: file -``` - ## `vpt stat-file .node-version --assert-not file` -only the hooks action ran +volta.node is not migrated without --full ``` .node-version: missing diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_upgrade_setup_full_pnpm/.nvmrc b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_upgrade_setup_full_pnpm/.nvmrc deleted file mode 100644 index 0a492611a0..0000000000 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_upgrade_setup_full_pnpm/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -24.11.0 diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_upgrade_setup_full_pnpm/package.json b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_upgrade_setup_full_pnpm/package.json index a0f4b59778..0162d37ea7 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_upgrade_setup_full_pnpm/package.json +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_upgrade_setup_full_pnpm/package.json @@ -11,5 +11,8 @@ "version": "10.33.0", "onFail": "download" } + }, + "volta": { + "node": "24.11.0" } } diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_upgrade_setup_full_pnpm/snapshots.toml b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_upgrade_setup_full_pnpm/snapshots.toml index fd6de71313..f11cee387a 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_upgrade_setup_full_pnpm/snapshots.toml +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_upgrade_setup_full_pnpm/snapshots.toml @@ -6,7 +6,6 @@ steps = [ { argv = ["vpt", "chmod", "+x", "fix-baseurl.mjs"], comment = "stub the baseUrl fixer so the dlx stays offline", continue-on-failure = true }, { argv = ["vp", "migrate", "--full", "--no-interactive"], comment = "existing Vite+ project: upgrade plus the full setup", continue-on-failure = true }, { argv = ["vpt", "print-file", "package.json"], comment = "vite-plus version upgrade is applied", continue-on-failure = true }, - { argv = ["vpt", "print-file", ".node-version"], comment = ".nvmrc is migrated to .node-version by the full setup", continue-on-failure = true }, - { argv = ["vpt", "stat-file", ".nvmrc", "--assert-not", "file"], comment = "the original .nvmrc is removed", continue-on-failure = true }, + { argv = ["vpt", "print-file", ".node-version"], comment = "volta.node is migrated to .node-version by the full setup", continue-on-failure = true }, { argv = ["vpt", "print-file", "tsconfig.json"], comment = "tsconfig baseUrl is removed by the full setup", continue-on-failure = true }, ] diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_upgrade_setup_full_pnpm/snapshots/migration_upgrade_setup_full_pnpm.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_upgrade_setup_full_pnpm/snapshots/migration_upgrade_setup_full_pnpm.md index 363d248140..a8618adbf1 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_upgrade_setup_full_pnpm/snapshots/migration_upgrade_setup_full_pnpm.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_upgrade_setup_full_pnpm/snapshots/migration_upgrade_setup_full_pnpm.md @@ -22,6 +22,8 @@ VITE+ - The Unified Toolchain for the Web • 2 config updates applied • Node version manager file migrated to .node-version • Package manager settings configured +→ Manual follow-up: + - Remove the "volta" field from package.json ``` ## `vpt print-file package.json` @@ -42,6 +44,9 @@ vite-plus version upgrade is applied "onFail": "download" } }, + "volta": { + "node": "24.11.0" + }, "scripts": { "prepare": "vp config" } @@ -50,20 +55,12 @@ vite-plus version upgrade is applied ## `vpt print-file .node-version` -.nvmrc is migrated to .node-version by the full setup +volta.node is migrated to .node-version by the full setup ``` 24.11.0 ``` -## `vpt stat-file .nvmrc --assert-not file` - -the original .nvmrc is removed - -``` -.nvmrc: missing -``` - ## `vpt print-file tsconfig.json` tsconfig baseUrl is removed by the full setup diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_upgrade_setup_skipped_default_pnpm/.nvmrc b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_upgrade_setup_skipped_default_pnpm/.nvmrc deleted file mode 100644 index 0a492611a0..0000000000 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_upgrade_setup_skipped_default_pnpm/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -24.11.0 diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_upgrade_setup_skipped_default_pnpm/package.json b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_upgrade_setup_skipped_default_pnpm/package.json index 6305ae89db..166919d17d 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_upgrade_setup_skipped_default_pnpm/package.json +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_upgrade_setup_skipped_default_pnpm/package.json @@ -11,5 +11,8 @@ "version": "10.33.0", "onFail": "download" } + }, + "volta": { + "node": "24.11.0" } } diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_upgrade_setup_skipped_default_pnpm/snapshots.toml b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_upgrade_setup_skipped_default_pnpm/snapshots.toml index 79a67da670..93faf56489 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_upgrade_setup_skipped_default_pnpm/snapshots.toml +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_upgrade_setup_skipped_default_pnpm/snapshots.toml @@ -5,7 +5,6 @@ steps = [ { argv = ["vp", "migrate", "--no-interactive"], comment = "existing Vite+ project: upgrade the toolchain version only, skip the full setup", continue-on-failure = true }, { argv = ["vpt", "print-file", "package.json"], comment = "vite-plus version upgrade is applied", continue-on-failure = true }, { argv = ["vpt", "print-file", "pnpm-workspace.yaml"], comment = "pnpm settings consolidated by the version upgrade", continue-on-failure = true }, - { argv = ["vpt", "stat-file", ".nvmrc", "--assert", "file"], comment = ".nvmrc is left untouched without --full", continue-on-failure = true }, - { argv = ["vpt", "stat-file", ".node-version", "--assert-not", "file"], comment = "no .node-version is written", continue-on-failure = true }, + { argv = ["vpt", "stat-file", ".node-version", "--assert-not", "file"], comment = "volta.node is not migrated without --full", continue-on-failure = true }, { argv = ["vpt", "print-file", "tsconfig.json"], comment = "tsconfig baseUrl is left untouched without --full", continue-on-failure = true }, ] diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_upgrade_setup_skipped_default_pnpm/snapshots/migration_upgrade_setup_skipped_default_pnpm.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_upgrade_setup_skipped_default_pnpm/snapshots/migration_upgrade_setup_skipped_default_pnpm.md index dced98c173..227c579723 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_upgrade_setup_skipped_default_pnpm/snapshots/migration_upgrade_setup_skipped_default_pnpm.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_upgrade_setup_skipped_default_pnpm/snapshots/migration_upgrade_setup_skipped_default_pnpm.md @@ -33,6 +33,9 @@ vite-plus version upgrade is applied "version": "", "onFail": "download" } + }, + "volta": { + "node": "24.11.0" } } ``` @@ -54,17 +57,9 @@ catalog: vite-plus: ``` -## `vpt stat-file .nvmrc --assert file` - -.nvmrc is left untouched without --full - -``` -.nvmrc: file -``` - ## `vpt stat-file .node-version --assert-not file` -no .node-version is written +volta.node is not migrated without --full ``` .node-version: missing diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_volta_with_nvmrc/snapshots.toml b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_volta_with_nvmrc/snapshots.toml index 4c41cf96b4..675e6f40a4 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_volta_with_nvmrc/snapshots.toml +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_volta_with_nvmrc/snapshots.toml @@ -2,8 +2,8 @@ name = "migration_volta_with_nvmrc" vp = "global" steps = [ - { argv = ["vp", "migrate", "--no-interactive"], comment = ".nvmrc should take priority over volta.node", continue-on-failure = true }, - { argv = ["vpt", "print-file", ".node-version"], comment = "check .node-version comes from .nvmrc (v24.11.0), not volta.node (18.0.0)", continue-on-failure = true }, - { argv = ["vpt", "stat-file", ".nvmrc", "--assert-not", "file"], comment = "check .nvmrc is removed", continue-on-failure = true }, + { argv = ["vp", "migrate", "--no-interactive"], comment = "an existing .nvmrc is kept and volta.node is not converted", continue-on-failure = true }, + { argv = ["vpt", "stat-file", ".node-version", "--assert-not", "file"], comment = "no .node-version is written to shadow .nvmrc", continue-on-failure = true }, + { argv = ["vpt", "print-file", ".nvmrc"], comment = ".nvmrc is left untouched", continue-on-failure = true }, { argv = ["vpt", "print-file", "package.json"], comment = "volta field must remain intact", continue-on-failure = true }, ] diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_volta_with_nvmrc/snapshots/migration_volta_with_nvmrc.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_volta_with_nvmrc/snapshots/migration_volta_with_nvmrc.md index 245cbbd938..2ee8a20591 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_volta_with_nvmrc/snapshots/migration_volta_with_nvmrc.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_volta_with_nvmrc/snapshots/migration_volta_with_nvmrc.md @@ -2,7 +2,7 @@ ## `vp migrate --no-interactive` -.nvmrc should take priority over volta.node +an existing .nvmrc is kept and volta.node is not converted ``` VITE+ - The Unified Toolchain for the Web @@ -10,25 +10,22 @@ VITE+ - The Unified Toolchain for the Web ◇ Migrated . to Vite+ • Node pnpm • 2 config updates applied -• Node version manager file migrated to .node-version -→ Manual follow-up: - - Remove the "volta" field from package.json ``` -## `vpt print-file .node-version` +## `vpt stat-file .node-version --assert-not file` -check .node-version comes from .nvmrc (v24.11.0), not volta.node (18.0.0) +no .node-version is written to shadow .nvmrc ``` -24.11.0 +.node-version: missing ``` -## `vpt stat-file .nvmrc --assert-not file` +## `vpt print-file .nvmrc` -check .nvmrc is removed +.nvmrc is left untouched ``` -.nvmrc: missing + ``` ## `vpt print-file package.json` diff --git a/docs/guide/migrate-rules.md b/docs/guide/migrate-rules.md index 1a868d7202..035369ea41 100644 --- a/docs/guide/migrate-rules.md +++ b/docs/guide/migrate-rules.md @@ -18,7 +18,7 @@ Migration is idempotent: running it again after a successful migration should no On a project that already depends on `vite-plus`, `vp migrate` performs an upgrade only: it updates dependencies and package-manager configuration and finalizes imports. It does not touch project setup. -- `--full` also runs the setup actions: git hooks, editor config, agent files, ESLint, Prettier, and tsup migration, framework shims, the tsconfig `baseUrl` fix, and the `.nvmrc`/Volta to `.node-version` conversion. +- `--full` also runs the setup actions: git hooks, editor config, agent files, ESLint, Prettier, and tsup migration, framework shims, the tsconfig `baseUrl` fix, and the Volta to `.node-version` conversion. - `--hooks`, `--agent`, and `--editor` opt into a single setup action without `--full`. When a default upgrade skips setup actions that would apply, it prints a hint to run `vp migrate --full`. Fresh (non Vite+) projects always run the full migration. @@ -257,10 +257,9 @@ Migration replaces exact `voidzero-dev/setup-vp@v1` references in GitHub Actions ## Node.js Version Rules -Migration converts legacy Node.js version-manager files to `.node-version`, the format Vite+ reads. On an existing Vite+ project this conversion is part of the full setup bucket, so it runs with `vp migrate --full`; fresh migrations run it unconditionally. +Migration converts the Volta `volta.node` pin to `.node-version`, because Vite+ does not read Volta. On an existing Vite+ project this conversion is part of the full setup bucket, so it runs with `vp migrate --full`; fresh migrations run it unconditionally. -- `.nvmrc` and Volta `volta.node` pins are converted to `.node-version`. An existing `.node-version` is kept. -- When `.nvmrc` is removed, any `actions/setup-node` `node-version-file: .nvmrc` reference in `.github/workflows/*.{yml,yaml}` and composite actions under `.github` (`.github/**/action.{yml,yaml}`) is repointed to `.node-version` so CI does not fail with "node version file ... does not exist". +- When `.node-version` or `.nvmrc` exists, migration keeps it and skips the Volta conversion. Vite+ reads both files directly (see [Node.js Selection](/guide/env#node-js-selection)). ## Package-Manager Rules diff --git a/packages/cli/src/migration/__tests__/migrator.spec.ts b/packages/cli/src/migration/__tests__/migrator.spec.ts index 07f5e509e3..f8b1490549 100644 --- a/packages/cli/src/migration/__tests__/migrator.spec.ts +++ b/packages/cli/src/migration/__tests__/migrator.spec.ts @@ -41,7 +41,6 @@ const { detectVitePlusBootstrapPending, ensureVitePlusBootstrap, finalizeCoreMigrationForExistingVitePlus, - parseNvmrcVersion, detectNodeVersionManagerFile, migrateNodeVersionManagerFile, migrateSetupVpVersion, @@ -1372,41 +1371,6 @@ describe('detectIncompatibleEslintIntegration', () => { }); }); -describe('parseNvmrcVersion', () => { - it('strips v prefix', () => { - expect(parseNvmrcVersion('v20.5.0')).toBe('20.5.0'); - }); - - it('passes through version without prefix', () => { - expect(parseNvmrcVersion('20.5.0')).toBe('20.5.0'); - expect(parseNvmrcVersion('20')).toBe('20'); - }); - - it('passes through lts aliases', () => { - expect(parseNvmrcVersion('lts/*')).toBe('lts/*'); - expect(parseNvmrcVersion('lts/iron')).toBe('lts/iron'); - expect(parseNvmrcVersion('lts/-1')).toBe('lts/-1'); - }); - - it('converts node/stable aliases to lts/*', () => { - expect(parseNvmrcVersion('node')).toBe('lts/*'); - expect(parseNvmrcVersion('stable')).toBe('lts/*'); - }); - - it('returns null for untranslatable aliases', () => { - expect(parseNvmrcVersion('iojs')).toBeNull(); - expect(parseNvmrcVersion('system')).toBeNull(); - expect(parseNvmrcVersion('default')).toBeNull(); - expect(parseNvmrcVersion('')).toBeNull(); - }); - - it('returns null for invalid version strings', () => { - expect(parseNvmrcVersion('v')).toBeNull(); - expect(parseNvmrcVersion('laetst')).toBeNull(); - expect(parseNvmrcVersion('20.5.0.1')).toBeNull(); - }); -}); - describe('setPackageManager', () => { let tmpDir: string; @@ -1620,11 +1584,6 @@ describe('detectNodeVersionManagerFile', () => { expect(detectNodeVersionManagerFile(tmpDir)).toBeUndefined(); }); - it('detects .nvmrc', () => { - fs.writeFileSync(path.join(tmpDir, '.nvmrc'), 'v20.5.0\n'); - expect(detectNodeVersionManagerFile(tmpDir)).toEqual({ file: '.nvmrc' }); - }); - it('detects volta node in package.json', () => { fs.writeFileSync( path.join(tmpDir, 'package.json'), @@ -1636,13 +1595,13 @@ describe('detectNodeVersionManagerFile', () => { }); }); - it('prefers .nvmrc over volta when both are present and sets voltaPresent', () => { + it('returns undefined when .nvmrc exists even with volta', () => { fs.writeFileSync(path.join(tmpDir, '.nvmrc'), 'v20.5.0\n'); fs.writeFileSync( path.join(tmpDir, 'package.json'), JSON.stringify({ volta: { node: '18.0.0' } }), ); - expect(detectNodeVersionManagerFile(tmpDir)).toEqual({ file: '.nvmrc', voltaPresent: true }); + expect(detectNodeVersionManagerFile(tmpDir)).toBeUndefined(); }); it('returns undefined when .node-version already exists even with volta', () => { @@ -1666,136 +1625,6 @@ describe('migrateNodeVersionManagerFile', () => { fs.rmSync(tmpDir, { recursive: true, force: true }); }); - it('adds volta manual step when voltaPresent is set', () => { - fs.writeFileSync(path.join(tmpDir, '.nvmrc'), 'v20.5.0\n'); - const report = createMigrationReport(); - migrateNodeVersionManagerFile(tmpDir, { file: '.nvmrc', voltaPresent: true }, report); - expect(report.manualSteps).toContain('Remove the "volta" field from package.json'); - }); - - it('migrates .nvmrc to .node-version and removes .nvmrc', () => { - fs.writeFileSync(path.join(tmpDir, '.nvmrc'), 'v20.5.0\n'); - const ok = migrateNodeVersionManagerFile(tmpDir, { file: '.nvmrc' }); - expect(ok).toBe(true); - expect(fs.readFileSync(path.join(tmpDir, '.node-version'), 'utf8')).toBe('20.5.0\n'); - expect(fs.existsSync(path.join(tmpDir, '.nvmrc'))).toBe(false); - }); - - it('returns false and warns for unsupported alias', () => { - fs.writeFileSync(path.join(tmpDir, '.nvmrc'), 'system\n'); - const report = createMigrationReport(); - const ok = migrateNodeVersionManagerFile(tmpDir, { file: '.nvmrc' }, report); - expect(ok).toBe(false); - expect(report.warnings.length).toBe(1); - expect(fs.existsSync(path.join(tmpDir, '.node-version'))).toBe(false); - }); - - it('rewrites node-version-file references to .nvmrc in GitHub workflows, preserving quote style', () => { - fs.writeFileSync(path.join(tmpDir, '.nvmrc'), 'v20.19.0\n'); - const workflowsDir = path.join(tmpDir, '.github', 'workflows'); - fs.mkdirSync(workflowsDir, { recursive: true }); - // Quoted reference (the common actions/setup-node style). - fs.writeFileSync( - path.join(workflowsDir, 'ci.yml'), - [ - 'jobs:', - ' build:', - ' steps:', - ' - uses: actions/setup-node@v4', - ' with:', - " node-version-file: '.nvmrc'", - '', - ].join('\n'), - ); - // Unquoted reference in a .yaml file. - fs.writeFileSync( - path.join(workflowsDir, 'release.yaml'), - [ - 'jobs:', - ' release:', - ' steps:', - ' - uses: actions/setup-node@v4', - ' with:', - ' node-version-file: .nvmrc', - '', - ].join('\n'), - ); - const report = createMigrationReport(); - - const ok = migrateNodeVersionManagerFile(tmpDir, { file: '.nvmrc' }, report); - - expect(ok).toBe(true); - expect(fs.existsSync(path.join(tmpDir, '.nvmrc'))).toBe(false); - expect(fs.readFileSync(path.join(tmpDir, '.node-version'), 'utf8')).toBe('20.19.0\n'); - // Quote style preserved: single-quoted stays single-quoted, bare stays bare. - expect(fs.readFileSync(path.join(workflowsDir, 'ci.yml'), 'utf8')).toContain( - "node-version-file: '.node-version'", - ); - expect(fs.readFileSync(path.join(workflowsDir, 'ci.yml'), 'utf8')).not.toContain('.nvmrc'); - expect(fs.readFileSync(path.join(workflowsDir, 'release.yaml'), 'utf8')).toContain( - 'node-version-file: .node-version', - ); - expect(fs.readFileSync(path.join(workflowsDir, 'release.yaml'), 'utf8')).not.toContain( - '.nvmrc', - ); - // The change is surfaced in the report, naming the updated workflow. - expect(report.warnings.some((w) => w.includes('ci.yml'))).toBe(true); - }); - - it('rewrites node-version-file references to .nvmrc in composite action.yml files', () => { - fs.writeFileSync(path.join(tmpDir, '.nvmrc'), 'v20.19.0\n'); - // Composite actions live under .github/actions//action.{yml,yaml}, - // not .github/workflows, and reference .nvmrc the same way. - const actionDir = path.join(tmpDir, '.github', 'actions', 'setup-node'); - fs.mkdirSync(actionDir, { recursive: true }); - fs.writeFileSync( - path.join(actionDir, 'action.yml'), - [ - 'runs:', - ' using: composite', - ' steps:', - ' - uses: actions/setup-node@v4', - ' with:', - ' node-version-file: .nvmrc', - '', - ].join('\n'), - ); - const report = createMigrationReport(); - - const ok = migrateNodeVersionManagerFile(tmpDir, { file: '.nvmrc' }, report); - - expect(ok).toBe(true); - expect(fs.existsSync(path.join(tmpDir, '.nvmrc'))).toBe(false); - const actionYml = fs.readFileSync(path.join(actionDir, 'action.yml'), 'utf8'); - expect(actionYml).toContain('node-version-file: .node-version'); - expect(actionYml).not.toContain('.nvmrc'); - // The change is surfaced in the report, naming the updated action file. - expect(report.warnings.some((w) => w.includes('action.yml'))).toBe(true); - }); - - it('does not rewrite non-node-version-file .nvmrc mentions in workflows', () => { - fs.writeFileSync(path.join(tmpDir, '.nvmrc'), 'v20.19.0\n'); - const workflowsDir = path.join(tmpDir, '.github', 'workflows'); - fs.mkdirSync(workflowsDir, { recursive: true }); - fs.writeFileSync( - path.join(workflowsDir, 'shell.yml'), - ['jobs:', ' build:', ' steps:', ' - run: cat .nvmrc', ''].join('\n'), - ); - - const ok = migrateNodeVersionManagerFile(tmpDir, { file: '.nvmrc' }); - - expect(ok).toBe(true); - // A shell `cat .nvmrc` is out of scope and must be left untouched. - expect(fs.readFileSync(path.join(workflowsDir, 'shell.yml'), 'utf8')).toContain('cat .nvmrc'); - }); - - it('migrates .nvmrc with no .github/workflows directory present', () => { - fs.writeFileSync(path.join(tmpDir, '.nvmrc'), 'v20.19.0\n'); - const ok = migrateNodeVersionManagerFile(tmpDir, { file: '.nvmrc' }); - expect(ok).toBe(true); - expect(fs.readFileSync(path.join(tmpDir, '.node-version'), 'utf8')).toBe('20.19.0\n'); - }); - it('migrates volta node version to .node-version', () => { const ok = migrateNodeVersionManagerFile(tmpDir, { file: 'package.json', diff --git a/packages/cli/src/migration/bin.ts b/packages/cli/src/migration/bin.ts index a16a10a6e5..e4ed7080d1 100644 --- a/packages/cli/src/migration/bin.ts +++ b/packages/cli/src/migration/bin.ts @@ -99,7 +99,6 @@ async function confirmNodeVersionFileMigration( ): Promise { const confirmMessageByFile = { 'package.json': 'Migrate Volta node version (package.json) to .node-version?', - '.nvmrc': 'Migrate .nvmrc to .node-version?', } as const satisfies Record; const message = confirmMessageByFile[detection.file]; diff --git a/packages/cli/src/migration/migrator/README.md b/packages/cli/src/migration/migrator/README.md index 7b7abf5855..4e824f020a 100644 --- a/packages/cli/src/migration/migrator/README.md +++ b/packages/cli/src/migration/migrator/README.md @@ -36,7 +36,7 @@ Pick the file by what a function _does_, not by where it happens to be called. | `tsconfig.ts` | `tsconfig.json` cleanup and `types` rewriting. | | `framework-shim.ts` | Framework (Vue/Astro) shim detection and injection. | | `git-hooks.ts` | Vite+ hook defaults, project-owned hook preservation, and conservative detection of existing hook tools. | -| `setup.ts` | `packageManager` pin, `setup-vp` GitHub Actions version, and Node version-manager file (`.nvmrc`/`.node-version`/Volta) migration. | +| `setup.ts` | `packageManager` pin, `setup-vp` GitHub Actions version, and Volta to `.node-version` migration. | | `core-finalization.ts` | Finalizing an existing-Vite+ core migration (rules YAML, core package scripts). | | `shared.ts` | Cross-cutting constants, types, and tiny utilities used by **two or more** modules. The only module that other modules import from directly (see rules). | diff --git a/packages/cli/src/migration/migrator/setup.ts b/packages/cli/src/migration/migrator/setup.ts index d864f93227..a124ba8f25 100644 --- a/packages/cli/src/migration/migrator/setup.ts +++ b/packages/cli/src/migration/migrator/setup.ts @@ -3,7 +3,7 @@ import path from 'node:path'; import * as prompts from '@voidzero-dev/vite-plus-prompts'; import { globSync } from 'tinyglobby'; -import { normalize, normalizeRange } from 'verkit'; +import { normalize } from 'verkit'; import { parse as parseYaml } from 'yaml'; import { type DownloadPackageManagerResult } from '../../../binding/index.js'; @@ -47,15 +47,11 @@ export function setPackageManager( }); } -export type NodeVersionManagerDetection = - | { file: '.nvmrc'; voltaPresent?: true } - | { file: 'package.json'; voltaNodeVersion: string }; +export type NodeVersionManagerDetection = { file: 'package.json'; voltaNodeVersion: string }; /** - * Detect a .nvmrc file in the project directory. - * If not found, check for a Volta node version in package.json. - * If either is found, return the relevant info for migration. - * Returns undefined if not found or .node-version already exists. + * Detect a Volta node version in package.json for migration. + * Returns undefined if not found or the project already has a pin file Vite+ reads. */ export function detectNodeVersionManagerFile( projectPath: string, @@ -67,11 +63,9 @@ export function detectNodeVersionManagerFile( const configs = detectConfigs(projectPath); - // .nvmrc takes priority over volta.node when both are present. - // voltaPresent is carried through so the migration step can remind the user - // to remove the now-redundant volta field from package.json. + // Vite+ reads .nvmrc directly, and a new .node-version would take priority over it. if (configs.nvmrcFile) { - return configs.voltaNode ? { file: '.nvmrc', voltaPresent: true } : { file: '.nvmrc' }; + return undefined; } if (configs.voltaNode) { @@ -81,56 +75,6 @@ export function detectNodeVersionManagerFile( return undefined; } -/** - * Parse a version alias from a .nvmrc file into a .node-version compatible string. - * Accepts the first line of .nvmrc (pre-trimmed). - * Returns null for unsupported aliases like "system", "default", "iojs". - */ -export function parseNvmrcVersion(alias: string): string | null { - const version = alias.trim(); - - if (!version) { - return null; - } - - // "node" and "stable" mean "latest stable release" which maps closely to lts/*. - // Starting from Node 27, all releases will be LTS, so the gap is shrinking. - // We map these to lts/* and log the conversion so users are aware. - if (version === 'node' || version === 'stable') { - return 'lts/*'; - } - - // "iojs", "system", and "default" have no meaningful equivalent and cannot be auto-migrated. - if (version === 'iojs' || version === 'system' || version === 'default') { - return null; - } - - // LTS aliases (lts/*, lts/iron, etc.) pass through as-is - if (version.startsWith('lts/')) { - return version; - } - - // Strip optional 'v' prefix, then validate as a semver version or range - const normalized = version.startsWith('v') ? version.slice(1) : version; - if (!normalized || !normalizeRange(normalized)) { - return null; - } - return normalized; -} - -/** - * Match an `actions/setup-node` `node-version-file:` value that points at the - * now-removed `.nvmrc`, capturing the surrounding style so it can be preserved: - * 1. the key + whitespace (`node-version-file:` ...) - * 2. the optional opening quote (`'`, `"`, or none), reused as the closing quote - * 3. the optional `./` prefix - * The closing quote backreference (`\2`) plus the `(?=\s|$)` boundary keep this - * pinned to the exact value `.nvmrc` / `./.nvmrc` (quoted or bare) and prevent - * matching similar values such as `.nvmrc-backup`. Only `node-version-file:` lines - * are touched, so shell `cat .nvmrc` and comments are left alone. - */ -const NODE_VERSION_FILE_NVMRC_RE = /(node-version-file:[ \t]*)(['"]?)(\.\/)?\.nvmrc\2(?=\s|$)/gm; - function isCompositeActionFile(filePath: string): boolean { try { const action = parseYaml(fs.readFileSync(filePath, 'utf8')) as unknown; @@ -208,40 +152,8 @@ export function migrateSetupVpVersion(projectPath: string, report?: MigrationRep } /** - * After `.nvmrc` is converted to `.node-version`, rewrite any GitHub Actions - * workflow or composite action that still references the removed file via - * `node-version-file:`, otherwise `actions/setup-node` fails in CI with "The - * specified node version file at: .../.nvmrc does not exist". - * - * Best-effort and narrowly scoped: scans the files from `collectGithubActionFiles`, - * only rewrites `node-version-file:` values (preserving the original - * quoting/indentation), and never fails the migration if a file cannot be - * read/written. Returns the relative paths of the files that were updated. - */ -function rewriteNodeVersionFileReferences(projectPath: string): string[] { - const updated: string[] = []; - for (const filePath of collectGithubActionFiles(projectPath)) { - try { - const original = fs.readFileSync(filePath, 'utf8'); - // `$1` key+space, `$2` opening quote (reused as the closing quote), - // `$3` optional `./` prefix; only `.nvmrc` becomes `.node-version`. - const rewritten = original.replace(NODE_VERSION_FILE_NVMRC_RE, '$1$2$3.node-version$2'); - if (rewritten !== original) { - fs.writeFileSync(filePath, rewritten); - updated.push(path.relative(projectPath, filePath)); - } - } catch { - // Best-effort: skip files that cannot be read or written. - } - } - - return updated; -} - -/** - * Migrate .nvmrc or Volta node version from package.json to .node-version. - * - For .nvmrc: the source file is removed after migration. - * - For package.json (Volta): the volta field is left as-is; removal is left to the user's discretion. + * Migrate the Volta node version from package.json to .node-version. + * The volta field is left as-is; removal is left to the user's discretion. * Returns true on success, false if migration was skipped or failed. */ export function migrateNodeVersionManagerFile( @@ -251,75 +163,25 @@ export function migrateNodeVersionManagerFile( ): boolean { const nodeVersionPath = path.join(projectPath, '.node-version'); - // Volta: node version was already extracted during detection — no package.json re-read needed - if (detection.file === 'package.json') { - const { voltaNodeVersion } = detection; + // Node version was already extracted during detection — no package.json re-read needed + const { voltaNodeVersion } = detection; - // Normalize Volta's "lts" alias to the .node-version compatible form - const resolvedVersion = voltaNodeVersion === 'lts' ? 'lts/*' : voltaNodeVersion; + // Normalize Volta's "lts" alias to the .node-version compatible form + const resolvedVersion = voltaNodeVersion === 'lts' ? 'lts/*' : voltaNodeVersion; - if (!normalize(resolvedVersion) && resolvedVersion !== 'lts/*') { - warnMigration( - `package.json volta.node "${voltaNodeVersion}" is not an exact version. Pin an exact version (e.g. ${voltaNodeVersion}.0 or run \`volta pin node@${voltaNodeVersion}\`) then re-run migration.`, - report, - ); - return false; - } - - fs.writeFileSync(nodeVersionPath, `${resolvedVersion}\n`); - if (report) { - report.manualSteps.push('Remove the "volta" field from package.json'); - report.nodeVersionFileMigrated = true; - } else { - prompts.log.info('You can now remove the "volta" field from package.json manually.'); - } - return true; - } - - // .nvmrc: parse version alias and write to .node-version - const sourcePath = path.join(projectPath, '.nvmrc'); - const content = fs.readFileSync(sourcePath, 'utf8'); - const originalAlias = content.split('\n')[0]?.trim() ?? ''; - const version = parseNvmrcVersion(originalAlias); - - if (!version) { + if (!normalize(resolvedVersion) && resolvedVersion !== 'lts/*') { warnMigration( - '.nvmrc contains an unsupported version alias. Create .node-version manually with your desired Node.js version.', + `package.json volta.node "${voltaNodeVersion}" is not an exact version. Pin an exact version (e.g. ${voltaNodeVersion}.0 or run \`volta pin node@${voltaNodeVersion}\`) then re-run migration.`, report, ); return false; } - // TODO: remove this log once Node 27+ makes all releases LTS, at which point - // "node"/"stable" and "lts/*" will be effectively equivalent. - if (version === 'lts/*' && (originalAlias === 'node' || originalAlias === 'stable')) { - prompts.log.info( - `"${originalAlias}" in .nvmrc is not a specific version; automatically mapping to "lts/*"`, - ); - } - - fs.writeFileSync(nodeVersionPath, `${version}\n`); - fs.unlinkSync(sourcePath); - - // The .nvmrc is gone, so repoint any GitHub Actions workflow or composite - // action that fed it to `actions/setup-node` via `node-version-file:` at the - // new `.node-version`, so CI does not break with "node version file ... does - // not exist". - const updatedFiles = rewriteNodeVersionFileReferences(projectPath); - if (updatedFiles.length > 0) { - warnMigration( - `Updated node-version-file from .nvmrc to .node-version in GitHub Actions file(s): ${updatedFiles.join(', ')}`, - report, - ); - } - + fs.writeFileSync(nodeVersionPath, `${resolvedVersion}\n`); if (report) { + report.manualSteps.push('Remove the "volta" field from package.json'); report.nodeVersionFileMigrated = true; - // Both .nvmrc and volta were present; .nvmrc was migrated but volta still lingers. - if (detection.voltaPresent) { - report.manualSteps.push('Remove the "volta" field from package.json'); - } - } else if (detection.voltaPresent) { + } else { prompts.log.info('You can now remove the "volta" field from package.json manually.'); } return true;