diff --git a/.changeset/little-pears-push.md b/.changeset/little-pears-push.md new file mode 100644 index 0000000000..044a4183b2 --- /dev/null +++ b/.changeset/little-pears-push.md @@ -0,0 +1,6 @@ +--- +"@pnpm/core": minor +"pnpm": minor +--- + +`pnpm add` will now check if the added dependency is in the default workspace catalog. If the added dependency is present in the default catalog and the version requirement of the added dependency matches the default catalog's version, then the `pnpm add` will use the `catalog:` protocol. Note that if no version is specified in the `pnpm add` it will match the version described in the default catalog. If the added dependency does not match the default catalog's version it will use the default `pnpm add` behavior [#8640](https://github.com/pnpm/pnpm/issues/8640). diff --git a/pkg-manager/core/src/install/index.ts b/pkg-manager/core/src/install/index.ts index 59f8ae2c96..fc0f8a5a43 100644 --- a/pkg-manager/core/src/install/index.ts +++ b/pkg-manager/core/src/install/index.ts @@ -658,6 +658,7 @@ Note that in CI environments, this setting is enabled by default.`, updateWorkspaceDependencies: project.update, preferredSpecs, overrides: opts.overrides, + defaultCatalog: opts.catalogs?.default, }) projectsToInstall.push({ pruneDirectDependencies: false, diff --git a/pkg-manager/core/src/parseWantedDependencies.ts b/pkg-manager/core/src/parseWantedDependencies.ts index dffe21d135..24027f3026 100644 --- a/pkg-manager/core/src/parseWantedDependencies.ts +++ b/pkg-manager/core/src/parseWantedDependencies.ts @@ -2,6 +2,7 @@ import { parseWantedDependency } from '@pnpm/parse-wanted-dependency' import { type Dependencies } from '@pnpm/types' import { whichVersionIsPinned } from '@pnpm/which-version-is-pinned' import { type PinnedVersion, type WantedDependency } from '@pnpm/resolve-dependencies/lib/getWantedDependencies' +import { type Catalog } from '@pnpm/catalogs.types' export function parseWantedDependencies ( rawWantedDependencies: string[], @@ -16,6 +17,7 @@ export function parseWantedDependencies ( overrides?: Record updateWorkspaceDependencies?: boolean preferredSpecs?: Record + defaultCatalog?: Catalog } ): WantedDependency[] { return rawWantedDependencies @@ -28,6 +30,13 @@ export function parseWantedDependencies ( if (!opts.allowNew && (!alias || !opts.currentPrefs[alias])) { return null } + if (alias && opts.defaultCatalog?.[alias] && ( + (!opts.currentPrefs[alias] && pref === undefined) || + opts.defaultCatalog[alias] === pref || + opts.defaultCatalog[alias] === opts.currentPrefs[alias] + )) { + pref = 'catalog:' + } if (alias && opts.currentPrefs[alias]) { if (!pref) { pref = (opts.currentPrefs[alias].startsWith('workspace:') && opts.updateWorkspaceDependencies === true) diff --git a/pkg-manager/core/test/catalogs.ts b/pkg-manager/core/test/catalogs.ts index d5c51761c8..64b5e3b222 100644 --- a/pkg-manager/core/test/catalogs.ts +++ b/pkg-manager/core/test/catalogs.ts @@ -543,6 +543,95 @@ describe('add', () => { packages: { 'is-positive@1.0.0': expect.objectContaining({}) }, })) }) + + test('adding no specific version will use catalog if present', async () => { + const { options, projects, readLockfile } = preparePackagesAndReturnObjects([{ + name: 'project1', + dependencies: {}, + }]) + + const updatedManifest = await addDependenciesToPackage( + projects['project1' as ProjectId], + ['is-positive'], + { + ...options, + lockfileOnly: true, + allowNew: true, + catalogs: { + default: { 'is-positive': '1.0.0' }, + }, + }) + + expect(updatedManifest).toEqual({ + name: 'project1', + dependencies: { + 'is-positive': 'catalog:', + }, + }) + expect(readLockfile()).toEqual(expect.objectContaining({ + catalogs: { default: { 'is-positive': { specifier: '1.0.0', version: '1.0.0' } } }, + packages: { 'is-positive@1.0.0': expect.objectContaining({}) }, + })) + }) + + test('adding specific version equal to catalog version will use catalog if present', async () => { + const { options, projects, readLockfile } = preparePackagesAndReturnObjects([{ + name: 'project1', + dependencies: {}, + }]) + + const updatedManifest = await addDependenciesToPackage( + projects['project1' as ProjectId], + ['is-positive@1.0.0'], + { + ...options, + lockfileOnly: true, + allowNew: true, + catalogs: { + default: { 'is-positive': '1.0.0' }, + }, + }) + + expect(updatedManifest).toEqual({ + name: 'project1', + dependencies: { + 'is-positive': 'catalog:', + }, + }) + expect(readLockfile()).toEqual(expect.objectContaining({ + catalogs: { default: { 'is-positive': { specifier: '1.0.0', version: '1.0.0' } } }, + packages: { 'is-positive@1.0.0': expect.objectContaining({}) }, + })) + }) + + test('adding different version than the catalog will not use catalog', async () => { + const { options, projects, readLockfile } = preparePackagesAndReturnObjects([{ + name: 'project1', + dependencies: {}, + }]) + + const updatedManifest = await addDependenciesToPackage( + projects['project1' as ProjectId], + ['is-positive@2.0.0'], + { + ...options, + lockfileOnly: true, + allowNew: true, + catalogs: { + default: { 'is-positive': '1.0.0' }, + }, + }) + + expect(updatedManifest).toEqual({ + name: 'project1', + dependencies: { + 'is-positive': '2.0.0', + }, + }) + expect(readLockfile()).toEqual(expect.objectContaining({ + packages: { 'is-positive@2.0.0': expect.objectContaining({}) }, + })) + }) }) // The 'pnpm update' command should eventually support updates of dependencies