mirror of
https://github.com/pnpm/pnpm.git
synced 2026-04-28 02:53:15 -04:00
fix: pnpm add incorrectly modifies a catalog entry in pnpm-workspace.yaml to its exact version (#10370)
* refactor: factor out a `getRealNameAndSpec` function * test: `pnpm add` does not modify existing catalog entries * fix: resolve preferred version without mutating bare specifier close #9759
This commit is contained in:
6
.changeset/thin-crabs-smoke.md
Normal file
6
.changeset/thin-crabs-smoke.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@pnpm/resolve-dependencies": patch
|
||||
"pnpm": patch
|
||||
---
|
||||
|
||||
Fixed a bug ([#9759](https://github.com/pnpm/pnpm/issues/9759)) where `pnpm add` would incorrectly modify a catalog entry in `pnpm-workspace.yaml` to its exact version.
|
||||
@@ -1193,6 +1193,58 @@ describe('add', () => {
|
||||
})
|
||||
})
|
||||
|
||||
// Regression test for https://github.com/pnpm/pnpm/issues/9759
|
||||
test('adding new usage of default catalog does not mutate catalog entries', async () => {
|
||||
const { options, projects, readLockfile } = preparePackagesAndReturnObjects([
|
||||
{
|
||||
name: 'project1',
|
||||
dependencies: {
|
||||
'@pnpm.e2e/foo': 'catalog:',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'project2',
|
||||
},
|
||||
])
|
||||
|
||||
const catalogs = {
|
||||
default: { '@pnpm.e2e/foo': '^100.0.0' },
|
||||
}
|
||||
|
||||
await mutateModules(installProjects(projects), {
|
||||
...options,
|
||||
lockfileOnly: true,
|
||||
catalogs,
|
||||
})
|
||||
|
||||
await addDependenciesToPackage(
|
||||
projects['project2' as ProjectId],
|
||||
['@pnpm.e2e/foo'],
|
||||
{
|
||||
...options,
|
||||
dir: path.join(options.lockfileDir, 'project2'),
|
||||
lockfileOnly: true,
|
||||
allowNew: true,
|
||||
catalogs,
|
||||
})
|
||||
|
||||
const lockfile = readLockfile()
|
||||
|
||||
// This is the specific condition we're regression testing for. The
|
||||
// specifier used in the original catalog entry should not be modified.
|
||||
expect(lockfile.catalogs.default['@pnpm.e2e/foo'].specifier).toEqual(catalogs.default['@pnpm.e2e/foo'])
|
||||
|
||||
// Sanity check that the rest of the lockfile has expected contents.
|
||||
expect(readLockfile()).toMatchObject({
|
||||
catalogs: { default: { '@pnpm.e2e/foo': { specifier: '^100.0.0', version: '100.0.0' } } },
|
||||
importers: {
|
||||
project1: { dependencies: { '@pnpm.e2e/foo': { specifier: 'catalog:', version: '100.0.0' } } },
|
||||
project2: { dependencies: { '@pnpm.e2e/foo': { specifier: 'catalog:', version: '100.0.0' } } },
|
||||
},
|
||||
packages: { '@pnpm.e2e/foo@100.0.0': expect.any(Object) },
|
||||
})
|
||||
})
|
||||
|
||||
test('adding specific version equal to catalog version will use catalog if present', async () => {
|
||||
const { options, projects, readLockfile } = preparePackagesAndReturnObjects([{
|
||||
name: 'project1',
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { type PreferredVersions } from '@pnpm/resolver-base'
|
||||
import { type WantedDependency } from './getWantedDependencies.js'
|
||||
import { unwrapPackageName } from './unwrapPackageName.js'
|
||||
|
||||
/**
|
||||
* Create a PreferredVersions object with a specific exact version.
|
||||
*/
|
||||
export function getExactSinglePreferredVersions (wantedDependency: WantedDependency, version: string): PreferredVersions {
|
||||
const { pkgName } = unwrapPackageName(wantedDependency.alias, wantedDependency.bareSpecifier)
|
||||
return {
|
||||
[pkgName]: { [version]: 'version' },
|
||||
}
|
||||
}
|
||||
@@ -53,6 +53,7 @@ import pDefer from 'p-defer'
|
||||
import pShare from 'promise-share'
|
||||
import { pickBy, omit, zipWith } from 'ramda'
|
||||
import semver from 'semver'
|
||||
import { getExactSinglePreferredVersions } from './getExactSinglePreferredVersions.js'
|
||||
import { getNonDevWantedDependencies, type WantedDependency } from './getNonDevWantedDependencies.js'
|
||||
import { safeIntersect } from './mergePeers.js'
|
||||
import { type NodeId, nextNodeId } from './nextNodeId.js'
|
||||
@@ -1306,14 +1307,19 @@ async function resolveDependency (
|
||||
optional: true,
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize the `preferredVersion` (singular) and `preferredVersions`
|
||||
// (plural) options. If the singular option is passed through, it'll be used
|
||||
// instead of the plural option.
|
||||
const preferredVersions = !options.updateRequested && options.preferredVersion != null
|
||||
? getExactSinglePreferredVersions(wantedDependency, options.preferredVersion)
|
||||
: options.preferredVersions
|
||||
|
||||
try {
|
||||
const calcSpecifier = options.currentDepth === 0
|
||||
if (!options.update && currentPkg.version && currentPkg.pkgId?.endsWith(`@${currentPkg.version}`) && !calcSpecifier) {
|
||||
wantedDependency.bareSpecifier = replaceVersionInBareSpecifier(wantedDependency.bareSpecifier, currentPkg.version)
|
||||
}
|
||||
if (!options.updateRequested && options.preferredVersion != null) {
|
||||
wantedDependency.bareSpecifier = replaceVersionInBareSpecifier(wantedDependency.bareSpecifier, options.preferredVersion)
|
||||
}
|
||||
pkgResponse = await ctx.storeController.requestPackage(wantedDependency, {
|
||||
allowBuild: ctx.allowBuild,
|
||||
alwaysTryWorkspacePackages: ctx.linkWorkspacePackagesDepth >= options.currentDepth,
|
||||
@@ -1333,7 +1339,7 @@ async function resolveDependency (
|
||||
pickLowestVersion: options.pickLowestVersion,
|
||||
downloadPriority: -options.currentDepth,
|
||||
lockfileDir: ctx.lockfileDir,
|
||||
preferredVersions: options.preferredVersions,
|
||||
preferredVersions,
|
||||
preferWorkspacePackages: ctx.preferWorkspacePackages,
|
||||
projectDir: (
|
||||
options.currentDepth > 0 &&
|
||||
|
||||
Reference in New Issue
Block a user