diff --git a/.changeset/calm-pugs-breathe.md b/.changeset/calm-pugs-breathe.md new file mode 100644 index 00000000000..79cf82c6bea --- /dev/null +++ b/.changeset/calm-pugs-breathe.md @@ -0,0 +1,19 @@ +--- +"@pnpm/core": minor +"@pnpm/types": minor +"pnpm": minor +--- + +A new setting added: `pnpm.peerDependencyRules.allowAny`. `allowAny` is an array of package name patterns, any peer dependency matching the pattern will be resolved from any version, regardless of the range specified in `peerDependencies`. For instance: + +``` +{ + "pnpm": { + "peerDependencyRules": { + "allowAny": ["@babel/*", "eslint"] + } + } +} +``` + +The above setting will mute any warnings about peer dependency version mismatches related to `@babel/` packages or `eslint`. diff --git a/.changeset/real-news-mate.md b/.changeset/real-news-mate.md new file mode 100644 index 00000000000..e742c0763c3 --- /dev/null +++ b/.changeset/real-news-mate.md @@ -0,0 +1,16 @@ +--- +"@pnpm/core": minor +"pnpm": minor +--- + +The `pnpm.peerDependencyRules.ignoreMissing` setting may accept package name patterns. So you may ignore any missing `@babel/*` peer dependencies, for instance: + +```json +{ + "pnpm": { + "peerDependencyRules": { + "ignoreMissing": ["@babel/*"] + } + } +} +``` diff --git a/packages/core/package.json b/packages/core/package.json index ab14bb78a4a..f2bddf9cb79 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -32,6 +32,7 @@ "@pnpm/lockfile-utils": "workspace:4.0.4", "@pnpm/lockfile-walker": "workspace:5.0.4", "@pnpm/manifest-utils": "workspace:3.0.3", + "@pnpm/matcher": "workspace:3.0.0", "@pnpm/modules-cleaner": "workspace:12.0.7", "@pnpm/modules-yaml": "workspace:10.0.2", "@pnpm/normalize-registries": "workspace:3.0.2", diff --git a/packages/core/src/install/createPeerDependencyPatcher.ts b/packages/core/src/install/createPeerDependencyPatcher.ts index e14aa30ba14..0661bbd1c7d 100644 --- a/packages/core/src/install/createPeerDependencyPatcher.ts +++ b/packages/core/src/install/createPeerDependencyPatcher.ts @@ -1,38 +1,48 @@ import { PeerDependencyRules, ReadPackageHook } from '@pnpm/types' +import matcher from '@pnpm/matcher' import isEmpty from 'ramda/src/isEmpty' export default function ( peerDependencyRules: PeerDependencyRules ): ReadPackageHook { - const ignoreMissing = new Set(peerDependencyRules.ignoreMissing ?? []) + const ignoreMissingPatterns = [...new Set(peerDependencyRules.ignoreMissing ?? [])] + const ignoreMissingMatcher = matcher(ignoreMissingPatterns) + const allowAnyPatterns = [...new Set(peerDependencyRules.allowAny ?? [])] + const allowAnyMatcher = matcher(allowAnyPatterns) return ((pkg) => { if (isEmpty(pkg.peerDependencies)) return pkg for (const [peerName, peerVersion] of Object.entries(pkg.peerDependencies ?? {})) { - if (ignoreMissing.has(peerName) && !pkg.peerDependenciesMeta?.[peerName]?.optional) { + if ( + ignoreMissingMatcher(peerName) && + !pkg.peerDependenciesMeta?.[peerName]?.optional + ) { pkg.peerDependenciesMeta = pkg.peerDependenciesMeta ?? {} pkg.peerDependenciesMeta[peerName] = { optional: true, } } + if (allowAnyMatcher(peerName)) { + pkg.peerDependencies![peerName] = '*' + continue + } if ( - peerDependencyRules.allowedVersions?.[peerName] && - peerVersion !== '*' - ) { - if (peerDependencyRules.allowedVersions[peerName] === '*') { - pkg.peerDependencies![peerName] = '*' - } else { - const allowedVersions = parseVersions(peerDependencyRules.allowedVersions[peerName]) - const currentVersions = parseVersions(pkg.peerDependencies![peerName]) - - allowedVersions.forEach(allowedVersion => { - if (!currentVersions.includes(allowedVersion)) { - currentVersions.push(allowedVersion) - } - }) + !peerDependencyRules.allowedVersions?.[peerName] || + peerVersion === '*' + ) continue + if (peerDependencyRules.allowedVersions[peerName] === '*') { + pkg.peerDependencies![peerName] = '*' + continue + } + const allowedVersions = parseVersions(peerDependencyRules.allowedVersions[peerName]) + const currentVersions = parseVersions(pkg.peerDependencies![peerName]) - pkg.peerDependencies![peerName] = currentVersions.join(' || ') + allowedVersions.forEach(allowedVersion => { + if (!currentVersions.includes(allowedVersion)) { + currentVersions.push(allowedVersion) } - } + }) + + pkg.peerDependencies![peerName] = currentVersions.join(' || ') } return pkg }) as ReadPackageHook diff --git a/packages/core/src/install/index.ts b/packages/core/src/install/index.ts index dad5eac8595..36e096269d5 100644 --- a/packages/core/src/install/index.ts +++ b/packages/core/src/install/index.ts @@ -527,7 +527,11 @@ export function createReadPackageHook ( } if ( peerDependencyRules != null && - (!isEmpty(peerDependencyRules.ignoreMissing) || !isEmpty(peerDependencyRules.allowedVersions)) + ( + !isEmpty(peerDependencyRules.ignoreMissing) || + !isEmpty(peerDependencyRules.allowedVersions) || + !isEmpty(peerDependencyRules.allowAny) + ) ) { hooks.push(createPeerDependencyPatcher(peerDependencyRules)) } diff --git a/packages/core/test/install/createPeerDependencyPatcher.test.ts b/packages/core/test/install/createPeerDependencyPatcher.test.ts index 00845e81f1c..135f2118271 100644 --- a/packages/core/test/install/createPeerDependencyPatcher.test.ts +++ b/packages/core/test/install/createPeerDependencyPatcher.test.ts @@ -17,6 +17,23 @@ test('createPeerDependencyPatcher() ignores missing', () => { }) }) +test('createPeerDependencyPatcher() pattern matches to ignore missing', () => { + const patcher = createPeerDependencyPatcher({ + ignoreMissing: ['f*r'], + }) + const patchedPkg = patcher({ + peerDependencies: { + foobar: '*', + bar: '*', + }, + }) + expect(patchedPkg['peerDependenciesMeta']).toStrictEqual({ + foobar: { + optional: true, + }, + }) +}) + test('createPeerDependencyPatcher() extends peer ranges', () => { const patcher = createPeerDependencyPatcher({ allowedVersions: { @@ -41,6 +58,26 @@ test('createPeerDependencyPatcher() extends peer ranges', () => { }) }) +test('createPeerDependencyPatcher() ignores peer versions from allowAny', () => { + const patcher = createPeerDependencyPatcher({ + allowAny: ['foo', 'bar'], + }) + const patchedPkg = patcher({ + peerDependencies: { + foo: '2', + bar: '2', + qar: '2', + baz: '2', + }, + }) + expect(patchedPkg['peerDependencies']).toStrictEqual({ + foo: '*', + bar: '*', + qar: '2', + baz: '2', + }) +}) + test('createPeerDependencyPatcher() does not create duplicate extended ranges', async () => { const patcher = createPeerDependencyPatcher({ allowedVersions: { diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json index 0ad13abbdaa..0bafd2a8e3c 100644 --- a/packages/core/tsconfig.json +++ b/packages/core/tsconfig.json @@ -78,6 +78,9 @@ { "path": "../manifest-utils" }, + { + "path": "../matcher" + }, { "path": "../modules-cleaner" }, diff --git a/packages/types/src/package.ts b/packages/types/src/package.ts index 6dfdd64107b..a51e87c9f8e 100644 --- a/packages/types/src/package.ts +++ b/packages/types/src/package.ts @@ -112,6 +112,7 @@ export type PackageExtension = Pick } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cfa1aac1f5a..5c772ca68a4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -414,6 +414,7 @@ importers: '@pnpm/lockfile-walker': workspace:5.0.4 '@pnpm/logger': ^4.0.0 '@pnpm/manifest-utils': workspace:3.0.3 + '@pnpm/matcher': workspace:3.0.0 '@pnpm/modules-cleaner': workspace:12.0.7 '@pnpm/modules-yaml': workspace:10.0.2 '@pnpm/normalize-registries': workspace:3.0.2 @@ -490,6 +491,7 @@ importers: '@pnpm/lockfile-utils': link:../lockfile-utils '@pnpm/lockfile-walker': link:../lockfile-walker '@pnpm/manifest-utils': link:../manifest-utils + '@pnpm/matcher': link:../matcher '@pnpm/modules-cleaner': link:../modules-cleaner '@pnpm/modules-yaml': link:../modules-yaml '@pnpm/normalize-registries': link:../normalize-registries