Skip to content

Commit fb7397b

Browse files
committed
feat: default yarn nodeLinker to pnpm
Change the default yarn linker written by vp create and vp migrate from node-modules to pnpm (real on-disk node_modules via a content-addressable store, unlike Plug'n'Play zip entries that break @oxlint/migrate's fileURLToPath resolution). nodeLinker is only set when absent, so projects that already pin a linker keep their value. Migrating a yarn project without an explicit linker now switches it to pnpm's strict resolution.
1 parent ae58db4 commit fb7397b

7 files changed

Lines changed: 14 additions & 10 deletions

File tree

packages/cli/snap-tests-global/migration-monorepo-yarn4/snap.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ export default defineConfig({
7171
}
7272

7373
> cat .yarnrc.yml # check .yarnrc.yml
74-
nodeLinker: node-modules
74+
nodeLinker: pnpm
7575
catalog:
7676
vite: npm:@voidzero-dev/vite-plus-core@latest
7777
vitest: npm:@voidzero-dev/vite-plus-test@latest

packages/cli/src/create/__tests__/utils.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -352,12 +352,12 @@ describe('renameFiles', () => {
352352

353353
it('renames `_npmrc` and `_yarnrc.yml`', () => {
354354
write('_npmrc', 'auto-install-peers=true\n');
355-
write('_yarnrc.yml', 'nodeLinker: node-modules\n');
355+
write('_yarnrc.yml', 'nodeLinker: pnpm\n');
356356
renameFiles(projectDir);
357357
expect(exists('_npmrc')).toBe(false);
358358
expect(exists('_yarnrc.yml')).toBe(false);
359359
expect(read('.npmrc')).toBe('auto-install-peers=true\n');
360-
expect(read('.yarnrc.yml')).toBe('nodeLinker: node-modules\n');
360+
expect(read('.yarnrc.yml')).toBe('nodeLinker: pnpm\n');
361361
});
362362

363363
it('is a no-op when no source files exist', () => {

packages/cli/src/create/bin.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1300,14 +1300,15 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h
13001300
// migrate before the vite-plus rewrite so the generated .oxlintrc/.oxfmtrc
13011301
// get merged into vite.config.ts — matching `vp migrate`. Pin the
13021302
// packageManager field (vite_install hardcodes pnpm in CI/non-TTY when no
1303-
// signal is present) and force yarn's classic node_modules layout
1304-
// (Plug'n'Play zip entries break @oxlint/migrate's fileURLToPath resolution).
1303+
// signal is present) and force yarn's pnpm linker (its node_modules entries
1304+
// are real on-disk files, unlike Plug'n'Play zip entries that break
1305+
// @oxlint/migrate's fileURLToPath resolution).
13051306
const installAndMigrate = async (installCwd: string) => {
13061307
setPackageManager(fullPath, workspaceInfo.downloadPackageManager);
13071308
if (workspaceInfo.packageManager === PackageManager.yarn) {
13081309
const yarnrcPath = path.join(fullPath, '.yarnrc.yml');
13091310
if (!fs.existsSync(yarnrcPath)) {
1310-
fs.writeFileSync(yarnrcPath, 'nodeLinker: node-modules\n');
1311+
fs.writeFileSync(yarnrcPath, 'nodeLinker: pnpm\n');
13111312
}
13121313
}
13131314
updateCreateProgress('Installing dependencies');

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1340,7 +1340,7 @@ describe('ensureVitePlusBootstrap', () => {
13401340
nodeLinker: string;
13411341
catalog: Record<string, string>;
13421342
};
1343-
expect(yarnrc.nodeLinker).toBe('node-modules');
1343+
expect(yarnrc.nodeLinker).toBe('pnpm');
13441344
expect(yarnrc.catalog.vite).toBe('npm:@voidzero-dev/vite-plus-core@latest');
13451345
expect(yarnrc.catalog.vitest).toBe('npm:@voidzero-dev/vite-plus-test@latest');
13461346
expect(yarnrc.catalog['vite-plus']).toBe('latest');
@@ -1723,7 +1723,7 @@ describe('rewriteMonorepo yarn catalog', () => {
17231723
nodeLinker: string;
17241724
catalogs: Record<string, Record<string, string>>;
17251725
};
1726-
expect(yarnrc.nodeLinker).toBe('node-modules');
1726+
expect(yarnrc.nodeLinker).toBe('pnpm');
17271727
expect(yarnrc.catalogs.vite7.vite).toBe('npm:@voidzero-dev/vite-plus-core@latest');
17281728
expect(yarnrc.catalogs.vite7.react).toBe('^18.0.0');
17291729
expect(yarnrc.catalogs.test.vitest).toBe('npm:@voidzero-dev/vite-plus-test@latest');

packages/cli/src/migration/migrator.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1525,7 +1525,7 @@ function rewriteYarnrcYml(projectPath: string): void {
15251525

15261526
editYamlFile(yarnrcYmlPath, (doc) => {
15271527
if (!doc.has('nodeLinker')) {
1528-
doc.set('nodeLinker', 'node-modules');
1528+
doc.set('nodeLinker', 'pnpm');
15291529
}
15301530
// catalog
15311531
rewriteCatalog(doc);
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
nodeLinker: node-modules
1+
nodeLinker: pnpm
22
catalog:
33
'@types/node': ^24
44
typescript: ^5

rfcs/migration-command.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,11 +547,14 @@ peerDependencyRules:
547547
`.yarnrc.yml`
548548

549549
```yaml
550+
nodeLinker: pnpm
550551
catalog:
551552
vite: npm:@voidzero-dev/vite-plus-core@latest
552553
vitest: npm:@voidzero-dev/vite-plus-test@latest
553554
```
554555

556+
`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.
557+
555558
`package.json`
556559

557560
```json

0 commit comments

Comments
 (0)