mirror of
https://github.com/pnpm/pnpm.git
synced 2025-12-23 23:29:17 -05:00
feat: enhance peer dependency rules (#4876)
Co-authored-by: Zoltan Kochan <z@kochan.io> close #4835
This commit is contained in:
19
.changeset/calm-pugs-breathe.md
Normal file
19
.changeset/calm-pugs-breathe.md
Normal file
@@ -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`.
|
||||
16
.changeset/real-news-mate.md
Normal file
16
.changeset/real-news-mate.md
Normal file
@@ -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/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -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",
|
||||
|
||||
@@ -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 (
|
||||
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)
|
||||
}
|
||||
})
|
||||
|
||||
pkg.peerDependencies![peerName] = currentVersions.join(' || ')
|
||||
}
|
||||
if (allowAnyMatcher(peerName)) {
|
||||
pkg.peerDependencies![peerName] = '*'
|
||||
continue
|
||||
}
|
||||
if (
|
||||
!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])
|
||||
|
||||
allowedVersions.forEach(allowedVersion => {
|
||||
if (!currentVersions.includes(allowedVersion)) {
|
||||
currentVersions.push(allowedVersion)
|
||||
}
|
||||
})
|
||||
|
||||
pkg.peerDependencies![peerName] = currentVersions.join(' || ')
|
||||
}
|
||||
return pkg
|
||||
}) as ReadPackageHook
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -78,6 +78,9 @@
|
||||
{
|
||||
"path": "../manifest-utils"
|
||||
},
|
||||
{
|
||||
"path": "../matcher"
|
||||
},
|
||||
{
|
||||
"path": "../modules-cleaner"
|
||||
},
|
||||
|
||||
@@ -112,6 +112,7 @@ export type PackageExtension = Pick<BaseManifest, 'dependencies' | 'optionalDepe
|
||||
|
||||
export interface PeerDependencyRules {
|
||||
ignoreMissing?: string[]
|
||||
allowAny?: string[]
|
||||
allowedVersions?: Record<string, string>
|
||||
}
|
||||
|
||||
|
||||
2
pnpm-lock.yaml
generated
2
pnpm-lock.yaml
generated
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user