diff --git a/.changeset/lucky-chairs-begin.md b/.changeset/lucky-chairs-begin.md new file mode 100644 index 0000000000..b2c93cc29f --- /dev/null +++ b/.changeset/lucky-chairs-begin.md @@ -0,0 +1,6 @@ +--- +"@pnpm/npm-resolver": patch +"pnpm": patch +--- + +Deduplicate direct dependencies, when `resolution-mode` is set to `lowest-direct` [#6042](https://github.com/pnpm/pnpm/issues/6042). diff --git a/pkg-manager/core/test/install/dedupeInWorkspace.ts b/pkg-manager/core/test/install/dedupeInWorkspace.ts index 0677cc1400..dfa41799fc 100644 --- a/pkg-manager/core/test/install/dedupeInWorkspace.ts +++ b/pkg-manager/core/test/install/dedupeInWorkspace.ts @@ -5,7 +5,7 @@ import { mutateModules, type MutatedProject } from '@pnpm/core' import { addDistTag } from '@pnpm/registry-mock' import { testDefaults } from '../utils' -test('pick common range for a dependency used in two workspace projects', async () => { +test('pick common range for a dependency used in two workspace projects when resolution mode is highest', async () => { await addDistTag({ package: '@pnpm.e2e/dep-of-pkg-with-1-dep', version: '100.1.0', distTag: 'latest' }) preparePackages([ { @@ -54,10 +54,67 @@ test('pick common range for a dependency used in two workspace projects', async rootDir: path.resolve('project-2'), }, ] - await mutateModules(importers, await testDefaults({ allProjects, lockfileOnly: true })) + await mutateModules(importers, await testDefaults({ allProjects, lockfileOnly: true, resolutionMode: 'highest' })) const project = assertProject(process.cwd()) const lockfile = await project.readLockfile() expect(lockfile.packages).toHaveProperty(['/@pnpm.e2e/dep-of-pkg-with-1-dep@100.0.0']) expect(lockfile.packages).not.toHaveProperty(['/@pnpm.e2e/dep-of-pkg-with-1-dep@100.1.0']) }) + +test('pick common range for a dependency used in two workspace projects when resolution mode is lowest-direct', async () => { + await addDistTag({ package: '@pnpm.e2e/dep-of-pkg-with-1-dep', version: '100.1.0', distTag: 'latest' }) + preparePackages([ + { + location: 'project-1', + package: { name: 'project-1' }, + }, + { + location: 'project-2', + package: { name: 'project-2' }, + }, + ]) + + const importers: MutatedProject[] = [ + { + mutation: 'install', + rootDir: path.resolve('project-1'), + }, + { + mutation: 'install', + rootDir: path.resolve('project-2'), + }, + ] + const allProjects = [ + { + buildIndex: 0, + manifest: { + name: 'project-1', + version: '1.0.0', + + dependencies: { + '@pnpm.e2e/dep-of-pkg-with-1-dep': '100.1.0', + }, + }, + rootDir: path.resolve('project-1'), + }, + { + buildIndex: 0, + manifest: { + name: 'project-2', + version: '1.0.0', + + dependencies: { + '@pnpm.e2e/dep-of-pkg-with-1-dep': '^100.0.0', + }, + }, + rootDir: path.resolve('project-2'), + }, + ] + await mutateModules(importers, await testDefaults({ allProjects, lockfileOnly: true, resolutionMode: 'lowest-direct' })) + + const project = assertProject(process.cwd()) + const lockfile = await project.readLockfile() + expect(lockfile.packages).toHaveProperty(['/@pnpm.e2e/dep-of-pkg-with-1-dep@100.1.0']) + expect(lockfile.packages).not.toHaveProperty(['/@pnpm.e2e/dep-of-pkg-with-1-dep@100.0.0']) +}) diff --git a/resolving/npm-resolver/src/pickPackageFromMeta.ts b/resolving/npm-resolver/src/pickPackageFromMeta.ts index b6ab1d5d44..6d90e35350 100644 --- a/resolving/npm-resolver/src/pickPackageFromMeta.ts +++ b/resolving/npm-resolver/src/pickPackageFromMeta.ts @@ -60,8 +60,18 @@ export function pickPackageFromMeta ( export function pickLowestVersionByVersionRange ( meta: PackageMeta, - versionRange: string + versionRange: string, + preferredVerSels?: VersionSelectors ) { + if (preferredVerSels != null && Object.keys(preferredVerSels).length > 0) { + const prioritizedPreferredVersions = prioritizePreferredVersions(meta, versionRange, preferredVerSels) + for (const preferredVersions of prioritizedPreferredVersions) { + const preferredVersion = semver.minSatisfying(preferredVersions, versionRange, true) + if (preferredVersion) { + return preferredVersion + } + } + } if (versionRange === '*') { return Object.keys(meta.versions).sort(semver.compare)[0] }