Skip to content

Commit

Permalink
feat: enhance peer dependency rules (#4876)
Browse files Browse the repository at this point in the history
Co-authored-by: Zoltan Kochan <z@kochan.io>

close #4835
  • Loading branch information
TravisJRyan committed Jun 13, 2022
1 parent 9d5bf09 commit fb5bbfd
Show file tree
Hide file tree
Showing 9 changed files with 112 additions and 19 deletions.
19 changes: 19 additions & 0 deletions .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`.
16 changes: 16 additions & 0 deletions .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/*"]
}
}
}
```
1 change: 1 addition & 0 deletions packages/core/package.json
Expand Up @@ -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",
Expand Down
46 changes: 28 additions & 18 deletions 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
Expand Down
6 changes: 5 additions & 1 deletion packages/core/src/install/index.ts
Expand Up @@ -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))
}
Expand Down
37 changes: 37 additions & 0 deletions packages/core/test/install/createPeerDependencyPatcher.test.ts
Expand Up @@ -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: {
Expand All @@ -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: {
Expand Down
3 changes: 3 additions & 0 deletions packages/core/tsconfig.json
Expand Up @@ -78,6 +78,9 @@
{
"path": "../manifest-utils"
},
{
"path": "../matcher"
},
{
"path": "../modules-cleaner"
},
Expand Down
1 change: 1 addition & 0 deletions packages/types/src/package.ts
Expand Up @@ -112,6 +112,7 @@ export type PackageExtension = Pick<BaseManifest, 'dependencies' | 'optionalDepe

export interface PeerDependencyRules {
ignoreMissing?: string[]
allowAny?: string[]
allowedVersions?: Record<string, string>
}

Expand Down
2 changes: 2 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit fb5bbfd

Please sign in to comment.