diff --git a/.changeset/wet-monkeys-guess.md b/.changeset/wet-monkeys-guess.md new file mode 100644 index 0000000000..0384932887 --- /dev/null +++ b/.changeset/wet-monkeys-guess.md @@ -0,0 +1,5 @@ +--- +"@pnpm/headless": patch +--- + +Don't create broken symlinks in subprojects that have external symlinks, when the linked dependencies are excluded from the lockfile. diff --git a/pkg-manager/core/test/install/excludeLinksFromLockfile.ts b/pkg-manager/core/test/install/excludeLinksFromLockfile.ts index d1e90b0762..adba46779e 100644 --- a/pkg-manager/core/test/install/excludeLinksFromLockfile.ts +++ b/pkg-manager/core/test/install/excludeLinksFromLockfile.ts @@ -20,8 +20,11 @@ const f = fixtures(__dirname) test('links are not added to the lockfile when excludeLinksFromLockfile is true', async () => { const externalPkg1 = tempDir(false) + fs.writeFileSync(path.join(externalPkg1, 'index.js'), '', 'utf8') const externalPkg2 = tempDir(false) + fs.writeFileSync(path.join(externalPkg2, 'index.js'), '', 'utf8') const externalPkg3 = tempDir(false) + fs.writeFileSync(path.join(externalPkg3, 'index.js'), '', 'utf8') preparePackages([ { location: 'project-1', @@ -53,7 +56,7 @@ test('links are not added to the lockfile when excludeLinksFromLockfile is true' dependencies: { 'is-positive': '1.0.0', - 'external-1': `link:${path.relative(project1Dir, externalPkg1)}`, + 'external-1': `link:${externalPkg1}`, }, }, rootDir: project1Dir, @@ -77,8 +80,8 @@ test('links are not added to the lockfile when excludeLinksFromLockfile is true' expect(lockfile.importers['project-1'].dependencies?.['external-1']).toBeUndefined() expect(lockfile.importers['project-2'].dependencies?.['external-2']).toBeUndefined() - expect(fs.existsSync(path.resolve('project-1/node_modules/external-1'))).toBeTruthy() - expect(fs.existsSync(path.resolve('project-2/node_modules/external-2'))).toBeTruthy() + expect(fs.existsSync(path.resolve('project-1/node_modules/external-1/index.js'))).toBeTruthy() + expect(fs.existsSync(path.resolve('project-2/node_modules/external-2/index.js'))).toBeTruthy() await rimraf('node_modules') await rimraf('project-1/node_modules') @@ -88,8 +91,8 @@ test('links are not added to the lockfile when excludeLinksFromLockfile is true' expect(lockfile.importers['project-1'].dependencies?.['external-1']).toBeUndefined() expect(lockfile.importers['project-2'].dependencies?.['external-2']).toBeUndefined() - expect(fs.existsSync(path.resolve('project-1/node_modules/external-1'))).toBeTruthy() - expect(fs.existsSync(path.resolve('project-2/node_modules/external-2'))).toBeTruthy() + expect(fs.existsSync(path.resolve('project-1/node_modules/external-1/index.js'))).toBeTruthy() + expect(fs.existsSync(path.resolve('project-2/node_modules/external-2/index.js'))).toBeTruthy() await rimraf('node_modules') await rimraf('project-1/node_modules') @@ -99,8 +102,8 @@ test('links are not added to the lockfile when excludeLinksFromLockfile is true' expect(lockfile.importers['project-1'].dependencies?.['external-1']).toBeUndefined() expect(lockfile.importers['project-2'].dependencies?.['external-2']).toBeUndefined() - expect(fs.existsSync(path.resolve('project-1/node_modules/external-1'))).toBeTruthy() - expect(fs.existsSync(path.resolve('project-2/node_modules/external-2'))).toBeTruthy() + expect(fs.existsSync(path.resolve('project-1/node_modules/external-1/index.js'))).toBeTruthy() + expect(fs.existsSync(path.resolve('project-2/node_modules/external-2/index.js'))).toBeTruthy() delete allProjects[1].manifest.dependencies!['external-2'] allProjects[1].manifest.dependencies!['external-3'] = `link:${path.relative(project2Dir, externalPkg3)}` @@ -109,9 +112,9 @@ test('links are not added to the lockfile when excludeLinksFromLockfile is true' expect(lockfile.importers['project-2'].dependencies?.['external-2']).toBeUndefined() expect(lockfile.importers['project-2'].dependencies?.['external-3']).toBeUndefined() - expect(fs.existsSync(path.resolve('project-1/node_modules/external-1'))).toBeTruthy() + expect(fs.existsSync(path.resolve('project-1/node_modules/external-1/index.js'))).toBeTruthy() // expect(fs.existsSync(path.resolve('project-2/node_modules/external-2'))).toBeFalsy() // Should we remove external links that are not in deps anymore? - expect(fs.existsSync(path.resolve('project-2/node_modules/external-3'))).toBeTruthy() + expect(fs.existsSync(path.resolve('project-2/node_modules/external-3/index.js'))).toBeTruthy() }) test('local file using absolute path is correctly installed on repeat install', async () => { diff --git a/pkg-manager/headless/src/index.ts b/pkg-manager/headless/src/index.ts index c8fc2f52da..7a2fe832a6 100644 --- a/pkg-manager/headless/src/index.ts +++ b/pkg-manager/headless/src/index.ts @@ -278,7 +278,7 @@ export async function headlessInstall (opts: HeadlessOptions): Promise spec.startsWith('link:')) .reduce((acc, [depName, spec]) => { - acc[depName] = `link:${path.relative(opts.lockfileDir, spec.substring(5))}` + const linkPath = spec.substring(5) + acc[depName] = path.isAbsolute(linkPath) ? `link:${path.relative(rootDir, spec.substring(5))}` : spec return acc }, {} as Record), }