diff --git a/.changeset/hungry-dolls-rush.md b/.changeset/hungry-dolls-rush.md new file mode 100644 index 0000000000..9811a455a8 --- /dev/null +++ b/.changeset/hungry-dolls-rush.md @@ -0,0 +1,5 @@ +--- +"@pnpm/headless": patch +--- + +Don't create broken symlinks to skipped optional dependencies, when hoisting. diff --git a/packages/headless/src/index.ts b/packages/headless/src/index.ts index 67df9a8b14..39d79974de 100644 --- a/packages/headless/src/index.ts +++ b/packages/headless/src/index.ts @@ -223,6 +223,11 @@ export default async (opts: HeadlessOptions) => { pnpmVersion: opts.currentEngine.pnpmVersion, } as LockfileToDepGraphOptions ) + if (filteredLockfile.packages) { + for (const skippedDepPath of Array.from(skipped)) { + delete filteredLockfile.packages[skippedDepPath] + } + } if (opts.enablePnp) { const importerNames = R.fromPairs( opts.projects.map(({ manifest, id }) => [id, manifest.name ?? id]) diff --git a/packages/supi/test/install/hoist.ts b/packages/supi/test/install/hoist.ts index bec5ff6cca..649e0b7cfe 100644 --- a/packages/supi/test/install/hoist.ts +++ b/packages/supi/test/install/hoist.ts @@ -499,3 +499,27 @@ test('should recreate node_modules with hoisting', async () => { expect(Object.keys(modulesManifest?.hoistedDependencies ?? {}).length > 0).toBeTruthy() } }) + +test('hoisting should not create a broken symlink to a skipped optional dependency', async () => { + const project = prepareEmpty() + console.log(process.cwd()) + + await install({ + optionalDependencies: { + 'not-compatible-with-any-os': '*', + }, + }, await testDefaults({ publicHoistPattern: '*' })) + + await project.hasNot('dep-of-optional-pkg') + + // Verifying the same with headless installation + await rimraf('node_modules') + + await install({ + optionalDependencies: { + 'not-compatible-with-any-os': '*', + }, + }, await testDefaults({ publicHoistPattern: '*' })) + + await project.hasNot('dep-of-optional-pkg') +}) \ No newline at end of file