Skip to content

Commit 64b4525

Browse files
committed
feat(init): opt yarn projects out of plug'n'play
1 parent 9ff68d9 commit 64b4525

2 files changed

Lines changed: 58 additions & 2 deletions

File tree

packages/nuxt-cli/src/commands/init.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type { InstallResult } from '../utils/install'
55
import type { TemplateData } from '../utils/starter-templates'
66

77
import { existsSync } from 'node:fs'
8+
import { writeFile } from 'node:fs/promises'
89
import process from 'node:process'
910
import { Writable } from 'node:stream'
1011

@@ -85,6 +86,27 @@ async function reportMissingNonInteractiveArgs<T extends ArgsDef>(
8586
.join(', ')}`)
8687
}
8788

89+
const YARN_NODE_LINKER = `# Nuxt cannot resolve its modules under Yarn's default Plug'n'Play linker.
90+
# See https://github.com/nuxt/nuxt/issues/26750
91+
nodeLinker: node-modules
92+
`
93+
94+
/**
95+
* Opt a scaffolded project out of Yarn's Plug'n'Play linker, under which
96+
* `nuxt prepare` cannot resolve `@nuxt/kit` and the project will not build.
97+
*
98+
* Templates that ship any Yarn configuration of their own are left alone, so a
99+
* template can still choose Plug'n'Play. Yarn 1 ignores `.yarnrc.yml`, so
100+
* writing it there is harmless.
101+
*/
102+
export async function useYarnNodeModulesLinker(dir: string): Promise<boolean> {
103+
if (existsSync(join(dir, '.yarnrc.yml')) || existsSync(join(dir, '.yarnrc'))) {
104+
return false
105+
}
106+
await writeFile(join(dir, '.yarnrc.yml'), YARN_NODE_LINKER, 'utf8')
107+
return true
108+
}
109+
88110
/**
89111
* Commands to print in the closing 'Next steps' box, in the order to run them.
90112
*
@@ -505,6 +527,10 @@ export default defineCommand({
505527
selectedPackageManager = result
506528
}
507529

530+
if (selectedPackageManager === 'yarn' && await useYarnNodeModulesLinker(template.dir)) {
531+
logger.info(`Created ${colors.cyan('.yarnrc.yml')} with ${colors.cyan('nodeLinker: node-modules')}, as Nuxt cannot resolve its modules under Yarn's Plug'n'Play linker.`)
532+
}
533+
508534
// Determine if we should init git
509535
let gitInit: boolean | undefined = ctx.args.gitInit === 'false' as unknown ? false : ctx.args.gitInit
510536
if (gitInit === undefined) {

packages/nuxt-cli/test/unit/commands/init.spec.ts

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,39 @@
1-
import { mkdtemp, rm, writeFile } from 'node:fs/promises'
1+
import { existsSync } from 'node:fs'
2+
import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
23
import { tmpdir } from 'node:os'
34
import { join } from 'node:path'
45
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
56

6-
import { detectTemplatePackageManager, getNextSteps } from '../../../src/commands/init'
7+
import { detectTemplatePackageManager, getNextSteps, useYarnNodeModulesLinker } from '../../../src/commands/init'
8+
9+
describe('useYarnNodeModulesLinker', () => {
10+
let dir: string
11+
12+
beforeEach(async () => {
13+
dir = await mkdtemp(join(tmpdir(), 'nuxt-init-yarn-'))
14+
})
15+
16+
afterEach(async () => {
17+
await rm(dir, { recursive: true, force: true })
18+
})
19+
20+
it('should opt a fresh project out of plug\'n\'play', async () => {
21+
expect(await useYarnNodeModulesLinker(dir)).toBe(true)
22+
expect(await readFile(join(dir, '.yarnrc.yml'), 'utf8')).toContain('nodeLinker: node-modules')
23+
})
24+
25+
it('should not overwrite yarn configuration from a template', async () => {
26+
await writeFile(join(dir, '.yarnrc.yml'), 'nodeLinker: pnp\n')
27+
expect(await useYarnNodeModulesLinker(dir)).toBe(false)
28+
expect(await readFile(join(dir, '.yarnrc.yml'), 'utf8')).toBe('nodeLinker: pnp\n')
29+
})
30+
31+
it('should not add yarn 2+ configuration alongside a yarn 1 config', async () => {
32+
await writeFile(join(dir, '.yarnrc'), 'registry "https://example.com"\n')
33+
expect(await useYarnNodeModulesLinker(dir)).toBe(false)
34+
expect(existsSync(join(dir, '.yarnrc.yml'))).toBe(false)
35+
})
36+
})
737

838
describe('getNextSteps', () => {
939
const base = { shell: false, recoveryCommands: [] as string[], packageManager: 'npm' as const }

0 commit comments

Comments
 (0)