diff --git a/.changeset/good-radios-fail.md b/.changeset/good-radios-fail.md new file mode 100644 index 00000000000..c61c84d2ad5 --- /dev/null +++ b/.changeset/good-radios-fail.md @@ -0,0 +1,5 @@ +--- +"@pnpm/npm-resolver": patch +--- + +fix: correctly install dependencies when resolution-mode is lowest-direct or time-based diff --git a/pkg-manager/core/test/install/resolutionMode.ts b/pkg-manager/core/test/install/resolutionMode.ts index f32731010c1..90837991c5d 100644 --- a/pkg-manager/core/test/install/resolutionMode.ts +++ b/pkg-manager/core/test/install/resolutionMode.ts @@ -92,3 +92,40 @@ test('the lowest version of a direct dependency is installed when resolution mod '@pnpm.e2e/pkg-with-1-dep': '^100.1.0', }) }) + +test('the lowest version of a direct dependency is installed when resolution mode is lowest-direct and already installed other version ', async () => { + const project = prepareEmpty() + + await addDependenciesToPackage( + {}, + ['@pnpm.e2e/bravo'], + testDefaults({ resolutionMode: 'time-based' }) + ) + + const lockfile = project.readLockfile() + expect(lockfile.packages['@pnpm.e2e/bravo-dep@1.0.1']).toBeTruthy() + await install( + { + dependencies: { + '@pnpm.e2e/bravo-dep': '^1.0.0', + }, + }, + testDefaults({ resolutionMode: 'lowest-direct' }) + ) + + { + const lockfile = project.readLockfile() + expect(lockfile.packages['@pnpm.e2e/bravo-dep@1.0.0']).toBeTruthy() + } + + await install({ + dependencies: { + '@pnpm.e2e/bravo-dep': '^1.1.0', + }, + }, testDefaults({ resolutionMode: 'lowest-direct' })) + + { + const lockfile = project.readLockfile() + expect(lockfile.packages['@pnpm.e2e/bravo-dep@1.1.0']).toBeTruthy() + } +}) diff --git a/resolving/npm-resolver/src/pickPackageFromMeta.ts b/resolving/npm-resolver/src/pickPackageFromMeta.ts index 6e3497e19cf..87981188de5 100644 --- a/resolving/npm-resolver/src/pickPackageFromMeta.ts +++ b/resolving/npm-resolver/src/pickPackageFromMeta.ts @@ -91,7 +91,16 @@ export function pickLowestVersionByVersionRange ( preferredVerSels?: VersionSelectors ) { if (preferredVerSels != null && Object.keys(preferredVerSels).length > 0) { - const prioritizedPreferredVersions = prioritizePreferredVersions(meta, versionRange, preferredVerSels) + // ignore the subdependencies + // {'^1.0.0': { selectorType: 'range', weight: 1000 }, '1.0.1': 'version'} would be [1.0.1, 1.0.0] if sorted by weight + let lowestPreferredVerSels: VersionSelectors | null = null + Object.entries(preferredVerSels).forEach(([key, value]) => { + if (value !== null && typeof value === 'object') { + if (!lowestPreferredVerSels) lowestPreferredVerSels = {} + lowestPreferredVerSels[key] = value + } + }) + const prioritizedPreferredVersions = prioritizePreferredVersions(meta, versionRange, lowestPreferredVerSels ?? preferredVerSels) for (const preferredVersions of prioritizedPreferredVersions) { const preferredVersion = semver.minSatisfying(preferredVersions, versionRange, true) if (preferredVersion) {