Skip to content

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

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

feat(migrate): upgrade existing Vite+ projects across versions#1891
fengmk2 wants to merge 69 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

Running 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 pinning vite/vitest to old versions, and skewed coverage providers. The v0.2.1 release notes currently tell users not to run vp migrate yet.

What this does (verified)

  • Routing: when the local vite-plus is older than the global vp, run migrate from the global CLI.
  • Re-pin a behind vite-plus / vite->core spec so the lockfile moves off 0.1.x.
  • Fix the empty "pnpm": {} misrouting that left stale pnpm-workspace.yaml overrides.
  • Manage vitest by usage: removed in the common case (vite-plus provides it transitively), kept + ecosystem-aligned when the project uses it directly or via a range-peer integration.
  • Align the full @vitest/* ecosystem (coverage-v8/-istanbul, ui, web-worker) to the bundled version; exclude @vitest/eslint-plugin.

Verified: 248 migration + 728 CLI unit tests, tsc, vp lint, cargo check/clippy, the migration-vitest-peer-dep snap test, and the urllib 3-PM checks (node-modules/urllib #832-834).

Known gaps (draft, follow-ups)

  • The upgrade path does not yet handle vitest browser mode: a browser-test package (e.g. vibe-dashboard apps/dashboard) is left without @vitest/browser-playwright and a direct vitest, so browser tests break. The fresh-migration path handles this; the upgrade path must too.
  • Monorepo vitest localization: pin a concrete vitest in the package that needs it instead of the shared root catalog.
  • Collapse the direct-usage vitest pin into removal for official-@vitest/*-only projects.
  • Regenerate the migration-* snap suite and do the docs / npm deprecate rollout, then drop the "do not run vp migrate" disclaimer.

Manual pkg.pr.new migration testing

Use the repository helper to install an isolated pkg.pr.new global CLI and run the PR version of vp migrate against any local project:

./.github/scripts/test-pkg-pr-new-migrate.sh 1891 /path/to/project

The first argument accepts either a PR number or commit SHA. The helper keeps the normal ~/.vite-plus installation untouched, forces migration through the installed global preview CLI even when the project has a same-version local CLI, pins vite-plus and vite/core to the matching pkg.pr.new URLs, refuses dirty Git worktrees by default, and forwards additional options such as --no-interactive to vp migrate.

@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 823decd
🔍 Latest deploy log https://app.netlify.com/projects/viteplus-preview/deploys/6a4005a12d73ac0008ae9cfd

@socket-security

socket-security Bot commented Jun 19, 2026

Copy link
Copy Markdown

@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: c0970dc

@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

@chatgpt-codex-connector

This comment was marked as outdated.

fengmk2 added 26 commits June 28, 2026 00:58
- npm-reinstall: guard the package-lock.json parse so a malformed or
  merge-conflicted lockfile no longer aborts migration mid-write
- bun bootstrap: add the direct vite dependency so bun install resolves
  vitest's vite peer (mirrors the full-migration path)
- compat-worker: restore withConfigMetadataResolution so the config
  compatibility check skips user plugin factories instead of running them
  (no indefinite hang, no silently dropped warning)
- format: only fall back to whole-project formatting outside a git
  worktree (skip on git errors rather than reformatting everything), and
  batch the changed-file list to avoid ARG_MAX on large monorepos
- oxlint-plugin: invalidate the @nuxt/test-utils cache by package.json
  mtime so long-lived lint/LSP sessions pick up manifest edits
- migrator: drop the dead importOptions wrapper and the unused
  detectNuxtTestUtilsVitestImportFiles, dedupe redundant source-tree
  scans and the bun catalog resolver

Claude-Session: https://claude.ai/code/session_01DQhS6o1fyQd1yjiee6W8jR
Resolve pkg.pr.new commit builds as ordinary npm versions
(0.0.0-commit.<sha>) via the registry bridge instead of mutable
pkg.pr.new URLs, so test-pkg-pr-new-migrate.sh installs them like
released packages. This drops the Bun tarball repack, file: URL
overrides, blockExoticSubdeps, and the override/pnpm-version helpers;
the project's package manager is pointed at the bridge with
npm_config_registry / YARN_NPM_REGISTRY_SERVER. The global CLI keeps
installing from pkg.pr.new, which serves the per-platform binaries the
bridge cannot.

Register each commit build with the bridge from the pkg.pr.new publish
workflow (replacing its GitHub webhook), scoped to same-repo PRs.

Claude-Session: https://claude.ai/code/session_01DQhS6o1fyQd1yjiee6W8jR
Run oxfmt on migrator.ts / oxlint-plugin.ts, which drifted from the
formatter after the importOptions removal and the @nuxt/test-utils cache
change. Regenerate the two global migration snapshots
(migration-from-vitest-config and
migration-monorepo-pnpm-overrides-dependency-selector) so they reflect
the current vitest-provider catalog alignment and pnpm-overrides merge
behavior; both were left stale by an earlier commit.

Claude-Session: https://claude.ai/code/session_01DQhS6o1fyQd1yjiee6W8jR
The pkg.pr.new helper set the bridge registry only via environment
variables, which pnpm ignores for resolution (it reads .npmrc), so the
migration install fetched the commit version from registry.npmjs.org and
failed with ERR_PNPM_NO_MATCHING_VERSION. Write the bridge registry into
the project's .npmrc (npm/pnpm/Yarn Classic/Bun) and, for Yarn Berry
projects, .yarnrc.yml (npmRegistryServer), so the migrated project
resolves the commit builds both during the run and in its own CI. Both
writes are idempotent and left in place; the env vars remain as a
local-run fallback.

Claude-Session: https://claude.ai/code/session_01DQhS6o1fyQd1yjiee6W8jR
When a project declares vite-plus in `dependencies` (instead of the
conventional `devDependencies`), migration appended a second vite-plus
entry to `devDependencies`, leaving it in both groups with a conflicting
spec. Every "ensure vite-plus is present" check looked only at
`devDependencies`; the firing site for the full-migration path was
`rewritePackageJson`, whose `existingVitePlus` ignored `dependencies`.

Treat vite-plus as already present when it lives in `dependencies` or
`devDependencies` (a `hasDirectVitePlusInstallEntry` helper), and re-pin
/ normalize the existing entry in place rather than adding a cross-group
duplicate. `optionalDependencies` is intentionally excluded so an
optional-only entry still gets a guaranteed devDependencies entry.
Force-override still re-pins a pre-existing devDependencies entry.

Adds a reproducing unit test, the migration-vite-plus-in-dependencies-pnpm
snap test, and updates the bootstrap-path tests that codified the old
duplicate behavior.

Claude-Session: https://claude.ai/code/session_01DQhS6o1fyQd1yjiee6W8jR
migrator.ts was ~7,300 lines. Move its 244 declarations verbatim into 16
category modules under migration/migrator/ (eslint, prettier, catalog,
vitest-ecosystem, vite-plus-bootstrap, package-json, vite-config, yarn,
source-scan, git-hooks, orchestrators, ...). migrator.ts is now a barrel
of `export *` re-exports, so the external importers keep importing from
./migrator.ts unchanged.

Cross-module function helpers are imported from the barrel (safe because
they are only referenced inside function bodies at runtime); shared
constants/types live in shared.ts and are imported directly from it to
avoid a load-time cycle. Pure code move, no behavior change: tsc clean,
vp check clean, 323 migration unit tests unchanged. Adds migrator/README.md
documenting the structure and the rules for adding modules.

Claude-Session: https://claude.ai/code/session_01DQhS6o1fyQd1yjiee6W8jR
Add a "Where to Start" pointer so agents read
packages/cli/src/migration/migrator/README.md before changing migrator code.

Claude-Session: https://claude.ai/code/session_01DQhS6o1fyQd1yjiee6W8jR
Project skill that verifies a pkg.pr.new build of vite-plus against one
real project: runs .github/scripts/test-pkg-pr-new-migrate.sh (vp migrate
with deps resolved through the registry bridge), then `vp why` to confirm a
single, correct version of vite-plus/vite/vitest. Asks for the PR/SHA and
project path when not provided.

Claude-Session: https://claude.ai/code/session_01DQhS6o1fyQd1yjiee6W8jR
In pkg.pr.new force-override runs (VP_VERSION=https://pkg.pr.new/...), the
force-override block re-pins vite-plus to that commit URL and sets
needVitePlus. The dedup rewrite only normalized non-protocol specs, so the
raw URL leaked into the direct dep instead of `catalog:` (the catalog still
holds the URL), breaking the migration-upgrade-pkg-pr-new-pnpm snapshot.

Normalize protocol-pinned specs under force-override too, while preserving
catalog:named references, matching ensureVitePlusDependencySpecs. Adds a
regression spec that mocks VITE_PLUS_VERSION to a pkg.pr.new URL (the shared
migrator spec mocks it to `latest`, which hid this case).

Claude-Session: https://claude.ai/code/session_01DQhS6o1fyQd1yjiee6W8jR
Re-run the Yarn PnP guard after the package manager is resolved: an existing
Vite+ project with no detectable manager runs the guard with an undefined
manager (a no-op), so a later Yarn resolution under YARN_NODE_LINKER=pnp would
slip through. Re-running once the manager is known still rejects the
unsupported PnP layout.

Pin the preview CLI's own managed Node when running the pkg.pr.new migrate
entry. `vp node` resolves Node from the target project's cwd, so a project
pinned to an old/unsupported version could fail to launch dist/bin.js even
though the isolated CLI ships a compatible runtime; probe the CLI default and
pass it via `env exec --node`.

Claude-Session: https://claude.ai/code/session_01DQhS6o1fyQd1yjiee6W8jR
The registry bridge serves pkg.pr.new commit builds as ordinary npm versions
(0.0.0-commit.<sha>), so the migrate test path no longer needs force-override
to pin raw pkg.pr.new URLs. Remove VP_FORCE_MIGRATE from the bridge e2e script
and convert the migration-upgrade-pkg-pr-new-{pnpm,npm} and
migration-upgrade-pnpm-named-catalog snap fixtures to the bridge version so
they exercise the normal upgrade path (force-override itself stays for the
vp create / ecosystem-ci local-tgz installs).

Revert the force-override protocol-pin normalization in rewritePackageJson and
its spec, since only the now-removed URL+force-override flow reached it. Also
bound the snap normalizer prerelease match to version characters so a
`@0.0.0-commit.<sha>` npm alias in JSON no longer swallows the closing quote.

Claude-Session: https://claude.ai/code/session_01DQhS6o1fyQd1yjiee6W8jR
The bounded-prerelease snap normalizer (515443d) correctly stops eating the
` (current: <semver>)` suffix after the alpha `found` version. This snap was
reverted with the network-flaky regen batch; regenerate it for real.

Claude-Session: https://claude.ai/code/session_01DQhS6o1fyQd1yjiee6W8jR
Once the registry bridge accepts a commit build, post (or update via a sticky
marker) a PR comment with the resolved vite-plus / vite-plus-core npm versions
(0.0.0-commit.<sha>) and the per-package-manager bridge registry config, so
reviewers can install the build directly. Gated on the bridge step's real
outcome and skipped for fork PRs; never fails the publish.

Claude-Session: https://claude.ai/code/session_01DQhS6o1fyQd1yjiee6W8jR
Align the markdown tables; formatting only (follow-up to the bridge-comment
workflow step, which was committed without running vp check).

Claude-Session: https://claude.ai/code/session_01DQhS6o1fyQd1yjiee6W8jR
Move the four compat-* files into migration/compat/ with concise names:
compat-protocol.ts -> protocol.ts, compat.ts -> manual-chunks.ts,
compat-runner.ts -> runner.ts, compat-worker.ts -> worker.ts.

Update the cross-file imports, bin.ts and both specs, the tsdown worker entry
(now emits dist/migration/compat/worker.js), and the runner's subprocess path
(./compat/worker.js). Behavior-preserving: vp check clean, both compat specs
pass, and the built bin.js resolves the relocated worker entry.

Claude-Session: https://claude.ai/code/session_01DQhS6o1fyQd1yjiee6W8jR
A `bunx --bun <tool>` script (e.g. `bunx --bun vite build`) lost its `--bun`
flag when the tool was rewritten to a vp subcommand, producing `bunx vp build`
and silently switching the user's chosen runtime from Bun to Node. Stop
stripping it so it becomes `bunx --bun vp build`, and drop the now-unused
forced_bun_suffix_indices tracking.

Update the vite_migration Rust unit tests and the JS rewritePackageJson vitest
snapshot (package.json scripts are rewritten through the crate via NAPI).

Claude-Session: https://claude.ai/code/session_01DQhS6o1fyQd1yjiee6W8jR
@fengmk2 fengmk2 force-pushed the rfc/migrate-upgrade-path branch from 6702c0f to c0970dc Compare June 27, 2026 16:59
In the existing-Vite+ upgrade path, ensureExistingPackageManager (the package
manager download) starts the "Preparing migration" timer spinner, but nothing
stopped it before the interactive setup prompts (collectMigrationSetupPlan).
The live spinner kept re-rendering its timer line over the prompt and corrupted
it. Clear it right after the download, matching the clearMigrationProgress
pattern already used elsewhere in the flow; it restarts for the
bootstrap/install phase.

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