Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 41 additions & 7 deletions .github/workflows/test-vp-create.yml
Original file line number Diff line number Diff line change
Expand Up @@ -195,13 +195,6 @@ jobs:
# `YN0016 ... are quarantined`. The migrate tool is version-pinned to the
# bundled oxlint, so disable the gate for this test (no-op for npm/pnpm/bun).
YARN_NPM_MINIMAL_AGE_GATE: '0'
# pnpm 11's default `minimumReleaseAge` (~24h) makes the bundled vitest's
# auto-installed `vite` peer resolve to the previous upstream release,
# which can predate vite's `@voidzero-dev/vite-task-client` integration and
# surface a false `vp test` cache miss in "Verify cache". Force 0 so the
# latest vite (with the integration) is used. pnpm-only (no-op elsewhere).
# Temporary band-aid; real fix tracked in voidzero-dev/vite-plus#1932.
PNPM_CONFIG_MINIMUM_RELEASE_AGE: '0'
steps:
- uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2

Expand Down Expand Up @@ -300,6 +293,47 @@ jobs:
esac
fi

- name: Verify single dependency instances (pnpm only)
if: matrix.package-manager == 'pnpm'
working-directory: ${{ runner.temp }}/test-project
run: |
# The `vite` override must dedupe vite-plus / vite / vitest to a single
# instance each. When a peer variation splits the graph (e.g. an upstream
# `vite` auto-installed to satisfy vitest's peer in a package without a
# direct `vite` dep), `vp why` reports multiple instances. Detection:
# - vite-plus / vitest: a split prints "Found 1 version, N instances of <pkg>".
# - vite: it is overridden to @voidzero-dev/vite-plus-core, so a clean tree
# only summarises that package; a standalone upstream copy adds a
# "Found <n> version(s) of vite" line.
# Regression guard for voidzero-dev/vite-plus#1932 (the pnpm dedupe fix).
# `-r` checks every workspace package, not just the root importer, so a
# duplicate confined to a sub-package (apps/website, packages/utils) is
# still caught.
fail=0
check() {
pkg="$1"; pattern="$2"
out=$(vp why -r "$pkg" 2>&1)
found=$(echo "$out" | grep '^Found' || true)
echo "[$pkg]"; echo "$found"
if echo "$found" | grep -qE "$pattern"; then
echo "✗ $pkg is not a single instance (override did not dedupe under pnpm)"
echo "----- full \`vp why -r $pkg\` output -----"
echo "$out"
echo "---------------------------------------"
fail=1
else
echo "✓ $pkg single instance"
fi
}
check vite-plus 'instances of vite-plus'
check vitest 'instances of vitest'
check vite 'of vite$'
if [ "$fail" -ne 0 ]; then
echo "Expected vite-plus, vite, and vitest to each resolve to a single instance."
exit 1
fi
echo "✓ vite-plus, vite, vitest are all single instances"

- name: Verify local tgz packages installed
working-directory: ${{ runner.temp }}/test-project
run: |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export default defineConfig({
> cat package.json # check package.json
{
"devDependencies": {
"vite": "catalog:",
"vite-plus": "catalog:"
},
"devEngines": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export default defineConfig({
> cat package.json # check package.json
{
"devDependencies": {
"vite": "catalog:",
"vite-plus": "catalog:"
},
"devEngines": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ Documentation: https://viteplus.dev/guide/migrate
{
"name": "migration-lintstagedrc",
"devDependencies": {
"vite": "catalog:",
"vite-plus": "catalog:"
},
"devEngines": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export default {
"devDependencies": {
"husky": "^9.1.7",
"lint-staged": "^16.2.6",
"vite": "catalog:",
"vite-plus": "catalog:"
},
"devEngines": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ minimumReleaseAgeExclude:
"lint": "vp lint --fix"
},
"devDependencies": {
"vite": "catalog:",
"vite-plus": "catalog:"
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"prepare": "vp config"
},
"devDependencies": {
"vite": "catalog:",
"vitest-browser-svelte": "^2.1.0",
"vite-plus": "catalog:",
"vitest": "catalog:"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export default defineConfig({
{
"name": "migration-monorepo-skip-vite-peer-dependency",
"devDependencies": {
"vite": "catalog:",
"vite-plus": "catalog:"
},
"devEngines": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export default defineConfig({
> cat package.json # check package.json
{
"devDependencies": {
"vite": "catalog:",
"vite-plus": "catalog:"
},
"devEngines": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export default defineConfig({
> cat package.json # check package.json
{
"devDependencies": {
"vite": "catalog:",
"vite-plus": "catalog:"
},
"devEngines": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ declare module 'vitest/config' {
{
"name": "migration-rewrite-declare-module",
"devDependencies": {
"vite": "catalog:",
"vite-plus": "catalog:"
},
"devEngines": {
Expand Down
1 change: 1 addition & 0 deletions packages/cli/snap-tests-global/migration-subpath/snap.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
]
},
"devDependencies": {
"vite": "catalog:",
"vite-plus": "catalog:"
},
"devEngines": {
Expand Down
3 changes: 3 additions & 0 deletions packages/cli/snap-tests-global/new-vite-monorepo/snap.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ vite.config.ts
"prepare": "vp config"
},
"devDependencies": {
"vite": "catalog:",
"vite-plus": "catalog:"
},
"devEngines": {
Expand Down Expand Up @@ -162,6 +163,7 @@ website
"@typescript/native-preview": "7.0.0-dev.20260509.2",
"bumpp": "^11.1.0",
"typescript": "^6.0.3",
"vite": "catalog:",
"vite-plus": "catalog:"
}
}
Expand Down Expand Up @@ -250,6 +252,7 @@ vite.config.ts
"prepare": "vp config"
},
"devDependencies": {
"vite": "catalog:",
"vite-plus": "catalog:"
},
"devEngines": {
Expand Down
1 change: 1 addition & 0 deletions packages/cli/snap-tests/create-org-bundled/snap.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"prepare": "vp config"
},
"devDependencies": {
"vite": "catalog:",
"vite-plus": "catalog:"
},
"devEngines": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ exports[`rewritePackageJson > should rewrite devDependencies and dependencies on
"foo": "1.0.0",
},
"devDependencies": {
"vite": "catalog:",
"vite-plus": "catalog:",
},
}
Expand All @@ -28,6 +29,7 @@ exports[`rewritePackageJson > should rewrite devDependencies and dependencies on
"foo": "1.0.0",
},
"devDependencies": {
"vite": "npm:@voidzero-dev/vite-plus-core@latest",
"vite-plus": "latest",
},
}
Expand Down
110 changes: 97 additions & 13 deletions packages/cli/src/migration/__tests__/migrator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,83 @@ describe('rewritePackageJson', () => {
}
});

// Under pnpm, a package that depends on vite-plus needs a direct `vite` so
// vitest's required `vite` peer binds to the override (@voidzero-dev/vite-plus-core);
// otherwise pnpm's autoInstallPeers installs a second upstream vite and splits
// vite-plus / vite / vitest into duplicate instances.
describe('pnpm direct-vite dedupe (#1932)', () => {
it('adds a direct `vite` devDep when a package depends on vite-plus under pnpm', () => {
// monorepo sub-package -> catalog: (catalog.vite is written by rewriteCatalog)
const sub: { devDependencies: Record<string, string> } = {
devDependencies: { 'vite-plus': 'catalog:' },
};
rewritePackageJson(sub, PackageManager.pnpm, true);
expect(sub.devDependencies.vite).toBe('catalog:');
// inserted in sorted position (oxfmt sorts package.json), not appended
expect(Object.keys(sub.devDependencies)).toEqual(['vite', 'vite-plus']);

// standalone (no catalog) -> mirror the override target directly
const standalone: { devDependencies: Record<string, string> } = {
devDependencies: { 'vite-plus': 'latest' },
};
rewritePackageJson(standalone, PackageManager.pnpm);
expect(standalone.devDependencies.vite).toBe(VITE_PLUS_OVERRIDE_PACKAGES.vite);
});

it('does not add a direct `vite` for npm/yarn/bun (they dedupe via overrides/resolutions)', () => {
for (const pm of [PackageManager.npm, PackageManager.yarn, PackageManager.bun]) {
const pkg: { devDependencies: Record<string, string> } = {
devDependencies: { 'vite-plus': pm === PackageManager.npm ? '^0.1.20' : 'catalog:' },
};
rewritePackageJson(pkg, pm, true);
expect(pkg.devDependencies.vite).toBeUndefined();
}
});

it('does not add `vite` for a pnpm package that does not depend on vite-plus', () => {
const pkg: { devDependencies: Record<string, string> } = {
devDependencies: { typescript: '^5' },
};
rewritePackageJson(pkg, PackageManager.pnpm, true);
expect(pkg.devDependencies.vite).toBeUndefined();
});

it('keeps an existing direct `vite` instead of overwriting it under pnpm', () => {
const pkg: { devDependencies: Record<string, string> } = {
devDependencies: { 'vite-plus': 'catalog:', vite: 'catalog:vite7' },
};
rewritePackageJson(pkg, PackageManager.pnpm, true);
expect(pkg.devDependencies.vite).toBe('catalog:vite7');
});

it('does not inject a direct vite when vite is only a peerDependency under pnpm', () => {
// A vite plugin that pins `vite` as a peer must keep its own contract;
// injecting vite-plus-core as a concrete devDep would conflict with it.
const pkg: {
devDependencies: Record<string, string>;
peerDependencies: Record<string, string>;
} = {
devDependencies: { 'vite-plus': 'catalog:' },
peerDependencies: { vite: '^6.0.0' },
};
rewritePackageJson(pkg, PackageManager.pnpm, true);
expect(pkg.devDependencies.vite).toBeUndefined();
expect(pkg.peerDependencies.vite).toBe('^6.0.0');
});

it('does not add a second vite when an empty-string vite spec is already declared under pnpm', () => {
const pkg: {
dependencies: Record<string, string>;
devDependencies: Record<string, string>;
} = {
dependencies: { vite: '' },
devDependencies: { 'vite-plus': 'catalog:' },
};
rewritePackageJson(pkg, PackageManager.pnpm, true);
expect(pkg.devDependencies.vite).toBeUndefined();
});
});

it('preserves protocol-prefixed vite-plus specs (catalog:named, workspace:, link:, github:) in catalog-supporting monorepos', async () => {
for (const existing of [
'catalog:next',
Expand Down Expand Up @@ -425,19 +502,26 @@ describe('rewritePackageJson', () => {
expect(pkg.devDependencies).not.toHaveProperty('vite');
});

it('does not inject a direct vite devDependency for non-npm provider projects', async () => {
// pnpm/yarn use symlink/PnP layouts that already expose the `vite` override
// to the provider subtree, so the npm-only direct-`vite` workaround must not
// fire for them.
const pkg = {
devDependencies: {
'@vitest/browser-playwright': '^4.0.0',
playwright: '^1.60.0',
vitest: '^4.0.0',
},
};
rewritePackageJson(pkg, PackageManager.pnpm);
expect(pkg.devDependencies).not.toHaveProperty('vite');
it('injects a direct vite devDependency for pnpm projects depending on vite-plus, but not yarn/bun', async () => {
// pnpm needs a direct `vite` so vitest's `vite` peer binds to the override
// instead of pnpm auto-installing a separate upstream vite. yarn/bun redirect
// the transitive/peer vite via resolutions/overrides, so they do not get a
// direct `vite` here (the bun workspace root is handled separately).
for (const pm of [PackageManager.pnpm, PackageManager.yarn, PackageManager.bun]) {
const pkg: { devDependencies: Record<string, string> } = {
devDependencies: {
'@vitest/browser-playwright': '^4.0.0',
playwright: '^1.60.0',
vitest: '^4.0.0',
},
};
rewritePackageJson(pkg, pm);
if (pm === PackageManager.pnpm) {
expect(pkg.devDependencies).toHaveProperty('vite', VITE_PLUS_OVERRIDE_PACKAGES.vite);
} else {
expect(pkg.devDependencies).not.toHaveProperty('vite');
}
}
});

it('normalizes a pre-existing direct vite dep to the override target for an npm provider project', async () => {
Expand Down
Loading
Loading