Skip to content

Commit 1081f16

Browse files
committed
fix(migrate): gate catalog support on pnpm >= 9.5.0, not the 10.6.2 settings version
The existing-Vite+ upgrade path derived `supportCatalog` from `pnpmSupportsWorkspaceSettings` (pnpm >= 10.6.2, the "pnpm settings in pnpm-workspace.yaml" feature). But pnpm catalogs are a separate, earlier feature (9.5.0). A pnpm 9.5-10.6.1 project already using catalogs got `supportCatalog=false`, so its reconciled toolchain edges (vite, vite-plus, and the vitest ecosystem) were inlined to concrete versions instead of kept `catalog:` — also nullifying the #2005 aligned-@vitest/* fix. varlet (pnpm 9.15.9) hit this. Add `pnpmSupportsCatalog` (>= 9.5.0, per the pnpm 9.5.0 release notes) and use it for the three bootstrap-path `supportCatalog` computations, keeping `usePnpmWorkspaceYaml` (>= 10.6.2) for the settings-move logic. Adds pnpm 9.15.9 (supported) and 9.4.0 (unsupported) regression tests. Claude-Session: https://claude.ai/code/session_01DQhS6o1fyQd1yjiee6W8jR
1 parent d36064e commit 1081f16

4 files changed

Lines changed: 145 additions & 3 deletions

File tree

packages/cli/src/migration/__tests__/migrator.spec.ts

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2450,6 +2450,120 @@ describe('ensureVitePlusBootstrap', () => {
24502450
expect(cliPkg.devDependencies?.vite).toBe('catalog:');
24512451
});
24522452

2453+
it('keeps toolchain catalog: refs on a pnpm 9.5-10.6.1 catalog project (varlet-import-resolver #10)', () => {
2454+
const pkgDir = path.join(tmpDir, 'packages/import-resolver');
2455+
fs.mkdirSync(pkgDir, { recursive: true });
2456+
fs.writeFileSync(
2457+
path.join(tmpDir, 'package.json'),
2458+
JSON.stringify({
2459+
name: 'root',
2460+
private: true,
2461+
devDependencies: { 'vite-plus': 'catalog:' },
2462+
devEngines: { packageManager: { name: 'pnpm', version: '9.15.9', onFail: 'download' } },
2463+
}),
2464+
);
2465+
fs.writeFileSync(
2466+
path.join(pkgDir, 'package.json'),
2467+
JSON.stringify({
2468+
name: 'import-resolver',
2469+
devDependencies: { '@types/node': 'catalog:', 'vite-plus': 'catalog:' },
2470+
}),
2471+
);
2472+
fs.writeFileSync(
2473+
path.join(tmpDir, 'pnpm-workspace.yaml'),
2474+
[
2475+
'packages:',
2476+
' - packages/*',
2477+
'catalog:',
2478+
" '@types/node': ^20.19.0",
2479+
" vite: 'npm:@voidzero-dev/vite-plus-core@0.1.18'",
2480+
' vite-plus: 0.1.18',
2481+
'overrides:',
2482+
" vite: 'catalog:'",
2483+
'',
2484+
].join('\n'),
2485+
);
2486+
2487+
const workspaceInfo = {
2488+
...makeWorkspaceInfo(tmpDir, PackageManager.pnpm),
2489+
isMonorepo: true,
2490+
workspacePatterns: ['packages/*'],
2491+
packages: [{ name: 'import-resolver', path: 'packages/import-resolver' }],
2492+
};
2493+
workspaceInfo.downloadPackageManager = {
2494+
...workspaceInfo.downloadPackageManager,
2495+
version: '9.15.9',
2496+
};
2497+
ensureVitePlusBootstrap(workspaceInfo);
2498+
2499+
const pkg = readJson(path.join(pkgDir, 'package.json')) as {
2500+
devDependencies: Record<string, string>;
2501+
};
2502+
// pnpm 9.15.9 supports catalogs (>= 9.5.0), so the reconciled toolchain edges
2503+
// stay catalog: rather than being inlined to the concrete toolchain version.
2504+
expect(pkg.devDependencies['vite-plus']).toBe('catalog:');
2505+
expect(pkg.devDependencies.vite).toBe('catalog:');
2506+
expect(pkg.devDependencies['@types/node']).toBe('catalog:');
2507+
});
2508+
2509+
it('pins the toolchain to concrete on pnpm < 9.5.0 (catalogs unsupported)', () => {
2510+
// pnpm added catalogs in 9.5.0; below that they cannot resolve, so the
2511+
// reconciled toolchain edges must stay concrete rather than `catalog:`.
2512+
const pkgDir = path.join(tmpDir, 'packages/import-resolver');
2513+
fs.mkdirSync(pkgDir, { recursive: true });
2514+
fs.writeFileSync(
2515+
path.join(tmpDir, 'package.json'),
2516+
JSON.stringify({
2517+
name: 'root',
2518+
private: true,
2519+
devDependencies: { 'vite-plus': 'catalog:' },
2520+
devEngines: { packageManager: { name: 'pnpm', version: '9.4.0', onFail: 'download' } },
2521+
}),
2522+
);
2523+
fs.writeFileSync(
2524+
path.join(pkgDir, 'package.json'),
2525+
JSON.stringify({
2526+
name: 'import-resolver',
2527+
devDependencies: { '@types/node': 'catalog:', 'vite-plus': 'catalog:' },
2528+
}),
2529+
);
2530+
fs.writeFileSync(
2531+
path.join(tmpDir, 'pnpm-workspace.yaml'),
2532+
[
2533+
'packages:',
2534+
' - packages/*',
2535+
'catalog:',
2536+
" '@types/node': ^20.19.0",
2537+
" vite: 'npm:@voidzero-dev/vite-plus-core@0.1.18'",
2538+
' vite-plus: 0.1.18',
2539+
'overrides:',
2540+
" vite: 'catalog:'",
2541+
'',
2542+
].join('\n'),
2543+
);
2544+
2545+
const workspaceInfo = {
2546+
...makeWorkspaceInfo(tmpDir, PackageManager.pnpm),
2547+
isMonorepo: true,
2548+
workspacePatterns: ['packages/*'],
2549+
packages: [{ name: 'import-resolver', path: 'packages/import-resolver' }],
2550+
};
2551+
workspaceInfo.downloadPackageManager = {
2552+
...workspaceInfo.downloadPackageManager,
2553+
version: '9.4.0',
2554+
};
2555+
ensureVitePlusBootstrap(workspaceInfo);
2556+
2557+
const pkg = readJson(path.join(pkgDir, 'package.json')) as {
2558+
devDependencies: Record<string, string>;
2559+
};
2560+
// 9.4.0 < 9.5.0: toolchain edges are concrete (the direct vite is the core alias).
2561+
expect(pkg.devDependencies['vite-plus']).not.toBe('catalog:');
2562+
expect(pkg.devDependencies.vite).toContain('@voidzero-dev/vite-plus-core@');
2563+
// Untouched non-toolchain catalog refs are left as-is.
2564+
expect(pkg.devDependencies['@types/node']).toBe('catalog:');
2565+
});
2566+
24532567
it('does not align deprecated @vitest/coverage-c8 to a nonexistent Vitest 4 version', () => {
24542568
fs.writeFileSync(
24552569
path.join(tmpDir, 'package.json'),

packages/cli/src/migration/migrator/catalog.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,25 @@ export function pnpmSupportsWorkspaceSettings(version: string): boolean {
8585
return version === 'latest' || version === 'next';
8686
}
8787

88+
const PNPM_CATALOG_MIN_VERSION = '9.5.0';
89+
90+
// pnpm catalogs (the `catalog:` protocol and the pnpm-workspace.yaml
91+
// `catalog`/`catalogs` fields) shipped in pnpm 9.5.0 as a minor change ("Added
92+
// support for catalogs", https://github.com/pnpm/pnpm/releases/tag/v9.5.0), and
93+
// every release from 9.5.0 onward supports them. This is a SEPARATE, EARLIER
94+
// feature than moving package.json#pnpm settings into pnpm-workspace.yaml
95+
// (`pnpmSupportsWorkspaceSettings`, 10.6.2). `supportCatalog` must gate on THIS,
96+
// not on workspace-settings support: otherwise a pnpm 9.5–10.6.1 project that
97+
// already uses catalogs has its reconciled toolchain edges (vite/vite-plus and
98+
// the vitest ecosystem) inlined to concrete versions instead of kept `catalog:`.
99+
export function pnpmSupportsCatalog(version: string): boolean {
100+
const coerced = semver.coerce(version);
101+
if (coerced) {
102+
return semver.gte(coerced, PNPM_CATALOG_MIN_VERSION);
103+
}
104+
return version === 'latest' || version === 'next';
105+
}
106+
88107
const YARN_CATALOG_MIN_VERSION = '4.10.0';
89108

90109
// Yarn's `catalog:` protocol (and the `.yarnrc.yml` `catalog`/`catalogs` fields)

packages/cli/src/migration/migrator/orchestrators.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
mergeViteConfigFiles,
2727
migratePnpmOverridesToWorkspaceYaml,
2828
migratePnpmSettingsToWorkspaceYaml,
29+
pnpmSupportsCatalog,
2930
pnpmSupportsWorkspaceSettings,
3031
projectListsRequiredVitestPeer,
3132
projectUsesVitestDirectly,
@@ -111,7 +112,8 @@ export function rewriteStandaloneProject(
111112
// catalogs require Yarn >= 4.10.0 (older Yarn cannot resolve `catalog:`), so a
112113
// project resolving to an older Yarn falls back to concrete specs.
113114
const supportCatalog =
114-
usePnpmWorkspaceYaml ||
115+
(packageManager === PackageManager.pnpm &&
116+
pnpmSupportsCatalog(workspaceInfo.downloadPackageManager.version)) ||
115117
(packageManager === PackageManager.yarn &&
116118
yarnSupportsCatalog(workspaceInfo.downloadPackageManager.version));
117119
editJsonFile<{

packages/cli/src/migration/migrator/vite-plus-bootstrap.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {
3131
migratePnpmSettingsToWorkspaceYaml,
3232
normalizeVitestPeerCatalogSpec,
3333
pnpmPackageJsonSettingsPending,
34+
pnpmSupportsCatalog,
3435
pnpmSupportsWorkspaceSettings,
3536
pnpmWorkspaceMinimumReleaseAgeExemptionsPending,
3637
projectUsesVitestDirectly,
@@ -661,7 +662,10 @@ export function detectVitePlusBootstrapPending(
661662
}
662663
const supportCatalog =
663664
!VITE_PLUS_VERSION.startsWith('file:') &&
664-
(usePnpmWorkspaceYaml ||
665+
// pnpm catalogs require pnpm >= 9.5.0 — a SEPARATE gate from the 10.6.2
666+
// `usePnpmWorkspaceYaml` (workspace settings) still used elsewhere here.
667+
((packageManager === PackageManager.pnpm &&
668+
pnpmSupportsCatalog(resolvedPackageManagerVersion)) ||
665669
// Yarn catalogs require Yarn >= 4.10.0 (older Yarn cannot resolve `catalog:`).
666670
(packageManager === PackageManager.yarn &&
667671
yarnSupportsCatalog(resolvedPackageManagerVersion)) ||
@@ -931,7 +935,10 @@ export function ensureVitePlusBootstrap(
931935
);
932936
const supportCatalog =
933937
!VITE_PLUS_VERSION.startsWith('file:') &&
934-
(usePnpmWorkspaceYaml ||
938+
// pnpm catalogs require pnpm >= 9.5.0 — a SEPARATE gate from the 10.6.2
939+
// `usePnpmWorkspaceYaml` (workspace settings) still used elsewhere here.
940+
((workspaceInfo.packageManager === PackageManager.pnpm &&
941+
pnpmSupportsCatalog(workspaceInfo.downloadPackageManager.version)) ||
935942
// Yarn catalogs require Yarn >= 4.10.0; older Yarn cannot resolve `catalog:`.
936943
(workspaceInfo.packageManager === PackageManager.yarn &&
937944
yarnSupportsCatalog(workspaceInfo.downloadPackageManager.version)) ||

0 commit comments

Comments
 (0)