fix: prevent catalog: from leaking into pnpm-workspace.yaml (#10476)

close #10176
This commit is contained in:
Alessio Attilio
2026-01-27 15:52:31 +01:00
committed by GitHub
parent f3cd9f7c05
commit 94571fb2fe
3 changed files with 69 additions and 1 deletions

View File

@@ -0,0 +1,6 @@
---
"@pnpm/resolve-dependencies": patch
"pnpm": patch
---
Fixed a bug where `catalogMode: strict` would write the literal string `"catalog:"` to `pnpm-workspace.yaml` instead of the resolved version specifier when re-adding an existing catalog dependency [#10176](https://github.com/pnpm/pnpm/issues/10176).

View File

@@ -1340,6 +1340,65 @@ describe('add', () => {
})
})
// Regression test for https://github.com/pnpm/pnpm/issues/10176
// When re-adding a dependency that already exists in the catalog with catalogMode: strict,
// the catalog entry should preserve the original version specifier, not become 'catalog:'
test('re-adding existing catalog dependency with catalogMode: strict preserves catalog specifier', async () => {
const { options, projects, readLockfile } = preparePackagesAndReturnObjects([{
name: 'project1',
dependencies: {
'is-positive': 'catalog:',
},
}])
// First, install the existing dependency with the catalog
const mutateOpts = {
...options,
lockfileOnly: true,
catalogs: {
default: { 'is-positive': '^1.0.0' },
},
catalogMode: 'strict' as const,
}
await mutateModules(installProjects(projects), mutateOpts)
// Verify initial state
expect(readLockfile().catalogs?.default?.['is-positive']).toEqual({
specifier: '^1.0.0',
version: '1.0.0',
})
// Now re-add the same dependency (simulating 'pnpm add is-positive' from a subpackage)
const { updatedManifest, updatedCatalogs } = await addDependenciesToPackage(
projects['project1' as ProjectId],
['is-positive'],
{
...mutateOpts,
dir: path.join(options.lockfileDir, 'project1'),
allowNew: true,
})
// The manifest should still use catalog:
expect(updatedManifest).toEqual({
name: 'project1',
dependencies: {
'is-positive': 'catalog:',
},
})
// The catalog should preserve the original specifier, NOT become 'catalog:'
// This is the bug fix - previously it would incorrectly write 'catalog:' to the catalog
if (updatedCatalogs?.default?.['is-positive']) {
expect(updatedCatalogs.default['is-positive']).not.toBe('catalog:')
expect(updatedCatalogs.default['is-positive']).toMatch(/^\^?\d/)
}
// The lockfile should have the correct catalog specifier
const lockfile = readLockfile()
expect(lockfile.catalogs?.default?.['is-positive']?.specifier).not.toBe('catalog:')
})
test('adding with catalogMode: prefer will add to or use from catalog', async () => {
const { options, projects, readLockfile } = preparePackagesAndReturnObjects([{
name: 'project1',

View File

@@ -289,9 +289,12 @@ export async function resolveDependencies (
if (!updateSpec) continue
const dep = resolvedImporter.directDependencies[i]
if (dep.catalogLookup == null) continue
// If normalizedBareSpecifier isn't defined, this catalog entry was resolved from cache.
// Avoid updating the updatedCatalogs map since it is likely unchanged.
if (dep.normalizedBareSpecifier == null) continue
updatedCatalogs ??= {}
updatedCatalogs[dep.catalogLookup.catalogName] ??= {}
updatedCatalogs[dep.catalogLookup.catalogName][dep.alias] = dep.normalizedBareSpecifier ?? dep.catalogLookup.userSpecifiedBareSpecifier
updatedCatalogs[dep.catalogLookup.catalogName][dep.alias] = dep.normalizedBareSpecifier
}
}