mirror of
https://github.com/pnpm/pnpm.git
synced 2026-03-27 11:31:45 -04:00
fix: reuse existing cataloged resolutions for consistency (#8259)
* test: add a test to ensure catalog resolutions are consistent * fix: reuse existing cataloged resolutions for consistency
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { createPeersDirSuffix } from '@pnpm/dependency-path'
|
||||
import { type ProjectId, type ProjectManifest } from '@pnpm/types'
|
||||
import { prepareEmpty } from '@pnpm/prepare'
|
||||
import { type MutatedProject, mutateModules, type ProjectOptions } from '@pnpm/core'
|
||||
import { type MutatedProject, mutateModules, type ProjectOptions, type MutateModulesOptions } from '@pnpm/core'
|
||||
import path from 'path'
|
||||
import { testDefaults } from './utils'
|
||||
|
||||
@@ -315,6 +315,82 @@ test('external dependency using catalog protocol errors', async () => {
|
||||
).rejects.toThrow("@pnpm.e2e/hello-world-js-bin@catalog:foo isn't supported by any available resolver.")
|
||||
})
|
||||
|
||||
test('catalog resolutions should be consistent', async () => {
|
||||
const { options, projects, readLockfile } = preparePackagesAndReturnObjects([
|
||||
{
|
||||
name: 'project1',
|
||||
dependencies: {
|
||||
'is-positive': 'catalog:',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'project2',
|
||||
dependencies: {},
|
||||
},
|
||||
{
|
||||
name: 'project3',
|
||||
dependencies: {},
|
||||
},
|
||||
])
|
||||
|
||||
const catalogs = {
|
||||
default: {
|
||||
'is-positive': '=3.0.0',
|
||||
},
|
||||
}
|
||||
|
||||
const mutateOpts: MutateModulesOptions = {
|
||||
...options,
|
||||
lockfileOnly: true,
|
||||
resolutionMode: 'highest',
|
||||
catalogs,
|
||||
}
|
||||
|
||||
await mutateModules(installProjects(projects), mutateOpts)
|
||||
|
||||
// Change the is-positive catalog entry from =3.0.0 to ^3.0.0 to lock ^3.0.0
|
||||
// to the existing 3.0.0 version in the lockfile.
|
||||
catalogs.default['is-positive'] = '^3.0.0'
|
||||
await mutateModules(installProjects(projects), mutateOpts)
|
||||
expect(readLockfile().catalogs).toEqual({
|
||||
default: {
|
||||
'is-positive': { specifier: '^3.0.0', version: '3.0.0' },
|
||||
},
|
||||
})
|
||||
|
||||
// Add a different version of is-positive to the lockfile.
|
||||
projects['project2' as ProjectId].dependencies = {
|
||||
'is-positive': '3.1.0',
|
||||
}
|
||||
await mutateModules(installProjects(projects), mutateOpts)
|
||||
|
||||
// At this point, both 3.0.0 and 3.1.0 should be in the lockfile, but the
|
||||
// catalog entry still resolves to 3.0.0.
|
||||
expect(readLockfile()).toEqual(expect.objectContaining({
|
||||
catalogs: { default: { 'is-positive': { specifier: '^3.0.0', version: '3.0.0' } } },
|
||||
packages: expect.objectContaining({
|
||||
'is-positive@3.0.0': expect.objectContaining({}),
|
||||
'is-positive@3.1.0': expect.objectContaining({}),
|
||||
}),
|
||||
}))
|
||||
|
||||
// Adding a new catalog dependency. It should resolve to 3.0.0 instead of 3.1.0, despite resolution-mode=highest.
|
||||
projects['project3' as ProjectId].dependencies = {
|
||||
'is-positive': 'catalog:',
|
||||
}
|
||||
await mutateModules(installProjects(projects), mutateOpts)
|
||||
|
||||
// Expect all projects using the catalog specifier (e.g. project1 and project3) to resolve to the same version.
|
||||
expect(readLockfile()).toEqual(expect.objectContaining({
|
||||
catalogs: { default: { 'is-positive': { specifier: '^3.0.0', version: '3.0.0' } } },
|
||||
importers: expect.objectContaining({
|
||||
project1: expect.objectContaining({ dependencies: { 'is-positive': { specifier: 'catalog:', version: '3.0.0' } } }),
|
||||
project2: expect.objectContaining({ dependencies: { 'is-positive': { specifier: '3.1.0', version: '3.1.0' } } }),
|
||||
project3: expect.objectContaining({ dependencies: { 'is-positive': { specifier: 'catalog:', version: '3.0.0' } } }),
|
||||
}),
|
||||
}))
|
||||
})
|
||||
|
||||
// If a catalog specifier was used in one or more package.json files and all
|
||||
// usages were removed later, we should remove the catalog snapshot from
|
||||
// pnpm-lock.yaml. This should happen even if the dependency is still defined in
|
||||
|
||||
@@ -532,7 +532,18 @@ async function resolveDependenciesOfImporterDependency (
|
||||
})
|
||||
|
||||
if (catalogLookup != null) {
|
||||
extendedWantedDep.wantedDependency.pref = catalogLookup.specifier
|
||||
// The lockfile from a previous installation may have already resolved this
|
||||
// cataloged dependency. Reuse the exact version in the lockfile catalog
|
||||
// snapshot to ensure all projects using the same cataloged dependency get
|
||||
// the same version.
|
||||
const existingCatalogResolution = ctx.wantedLockfile.catalogs
|
||||
?.[catalogLookup.catalogName]
|
||||
?.[extendedWantedDep.wantedDependency.alias]
|
||||
const replacementPref = existingCatalogResolution?.specifier === catalogLookup.specifier
|
||||
? existingCatalogResolution.version
|
||||
: catalogLookup.specifier
|
||||
|
||||
extendedWantedDep.wantedDependency.pref = replacementPref
|
||||
}
|
||||
|
||||
const result = await resolveDependenciesOfDependency(
|
||||
|
||||
Reference in New Issue
Block a user