diff --git a/packages/cli/snap-tests-global/migration-monorepo-yarn4/snap.txt b/packages/cli/snap-tests-global/migration-monorepo-yarn4/snap.txt index d29b5d8f55..aa5c45c326 100644 --- a/packages/cli/snap-tests-global/migration-monorepo-yarn4/snap.txt +++ b/packages/cli/snap-tests-global/migration-monorepo-yarn4/snap.txt @@ -71,7 +71,7 @@ export default defineConfig({ } > cat .yarnrc.yml # check .yarnrc.yml -nodeLinker: node-modules +nodeLinker: pnpm catalog: vite: npm:@voidzero-dev/vite-plus-core@latest vitest: npm:@voidzero-dev/vite-plus-test@latest diff --git a/packages/cli/src/create/__tests__/utils.spec.ts b/packages/cli/src/create/__tests__/utils.spec.ts index ecec51f39c..8a0d379c75 100644 --- a/packages/cli/src/create/__tests__/utils.spec.ts +++ b/packages/cli/src/create/__tests__/utils.spec.ts @@ -352,12 +352,12 @@ describe('renameFiles', () => { it('renames `_npmrc` and `_yarnrc.yml`', () => { write('_npmrc', 'auto-install-peers=true\n'); - write('_yarnrc.yml', 'nodeLinker: node-modules\n'); + write('_yarnrc.yml', 'nodeLinker: pnpm\n'); renameFiles(projectDir); expect(exists('_npmrc')).toBe(false); expect(exists('_yarnrc.yml')).toBe(false); expect(read('.npmrc')).toBe('auto-install-peers=true\n'); - expect(read('.yarnrc.yml')).toBe('nodeLinker: node-modules\n'); + expect(read('.yarnrc.yml')).toBe('nodeLinker: pnpm\n'); }); it('is a no-op when no source files exist', () => { diff --git a/packages/cli/src/create/bin.ts b/packages/cli/src/create/bin.ts index feb23261d8..daf407768d 100644 --- a/packages/cli/src/create/bin.ts +++ b/packages/cli/src/create/bin.ts @@ -1300,14 +1300,15 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h // migrate before the vite-plus rewrite so the generated .oxlintrc/.oxfmtrc // get merged into vite.config.ts — matching `vp migrate`. Pin the // packageManager field (vite_install hardcodes pnpm in CI/non-TTY when no - // signal is present) and force yarn's classic node_modules layout - // (Plug'n'Play zip entries break @oxlint/migrate's fileURLToPath resolution). + // signal is present) and force yarn's pnpm linker (its node_modules entries + // are real on-disk files, unlike Plug'n'Play zip entries that break + // @oxlint/migrate's fileURLToPath resolution). const installAndMigrate = async (installCwd: string) => { setPackageManager(fullPath, workspaceInfo.downloadPackageManager); if (workspaceInfo.packageManager === PackageManager.yarn) { const yarnrcPath = path.join(fullPath, '.yarnrc.yml'); if (!fs.existsSync(yarnrcPath)) { - fs.writeFileSync(yarnrcPath, 'nodeLinker: node-modules\n'); + fs.writeFileSync(yarnrcPath, 'nodeLinker: pnpm\n'); } } updateCreateProgress('Installing dependencies'); diff --git a/packages/cli/src/migration/__tests__/migrator.spec.ts b/packages/cli/src/migration/__tests__/migrator.spec.ts index 3963fc91bc..1d68acb150 100644 --- a/packages/cli/src/migration/__tests__/migrator.spec.ts +++ b/packages/cli/src/migration/__tests__/migrator.spec.ts @@ -1340,7 +1340,7 @@ describe('ensureVitePlusBootstrap', () => { nodeLinker: string; catalog: Record; }; - expect(yarnrc.nodeLinker).toBe('node-modules'); + expect(yarnrc.nodeLinker).toBe('pnpm'); expect(yarnrc.catalog.vite).toBe('npm:@voidzero-dev/vite-plus-core@latest'); expect(yarnrc.catalog.vitest).toBe('npm:@voidzero-dev/vite-plus-test@latest'); expect(yarnrc.catalog['vite-plus']).toBe('latest'); @@ -1723,7 +1723,7 @@ describe('rewriteMonorepo yarn catalog', () => { nodeLinker: string; catalogs: Record>; }; - expect(yarnrc.nodeLinker).toBe('node-modules'); + expect(yarnrc.nodeLinker).toBe('pnpm'); expect(yarnrc.catalogs.vite7.vite).toBe('npm:@voidzero-dev/vite-plus-core@latest'); expect(yarnrc.catalogs.vite7.react).toBe('^18.0.0'); expect(yarnrc.catalogs.test.vitest).toBe('npm:@voidzero-dev/vite-plus-test@latest'); diff --git a/packages/cli/src/migration/migrator.ts b/packages/cli/src/migration/migrator.ts index e6950b80da..c483944b5a 100644 --- a/packages/cli/src/migration/migrator.ts +++ b/packages/cli/src/migration/migrator.ts @@ -1525,7 +1525,7 @@ function rewriteYarnrcYml(projectPath: string): void { editYamlFile(yarnrcYmlPath, (doc) => { if (!doc.has('nodeLinker')) { - doc.set('nodeLinker', 'node-modules'); + doc.set('nodeLinker', 'pnpm'); } // catalog rewriteCatalog(doc); diff --git a/packages/cli/templates/monorepo/_yarnrc.yml b/packages/cli/templates/monorepo/_yarnrc.yml index 3f2226d6f8..f15ef0bf9b 100644 --- a/packages/cli/templates/monorepo/_yarnrc.yml +++ b/packages/cli/templates/monorepo/_yarnrc.yml @@ -1,4 +1,4 @@ -nodeLinker: node-modules +nodeLinker: pnpm catalog: '@types/node': ^24 typescript: ^5 diff --git a/rfcs/migration-command.md b/rfcs/migration-command.md index db14eacfee..ccb1839161 100644 --- a/rfcs/migration-command.md +++ b/rfcs/migration-command.md @@ -547,11 +547,14 @@ peerDependencyRules: `.yarnrc.yml` ```yaml +nodeLinker: pnpm catalog: vite: npm:@voidzero-dev/vite-plus-core@latest vitest: npm:@voidzero-dev/vite-plus-test@latest ``` +`nodeLinker` is only added when absent, so a project that already pins a linker keeps its own value. The `pnpm` linker is used (real on-disk `node_modules` entries via a content-addressable store) rather than yarn's default Plug'n'Play, whose zip entries break `@oxlint/migrate`'s `fileURLToPath` resolution. + `package.json` ```json