Skip to content

feat(migrate): upgrade existing Vite+ projects across versions#1891

Draft
fengmk2 wants to merge 124 commits into
mainfrom
rfc/migrate-upgrade-path
Draft

feat(migrate): upgrade existing Vite+ projects across versions#1891
fengmk2 wants to merge 124 commits into
mainfrom
rfc/migrate-upgrade-path

Conversation

@fengmk2

@fengmk2 fengmk2 commented Jun 19, 2026

Copy link
Copy Markdown
Member

RFC: rfcs/migrate-existing-projects.md

Problem

vp migrate on an existing v0.1.x Vite+ project did not upgrade cleanly: it delegated to the stale local CLI, left pnpm-workspace.yaml overrides and catalogs pinning vite/vitest to old versions, and skewed coverage providers. The v0.2.x release notes currently tell users not to run vp migrate yet.

What it does

  • Routing: when the local vite-plus is older than the global vp, run migrate from the global CLI so the stale local CLI does not run.
  • Re-pin vite-plus and the vite -> @voidzero-dev/vite-plus-core alias off 0.1.x, across dependencies, overrides/resolutions, and catalogs, for every workspace package rather than only the root manifest.
  • vitest by usage: removed in the common case (vite-plus provides it transitively), kept and ecosystem-aligned when a package uses it directly or via a range-peer integration; align @vitest/* (coverage-v8/-istanbul, ui, web-worker) to the bundled version, with the browser providers kept opt-in.
  • pnpm bootstrap: move package.json#pnpm settings into pnpm-workspace.yaml, fix the empty "pnpm": {} misrouting that left stale overrides, inject the direct vite edge where pnpm needs it, and approve required build scripts. The "pending" check scans every workspace package's catalog ref, so a package pinned to a stale named catalog (catalog:legacy) is not missed.
  • Plugin packages: preserve real vite/vitest imports in vite-plugin-* and unplugin packages instead of rewriting them to vite-plus.

Node.js version

The native binding runs on any Node >=20.0.0, and the published @voidzero-dev/vite-plus-* platform packages now declare that ABI floor (the vite-plus and core packages keep their wider engines.node support range). So a Node pin whose floor is below the support range but at or above 20.0.0 (engines.node: 24.x, >=22, .node-version 24.3.0, ...) still installs the native binding and is left untouched by migration. .nvmrc and Volta volta.node are converted to .node-version, and any actions/setup-node node-version-file: .nvmrc reference in workflows and composite actions (.github/actions/**/action.{yml,yaml}) is repointed to .node-version so CI does not break.

Manual pkg.pr.new testing

Install an isolated pkg.pr.new global CLI and run the PR's vp migrate against a local project. Dependencies resolve through the registry bridge as ordinary 0.0.0-commit.<sha> versions, persisted into the project's .npmrc (and .yarnrc.yml for Yarn Berry) so its own CI installs the same build:

./.github/scripts/test-pkg-pr-new-migrate.sh 1891 /path/to/project [--no-interactive]

The first argument accepts a PR number or commit SHA. The helper keeps ~/.vite-plus untouched, forces migration through the preview CLI even when the project has a same-version local CLI, clears only the workspace root lockfile and node_modules before migrating, and refuses dirty Git worktrees unless ALLOW_DIRTY=1. Confirm the result with vp why -r vite-plus vite vitest (each must resolve to a single version).

@fengmk2 fengmk2 self-assigned this Jun 19, 2026
@netlify

netlify Bot commented Jun 19, 2026

Copy link
Copy Markdown

Deploy Preview for viteplus-preview canceled.

Name Link
🔨 Latest commit 2cfce6b
🔍 Latest deploy log https://app.netlify.com/projects/viteplus-preview/deploys/6a432b301529d9000873af7e

@fengmk2 fengmk2 added test: e2e Auto run e2e tests test: install-e2e run vite install e2e test test: create-e2e Run `vp create` e2e tests test: sfw pkg.pr.new labels Jun 21, 2026
@fengmk2 fengmk2 force-pushed the rfc/migrate-upgrade-path branch from feb8068 to 5090afc Compare June 21, 2026 14:11
@pkg-pr-new

pkg-pr-new Bot commented Jun 21, 2026

Copy link
Copy Markdown

Open in StackBlitz

vite-plus

npm i https://pkg.pr.new/voidzero-dev/vite-plus@1891

@voidzero-dev/vite-plus-core

npm i https://pkg.pr.new/voidzero-dev/vite-plus/@voidzero-dev/vite-plus-core@1891

@voidzero-dev/vite-plus-prompts

npm i https://pkg.pr.new/voidzero-dev/vite-plus/@voidzero-dev/vite-plus-prompts@1891

@voidzero-dev/vite-plus-cli-darwin-arm64

npm i https://pkg.pr.new/voidzero-dev/vite-plus/@voidzero-dev/vite-plus-cli-darwin-arm64@1891

@voidzero-dev/vite-plus-cli-darwin-x64

npm i https://pkg.pr.new/voidzero-dev/vite-plus/@voidzero-dev/vite-plus-cli-darwin-x64@1891

@voidzero-dev/vite-plus-cli-linux-arm64-gnu

npm i https://pkg.pr.new/voidzero-dev/vite-plus/@voidzero-dev/vite-plus-cli-linux-arm64-gnu@1891

@voidzero-dev/vite-plus-cli-linux-arm64-musl

npm i https://pkg.pr.new/voidzero-dev/vite-plus/@voidzero-dev/vite-plus-cli-linux-arm64-musl@1891

@voidzero-dev/vite-plus-cli-linux-x64-gnu

npm i https://pkg.pr.new/voidzero-dev/vite-plus/@voidzero-dev/vite-plus-cli-linux-x64-gnu@1891

@voidzero-dev/vite-plus-cli-linux-x64-musl

npm i https://pkg.pr.new/voidzero-dev/vite-plus/@voidzero-dev/vite-plus-cli-linux-x64-musl@1891

@voidzero-dev/vite-plus-cli-win32-arm64-msvc

npm i https://pkg.pr.new/voidzero-dev/vite-plus/@voidzero-dev/vite-plus-cli-win32-arm64-msvc@1891

@voidzero-dev/vite-plus-cli-win32-x64-msvc

npm i https://pkg.pr.new/voidzero-dev/vite-plus/@voidzero-dev/vite-plus-cli-win32-x64-msvc@1891

@voidzero-dev/vite-plus-darwin-arm64

npm i https://pkg.pr.new/voidzero-dev/vite-plus/@voidzero-dev/vite-plus-darwin-arm64@1891

@voidzero-dev/vite-plus-darwin-x64

npm i https://pkg.pr.new/voidzero-dev/vite-plus/@voidzero-dev/vite-plus-darwin-x64@1891

@voidzero-dev/vite-plus-linux-arm64-gnu

npm i https://pkg.pr.new/voidzero-dev/vite-plus/@voidzero-dev/vite-plus-linux-arm64-gnu@1891

@voidzero-dev/vite-plus-linux-arm64-musl

npm i https://pkg.pr.new/voidzero-dev/vite-plus/@voidzero-dev/vite-plus-linux-arm64-musl@1891

@voidzero-dev/vite-plus-linux-x64-gnu

npm i https://pkg.pr.new/voidzero-dev/vite-plus/@voidzero-dev/vite-plus-linux-x64-gnu@1891

@voidzero-dev/vite-plus-linux-x64-musl

npm i https://pkg.pr.new/voidzero-dev/vite-plus/@voidzero-dev/vite-plus-linux-x64-musl@1891

@voidzero-dev/vite-plus-win32-arm64-msvc

npm i https://pkg.pr.new/voidzero-dev/vite-plus/@voidzero-dev/vite-plus-win32-arm64-msvc@1891

@voidzero-dev/vite-plus-win32-x64-msvc

npm i https://pkg.pr.new/voidzero-dev/vite-plus/@voidzero-dev/vite-plus-win32-x64-msvc@1891

commit: 2cfce6b

@fengmk2 fengmk2 force-pushed the rfc/migrate-upgrade-path branch from 5090afc to 732edd6 Compare June 22, 2026 01:52
@fengmk2

fengmk2 commented Jun 22, 2026

Copy link
Copy Markdown
Member Author

E2E test projects

 × typescript(TS2783): 'staged' is specified more than once, so this usage will be overwritten.
     ╭─[vite.config.ts:118:3]
 117 │     export default defineConfig({
 118 │ ╭─▶   staged: {
 119 │ │       '*': 'vp check --fix',
 120 │ ╰─▶   },
 121 │       ...config, // shared lint/fmt/build from @janosh/vite-config (dotfiles)
     ╰────
help: Option 'moduleResolution=node10' has been removed. Please remove it from your configuration.
        See https://github.com/oxc-project/tsgolint/issues/351 for more information.
✖ bash -c "tsc --noEmit":
error TS2688: Cannot find type definition file for 'vite-plus/client'.
  The file is in the program because:
    Entry point of type library 'vite-plus/client' specified in compilerOptions

  tsconfig.json:12:33
    12     "types": ["vitest/globals", "vite-plus/client"],
                                       ~~~~~~~~~~~~~~~~~~
    File is entry point of type library specified here.

@chatgpt-codex-connector

This comment was marked as outdated.

fengmk2 added 3 commits June 29, 2026 23:40
A stale pre-migrate lockfile can keep an optional-peer copy of vite-plus pinned
to an older published version (e.g. a nested oxlint `vite-plus: '*'` peer pulled
transitively by vite-plugin-checker/nuxt), which the `--no-frozen-lockfile`
reinstall preserves rather than deduping, leaving `vp why` reporting two
vite-plus versions. Remove the lockfiles and node_modules before migrate so the
reinstall resolves that optional peer onto the in-tree managed version.

Claude-Session: https://claude.ai/code/session_01DQhS6o1fyQd1yjiee6W8jR
- Node engines union floor gate: lift each disjunct of a multi-major pin
  independently instead of gating on the overall floor, so `^20.19.0 || ^22.0.0`
  lifts the below-floor 22 branch to `>=22.18.0 <23.0.0` (was left unchanged).
- compat runner: resolve the rolldown-compat worker robustly across the source
  (sibling worker.js) and bundle (nested compat/worker.js) layouts.
- yarn satisfaction: yarnrcSatisfiesVitePlus now requires the
  npmPreapprovedPackages vitest/@vitest exemptions, so an age-gated yarn project
  no longer takes the early "already using Vite+" path without them.
- pnpm pending: detect missing minimumReleaseAgeExclude exemptions (shared
  PNPM_MINIMUM_RELEASE_AGE_EXCLUDES) in both detection and the bootstrap writer.
- yarn hoisting: detect + apply the nmHoistingLimits opt-out on the bootstrap
  path, not only in rewriteMonorepoProject.
- bun: gate the direct-vite injection to packages that need vitest/vite-plus/a
  browser provider instead of every workspace package.
- typos: drop the `unparseable` whitelist; fix usages to `unparsable`.

Claude-Session: https://claude.ai/code/session_01DQhS6o1fyQd1yjiee6W8jR
Replace the arity + element-wise comparison in supported_node_floor_range's
union branch with a joined-string compare. It is equivalent (a dropped branch
shortens the join, a lifted branch changes it) and reads clearer.

Claude-Session: https://claude.ai/code/session_01DQhS6o1fyQd1yjiee6W8jR
@fengmk2

fengmk2 commented Jun 29, 2026

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 595280825c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/cli/src/migration/migrator/eslint.ts
Comment thread packages/cli/src/migration/migrator/vite-plus-bootstrap.ts Outdated
Comment thread packages/cli/binding/src/migration.rs Outdated
fengmk2 added 3 commits June 30, 2026 00:54
On the existing-Vite+ upgrade path, reconcileVitePlusBootstrapPackage only
removed the @vitest/* subset of REMOVE_PACKAGES from package.json, while the
catalog rewrite deletes the full set (oxlint, oxlint-tsgolint, oxfmt, tsdown,
@vitest/browser, @vitest/browser-preview). An existing project that declared one
of the non-@vitest tools via `catalog:` was then left with a dangling catalog
reference whose catalog entry had just been removed, so `pnpm install` aborted
with ERR_PNPM_CATALOG_ENTRY_NOT_FOUND_FOR_SPEC (delta-comic/oxlint-tsgolint,
regle/tsdown). Iterate the full REMOVE_PACKAGES, matching the catalog removal
and the fresh path (package-json.ts).

Adds a unit test and a migration-upgrade-pnpm-bundled-catalog-dep snap proving
the bundled tools are removed from both package.json and the catalog.

Claude-Session: https://claude.ai/code/session_01DQhS6o1fyQd1yjiee6W8jR
- Node engines: bound the single-disjunct lift to its major, so `^20` /
  `20.18.0` become `>=20.19.0 <21.0.0` instead of an unbounded `>=20.19.0` that
  admitted unsupported Node 21/23. The single and union paths now share
  lift_disjunct; a genuinely open pin (`>=20`) stays open.
- bun overrides: gate the bun catalog dependency resolver on supportCatalog in
  both the reconcile and the pending detection, so a standalone bun project's
  `overrides.vite: "catalog:"` is rewritten to the concrete core alias (and
  detection no longer reports it already settled) instead of leaving a catalog
  override that bun cannot resolve.
- eslint versions import (false positive): the bundle resolves `../versions.js`
  correctly via the fix-versions-path tsdown plugin + a dist-root chunk; added a
  dist guard test that fails if any chunk's relative versions.js import stops
  resolving.

Claude-Session: https://claude.ai/code/session_01DQhS6o1fyQd1yjiee6W8jR
Replace the hand-rolled recursive collectJsFiles walker with globSync,
matching the in-package convention (utils/workspace.ts).

Claude-Session: https://claude.ai/code/session_01DQhS6o1fyQd1yjiee6W8jR
@fengmk2

fengmk2 commented Jun 29, 2026

Copy link
Copy Markdown
Member Author

@codex review

fengmk2 added 2 commits June 30, 2026 01:29
The .nvmrc to .node-version conversion only rewrote node-version-file
references in .github/workflows, leaving composite actions
(.github/actions/**/action.{yml,yaml}) pointing at the removed .nvmrc,
which breaks CI with "node version file ... does not exist".

Walk .github/actions recursively for action.{yml,yaml} alongside the
flat workflows scan and rewrite both.

Claude-Session: https://claude.ai/code/session_01DQhS6o1fyQd1yjiee6W8jR
Replace the hand-rolled .github/workflows readdir and the .github/actions
stack walk with a single globSync call, matching the in-package
convention (utils/workspace.ts). nocase preserves the prior
case-insensitive matching; the flat-workflows vs recursive-actions split
is kept via two glob patterns.

Claude-Session: https://claude.ai/code/session_01DQhS6o1fyQd1yjiee6W8jR

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fb2e405fcf

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/cli/binding/src/migration.rs Outdated
Comment thread packages/cli/src/migration/migrator/eslint.ts Outdated
Comment thread packages/cli/src/migration/migrator/vite-plus-bootstrap.ts Outdated
Comment thread crates/vite_global_cli/src/js_executor.rs
fengmk2 added 2 commits June 30, 2026 07:48
The native NAPI binding runs on any Node >=20.0.0 (its ABI floor), but the
@voidzero-dev/vite-plus-* platform packages were inheriting the product support
policy (^20.19.0 || ^22.18.0 || >=24.11.0). That policy's gaps (20.0-20.18,
22.0-22.17, 24.0-24.10) make engine-strict package managers skip the optional
native dependency whenever a consumer's declared Node floor lands in a gap,
surfacing as "Cannot find native binding". Rewrite each generated platform
package to its true ABI floor so the native dep is never skipped; the vite-plus
and core packages keep the product policy unchanged.

Claude-Session: https://claude.ai/code/session_01DQhS6o1fyQd1yjiee6W8jR
With the native packages declaring their true >=20.0.0 ABI floor, a below-policy
Node pin (engines.node "24.x", devEngines.runtime "^24", or a .node-version below
the policy minimum) no longer causes package managers to skip the native binding,
so migrate must leave it untouched. Remove the floor-raising: the Rust
supported_node_floor_range / lift_disjunct / supported_node_requirement /
resolved_if_supported helpers and the resolveSupportedNodeVersion /
resolveSupportedNodeRange NAPI exports, plus the JS upgradeUnsupportedNodeVersions
/ planNodeVersionUpgrades / hasUnsupportedNodeVersionPin surface and
SUPPORTED_NODE_RANGE.

The .nvmrc/Volta -> .node-version conversion, the node-version-file workflow and
composite-action rewrite, and the runtime node resolver
(resolveProjectNodeVersion) are unchanged. Adds a reproducing snap fixture
showing below-policy pins are preserved.

Claude-Session: https://claude.ai/code/session_01DQhS6o1fyQd1yjiee6W8jR
Comment thread packages/cli/publish-native-addons.ts Outdated
Comment thread rfcs/migrate-existing-projects.md Outdated
This NAPI export (with ProjectNodeVersion / version_source_label) was added by
the reverted Node auto-upgrade feature and never wired into the JS migrator, so
it has zero callers. Removing it completes the revert and drops the binding
crate's now-unused node-semver and vite_js_runtime dependencies (vite_path stays,
used elsewhere in the crate). The binding is regenerated so the export is gone
from the generated index files.

Claude-Session: https://claude.ai/code/session_01DQhS6o1fyQd1yjiee6W8jR
fengmk2 added 4 commits June 30, 2026 08:39
Reword the native-addon engines comment and the migrate RFC to refer to the
declared engines.node supported range instead of "product policy", and drop the
now-stale "reuses the Rust runtime resolver" claim (that resolver was removed).
Simplify the preserve-pins snap to `cat package.json` and add
`cat pnpm-workspace.yaml` so it also covers the catalog update.

Claude-Session: https://claude.ai/code/session_01DQhS6o1fyQd1yjiee6W8jR
collectInstalledPackageNames no longer counts peerDependencies as installed: a
peer-only package is not in node_modules, so a JS plugin referencing it would
fail to load at `vp lint` time. Limit it to dependencies, devDependencies, and
optionalDependencies.

The pnpm bootstrap pending check now scans every workspace package's vite-plus
catalog reference, not just the root manifest, so a workspace package pinned to a
stale named catalog (catalog:legacy) keeps the bootstrap pending. The same scan
gates the pnpm-workspace.yaml rewrite so the bootstrap converges.

Claude-Session: https://claude.ai/code/session_01DQhS6o1fyQd1yjiee6W8jR
The pre-migrate cleanup used a recursive `find -name node_modules -exec rm -rf`,
which wiped every sub-workspace node_modules in a monorepo, not just the root.
Removing the root lockfile and root node_modules is enough to force a clean
re-resolution (the root lockfile pins the stale optional-peer vite-plus, and the
reinstall recreates sub-package node_modules from the root store). Switch to
`rm -rf "$project_dir/node_modules"` so sub-workspace node_modules are left
untouched.

Claude-Session: https://claude.ai/code/session_01DQhS6o1fyQd1yjiee6W8jR
The fixture package.json was not oxfmt-formatted, so the repo-wide `vp check`
flagged it. Format it (key order only; the 24.x / ^24 Node pins are unchanged)
and regenerate the snap so its post-migrate output matches.

Claude-Session: https://claude.ai/code/session_01DQhS6o1fyQd1yjiee6W8jR
@fengmk2

fengmk2 commented Jun 30, 2026

Copy link
Copy Markdown
Member Author

@codex review

chatgpt-codex-connector[bot]

This comment was marked as resolved.

workspaceCatalogVitePlusDependencyPending and
workspaceVitestEcosystemCatalogReferencesPending had byte-identical
read/guard/iterate scaffold over bootstrapProjectPaths. Extract a shared
someBootstrapProjectPackageJson(rootDir, packages, predicate) helper that both
call, so each collapses to one line binding its resolver into the predicate.

Claude-Session: https://claude.ai/code/session_01DQhS6o1fyQd1yjiee6W8jR
@fengmk2

fengmk2 commented Jun 30, 2026

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Delightful!

Reviewed commit: a0e4154f12

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

fengmk2 added 2 commits June 30, 2026 09:40
Bump the voidzero-dev/pkg-pr-registry-bridge action to its latest main and pass
the new pr-url input (surfaced by the bridge's /-/refs). Point the registry at
the canonical https://registry-bridge.viteplus.dev in the PR comment and the
test-pkg-pr-new-migrate helper, per the bridge's ci-setup doc.

Claude-Session: https://claude.ai/code/session_01DQhS6o1fyQd1yjiee6W8jR
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pkg.pr.new test: create-e2e Run `vp create` e2e tests test: e2e Auto run e2e tests test: install-e2e run vite install e2e test test: sfw

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant