Skip to content
Open
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
51 changes: 51 additions & 0 deletions .github/workflows/test-vp-create.yml
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,57 @@ jobs:
console.log('✓ vite-plus@' + pkg.version + ' installed correctly');
"

- name: Verify monorepo sub-package deps
if: matrix.template.name == 'monorepo'
working-directory: ${{ runner.temp }}/test-project
env:
PACKAGE_MANAGER: ${{ matrix.package-manager }}
run: |
# Issue 1: packages/utils inherits `vite-plus: ^x.y.z` from the
# library template. In catalog-supporting monorepos (pnpm/yarn/bun)
# the migrator must normalize it so siblings don't drift.
# Issue 2: apps/website is scaffolded by create-vite which ships
# `vite` (and sometimes `vitest`) in devDependencies. After
# migration the scripts are rewritten to `vp ...` and `vite-plus`
# brings the runtime in transitively, so those keys must be gone.
node -e "
const fs = require('fs');
const pm = process.env.PACKAGE_MANAGER;
for (const f of ['apps/website/package.json', 'packages/utils/package.json']) {
if (!fs.existsSync(f)) {
console.error('✗ expected ' + f + ' to exist after vp create vite:monorepo');
process.exit(1);
}
}
const app = JSON.parse(fs.readFileSync('apps/website/package.json', 'utf8'));
const utils = JSON.parse(fs.readFileSync('packages/utils/package.json', 'utf8'));
const appDev = app.devDependencies || {};
const utilsDev = utils.devDependencies || {};

for (const name of ['vite', 'vitest']) {
if (appDev[name]) {
console.error('✗ apps/website devDependencies still has ' + name + ': ' + appDev[name]);
process.exit(1);
}
}
console.log('✓ apps/website devDependencies has no vite/vitest');

if (!appDev['vite-plus']) {
console.error('✗ apps/website missing vite-plus devDependency');
process.exit(1);
}
if (!utilsDev['vite-plus']) {
console.error('✗ packages/utils missing vite-plus devDependency');
process.exit(1);
}

if (pm !== 'npm' && appDev['vite-plus'] !== utilsDev['vite-plus']) {
console.error('✗ vite-plus spec drift: apps/website=' + appDev['vite-plus'] + ' packages/utils=' + utilsDev['vite-plus']);
process.exit(1);
}
console.log('✓ vite-plus consistent across sub-packages: app=' + appDev['vite-plus'] + ' utils=' + utilsDev['vite-plus']);
"

- name: Verify ESLint/Prettier auto-migration
if: matrix.template.verify-migration == 'true'
working-directory: ${{ runner.temp }}/test-project
Expand Down
43 changes: 42 additions & 1 deletion packages/cli/snap-tests-global/new-vite-monorepo-bun/snap.txt
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,48 @@ website
},
"devDependencies": {
"typescript": "~6.0.2",
"vite": "catalog:",
"vite-plus": "catalog:"
}
}

> cat vite-plus-monorepo/packages/utils/package.json # check utils normalizes vite-plus to catalog:
{
"name": "utils",
"version": "0.0.0",
"description": "A starter for creating a TypeScript package.",
"homepage": "https://github.com/author/library#readme",
"bugs": {
"url": "https://github.com/author/library/issues"
},
"license": "MIT",
"author": "Author Name <author.name@mail.com>",
"repository": {
"type": "git",
"url": "git+https://github.com/author/library.git"
},
"files": [
"dist"
],
"type": "module",
"exports": {
".": "./dist/index.mjs",
"./package.json": "./package.json"
},
"publishConfig": {
"access": "public"
},
"scripts": {
"build": "vp pack",
"dev": "vp pack --watch",
"test": "vp test",
"check": "vp check",
"prepublishOnly": "vp run build"
},
"devDependencies": {
"@types/node": "^25.6.2",
"@typescript/native-preview": "7.0.0-dev.20260509.2",
"bumpp": "^11.1.0",
"typescript": "^6.0.3",
"vite-plus": "catalog:"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"test ! -f vite-plus-monorepo/.yarnrc.yml && echo 'No .yarnrc.yml' || echo 'ERROR: .yarnrc.yml exists' # verify no yarn config",
"test -d vite-plus-monorepo/.git && echo 'Git initialized' # check git init",
"ls vite-plus-monorepo/apps # check apps directory",
"cat vite-plus-monorepo/apps/website/package.json # check website uses catalog:"
"cat vite-plus-monorepo/apps/website/package.json # check website uses catalog:",
"cat vite-plus-monorepo/packages/utils/package.json # check utils normalizes vite-plus to catalog:"
]
}
60 changes: 58 additions & 2 deletions packages/cli/snap-tests-global/new-vite-monorepo/snap.txt
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,64 @@ Git initialized
> ls vite-plus-monorepo/apps # check apps directory created
website

> ls vite-plus-monorepo/apps/website/package.json # check website package.json
vite-plus-monorepo/apps/website/package.json
> cat vite-plus-monorepo/apps/website/package.json # check website strips aliased vite/vitest
{
"name": "website",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vp dev",
"build": "tsc && vp build",
"preview": "vp preview"
},
"devDependencies": {
"typescript": "~6.0.2",
"vite-plus": "catalog:"
}
}

> cat vite-plus-monorepo/packages/utils/package.json # check utils normalizes vite-plus to catalog:
{
"name": "utils",
"version": "0.0.0",
"description": "A starter for creating a TypeScript package.",
"homepage": "https://github.com/author/library#readme",
"bugs": {
"url": "https://github.com/author/library/issues"
},
"license": "MIT",
"author": "Author Name <author.name@mail.com>",
"repository": {
"type": "git",
"url": "git+https://github.com/author/library.git"
},
"files": [
"dist"
],
"type": "module",
"exports": {
".": "./dist/index.mjs",
"./package.json": "./package.json"
},
"publishConfig": {
"access": "public"
},
"scripts": {
"build": "vp pack",
"dev": "vp pack --watch",
"test": "vp test",
"check": "vp check",
"prepublishOnly": "vp run build"
},
"devDependencies": {
"@types/node": "^25.6.2",
"@typescript/native-preview": "7.0.0-dev.20260509.2",
"bumpp": "^11.1.0",
"typescript": "^6.0.3",
"vite-plus": "catalog:"
}
}

> cd vite-plus-monorepo && vp create --no-interactive vite:application # create application in non-interactive mode
> ls vite-plus-monorepo/apps # check apps directory created
Expand Down
3 changes: 2 additions & 1 deletion packages/cli/snap-tests-global/new-vite-monorepo/steps.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
"test ! -f vite-plus-monorepo/.yarnrc.yml && echo 'No .yarnrc.yml' || echo 'ERROR: .yarnrc.yml exists' # verify no yarn config for pnpm",
"test -d vite-plus-monorepo/.git && echo 'Git initialized' # check git init",
"ls vite-plus-monorepo/apps # check apps directory created",
"ls vite-plus-monorepo/apps/website/package.json # check website package.json",
"cat vite-plus-monorepo/apps/website/package.json # check website strips aliased vite/vitest",
"cat vite-plus-monorepo/packages/utils/package.json # check utils normalizes vite-plus to catalog:",
{
"command": "cd vite-plus-monorepo && vp create --no-interactive vite:application # create application in non-interactive mode",
"ignoreOutput": true
Expand Down
16 changes: 16 additions & 0 deletions packages/cli/src/create/templates/monorepo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,22 @@ export async function executeMonorepoTemplate(
undefined,
options?.silent ?? false,
);
// Drop the aliased vite/vitest devDeps left by the migrator: the
// create-vite scripts were rewritten to `vp ...`, and the fresh
// template has no user `import 'vite'`, so vite-plus brings them in.
editJsonFile<{ devDependencies?: Record<string, string> }>(
path.join(appProjectPath, 'package.json'),
(pkg) => {
let changed = false;
for (const name of ['vite', 'vitest']) {
if (pkg.devDependencies?.[name]) {
delete pkg.devDependencies[name];
changed = true;
}
}
return changed ? pkg : undefined;
},
);

// Automatically create a default library in packages/utils
if (!options?.silent) {
Expand Down
60 changes: 60 additions & 0 deletions packages/cli/src/migration/__tests__/migrator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,66 @@ describe('rewritePackageJson', () => {
expect((pkg.devDependencies as Record<string, string>)['vite-plus']).toBe('catalog:');
});

it('normalizes a pre-existing pinned vite-plus to `catalog:` in catalog-supporting monorepos', async () => {
const pkg = {
devDependencies: {
'vite-plus': '^0.1.20',
},
};

rewritePackageJson(pkg, PackageManager.pnpm, true);

expect(pkg.devDependencies['vite-plus']).toBe('catalog:');
});

it('leaves a pre-existing pinned vite-plus alone on npm monorepo projects', async () => {
const pkg = {
devDependencies: {
'vite-plus': '^0.1.20',
},
};

rewritePackageJson(pkg, PackageManager.npm, true);

expect(pkg.devDependencies['vite-plus']).toBe('^0.1.20');
});

it('normalizes a pre-existing pinned vite-plus on yarn/bun monorepo projects', async () => {
for (const pm of [PackageManager.yarn, PackageManager.bun]) {
const pkg = { devDependencies: { 'vite-plus': '^0.1.20' } };
rewritePackageJson(pkg, pm, true);
expect(pkg.devDependencies['vite-plus']).toBe('catalog:');
}
});

it('preserves protocol-prefixed vite-plus specs (catalog:named, workspace:, link:, github:) in catalog-supporting monorepos', async () => {
for (const existing of [
'catalog:next',
'workspace:*',
'link:../vite-plus',
'github:fork/vite-plus',
'npm:@scope/vite-plus@^1.0.0',
]) {
const pkg = { devDependencies: { 'vite-plus': existing } };
rewritePackageJson(pkg, PackageManager.pnpm, true);
expect(pkg.devDependencies['vite-plus']).toBe(existing);
}
});

it('does not auto-add vitest on a pure normalize pass (only on actual vite/vitest/REMOVE migrations)', async () => {
const pkg = {
devDependencies: {
'vite-plus': '^0.1.20',
'vitest-browser-svelte': '^1.0.0',
},
};

rewritePackageJson(pkg, PackageManager.pnpm, true);

expect(pkg.devDependencies['vite-plus']).toBe('catalog:');
expect((pkg.devDependencies as Record<string, string>).vitest).toBeUndefined();
});

it('uses default catalog specs for non-catalog dependency specs in monorepo projects', async () => {
const pkg = {
devDependencies: {
Expand Down
41 changes: 32 additions & 9 deletions packages/cli/src/migration/migrator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1983,18 +1983,33 @@ export function rewritePackageJson(
pkg.devDependencies[peerDep] = '*';
}
}
if (needVitePlus) {
// add vite-plus to devDependencies
const version =
supportCatalog && !VITE_PLUS_VERSION.startsWith('file:') ? 'catalog:' : VITE_PLUS_VERSION;
// Normalize a pre-existing pinned vite-plus so sub-packages don't drift
// from siblings: in catalog-supporting monorepos that's `catalog:`, under
// force-override (file:) it's the tgz path. Preserve protocol-prefixed
// specs (catalog:named, workspace:*, link:, file:, npm:, github:, git+/git:,
// http(s)://) so deliberate user pins survive; only vanilla version ranges
// (e.g. `^0.1.20`, `latest`) are rewritten.
const canonicalVitePlusSpec =
supportCatalog && !VITE_PLUS_VERSION.startsWith('file:') ? 'catalog:' : VITE_PLUS_VERSION;
const existingVitePlus = pkg.devDependencies?.[VITE_PLUS_NAME];
const shouldNormalizeExistingVitePlus =
!!existingVitePlus &&
supportCatalog &&
existingVitePlus !== canonicalVitePlusSpec &&
!isProtocolPinnedSpec(existingVitePlus);
if (needVitePlus || shouldNormalizeExistingVitePlus) {
pkg.devDependencies = {
...pkg.devDependencies,
[VITE_PLUS_NAME]: version,
[VITE_PLUS_NAME]: canonicalVitePlusSpec,
};
// Add vitest to devDependencies when a remaining dependency likely peer-depends
// on vitest (e.g., vitest-browser-svelte). Without this, pnpm resolves the real
// vitest for peer deps instead of @voidzero-dev/vite-plus-test, causing
// third-party type augmentations to target the wrong module.
}
// Add vitest to devDependencies when a remaining dependency likely peer-depends
// on vitest (e.g., vitest-browser-svelte). Without this, pnpm resolves the real
// vitest for peer deps instead of @voidzero-dev/vite-plus-test, causing
// third-party type augmentations to target the wrong module. Gated by
// needVitePlus (something actually changed) — a pure normalize pass must not
// mutate the project beyond the vite-plus spec.
if (needVitePlus) {
const installableDeps = {
...pkg.dependencies,
...pkg.devDependencies,
Expand All @@ -2005,12 +2020,20 @@ export function rewritePackageJson(
Object.keys(installableDeps).some((name) => name.includes('vitest'))
) {
const ver = VITE_PLUS_OVERRIDE_PACKAGES.vitest;
pkg.devDependencies ??= {};
pkg.devDependencies.vitest = getCatalogDependencySpec(undefined, ver, supportCatalog);
}
}
return extractedStagedConfig;
}

// Returns true if the spec uses a known protocol prefix (catalog:, workspace:,
// link:, file:, npm:, github:, git+/git:, http(s)://) and so represents a
// deliberate user choice that should not be silently rewritten.
function isProtocolPinnedSpec(spec: string): boolean {
return /^(catalog:|workspace:|link:|file:|npm:|github:|git[+:]|https?:\/\/)/.test(spec);
}

// Remove the "lint-staged" key from package.json after config has been
// successfully merged into vite.config.ts.
function removeLintStagedFromPackageJson(packageJsonPath: string): void {
Expand Down
Loading