feat: support peerDependencyRules for muting peer dep issues (#4212)

close #4183
This commit is contained in:
Zoltan Kochan
2022-01-11 13:06:53 +02:00
committed by GitHub
parent a004d070c8
commit 26cd01b880
9 changed files with 128 additions and 2 deletions

View File

@@ -0,0 +1,21 @@
---
"pnpm": minor
"@pnpm/plugin-commands-installation": minor
---
In order to mute some types of peer dependency warnings, a new section in `package.json` may be used for declaring peer dependency warning rules. For example, the next configuration will turn off any warnings about missing `babel-loader` peer dependency and about `@angular/common`, when the wanted version of `@angular/common` is not v13.
```json
{
"name": "foo",
"version": "0.0.0",
"pnpm": {
"peerDependencyRules": {
"ignoreMissing": ["babel-loader"],
"allowedVersions": {
"@angular/common": "13"
}
}
}
}
```

View File

@@ -0,0 +1,5 @@
---
"@pnpm/core": minor
---
New optional option supported: `peerDependencyRules`. This setting allows to mute specific peer dependency warnings.

View File

@@ -0,0 +1,5 @@
---
"@pnpm/types": minor
---
New field added to package.json.pnpm section: peerDependencyRules.

View File

@@ -0,0 +1,26 @@
import { PeerDependencyRules, ReadPackageHook } from '@pnpm/types'
import isEmpty from 'ramda/src/isEmpty'
export default function (
peerDependencyRules: PeerDependencyRules
): ReadPackageHook {
const ignoreMissing = new Set(peerDependencyRules.ignoreMissing ?? [])
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) {
pkg.peerDependenciesMeta = pkg.peerDependenciesMeta ?? {}
pkg.peerDependenciesMeta[peerName] = {
optional: true,
}
}
if (
peerDependencyRules.allowedVersions?.[peerName] &&
peerVersion !== '*'
) {
pkg.peerDependencies![peerName] += ` || ${peerDependencyRules.allowedVersions[peerName]}`
}
}
return pkg
}) as ReadPackageHook
}

View File

@@ -7,6 +7,7 @@ import { WorkspacePackages } from '@pnpm/resolver-base'
import { StoreController } from '@pnpm/store-controller-types'
import {
PackageExtension,
PeerDependencyRules,
ReadPackageHook,
Registries,
} from '@pnpm/types'
@@ -80,6 +81,7 @@ export interface StrictInstallOptions {
symlink: boolean
enableModulesDir: boolean
modulesCacheMaxAge: number
peerDependencyRules: PeerDependencyRules
hoistPattern: string[] | undefined
forceHoistPattern: boolean

View File

@@ -49,6 +49,7 @@ import {
DependenciesField,
DependencyManifest,
PackageExtension,
PeerDependencyRules,
ProjectManifest,
ReadPackageHook,
} from '@pnpm/types'
@@ -68,6 +69,7 @@ import removeDeps from '../uninstall/removeDeps'
import allProjectsAreUpToDate from './allProjectsAreUpToDate'
import createPackageExtender from './createPackageExtender'
import createVersionsOverrider from './createVersionsOverrider'
import createPeerDependencyPatcher from './createPeerDependencyPatcher'
import extendOptions, {
InstallOptions,
StrictInstallOptions,
@@ -170,6 +172,7 @@ export async function mutateModules (
overrides: opts.overrides,
lockfileDir: opts.lockfileDir,
packageExtensions: opts.packageExtensions,
peerDependencyRules: opts.peerDependencyRules,
})
const ctx = await getContext(projects, opts)
const pruneVirtualStore = ctx.modulesFile?.prunedAt && opts.modulesCacheMaxAge > 0
@@ -483,11 +486,13 @@ export function createReadPackageHook (
lockfileDir,
overrides,
packageExtensions,
peerDependencyRules,
readPackageHook,
}: {
lockfileDir: string
overrides?: Record<string, string>
packageExtensions?: Record<string, PackageExtension>
peerDependencyRules?: PeerDependencyRules
readPackageHook?: ReadPackageHook
}
): ReadPackageHook | undefined {
@@ -498,6 +503,12 @@ export function createReadPackageHook (
if (!isEmpty(packageExtensions ?? {})) {
hooks.push(createPackageExtender(packageExtensions!))
}
if (
peerDependencyRules != null &&
(!isEmpty(peerDependencyRules.ignoreMissing) || !isEmpty(peerDependencyRules.allowedVersions))
) {
hooks.push(createPeerDependencyPatcher(peerDependencyRules))
}
if (hooks.length === 0) {
return readPackageHook
}

View File

@@ -0,0 +1,39 @@
import createPeerDependencyPatcher from '@pnpm/core/lib/install/createPeerDependencyPatcher'
test('createPeerDependencyPatcher() ignores missing', () => {
const patcher = createPeerDependencyPatcher({
ignoreMissing: ['foo'],
})
const patchedPkg = patcher({
peerDependencies: {
foo: '*',
bar: '*',
},
})
expect(patchedPkg['peerDependenciesMeta']).toStrictEqual({
foo: {
optional: true,
},
})
})
test('createPeerDependencyPatcher() extends peer ranges', () => {
const patcher = createPeerDependencyPatcher({
allowedVersions: {
foo: '1',
qar: '1',
},
})
const patchedPkg = patcher({
peerDependencies: {
foo: '0',
bar: '0',
qar: '*',
},
})
expect(patchedPkg['peerDependencies']).toStrictEqual({
foo: '0 || 1',
bar: '0',
qar: '*',
})
})

View File

@@ -1,15 +1,26 @@
import { ProjectManifest } from '@pnpm/types'
import {
PackageExtension,
PeerDependencyRules,
ProjectManifest,
} from '@pnpm/types'
export default function getOptionsFromRootManifest (manifest: ProjectManifest) {
export default function getOptionsFromRootManifest (manifest: ProjectManifest): {
overrides?: Record<string, string>
neverBuiltDependencies?: string[]
packageExtensions?: Record<string, PackageExtension>
peerDependencyRules?: PeerDependencyRules
} {
// We read Yarn's resolutions field for compatibility
// but we really replace the version specs to any other version spec, not only to exact versions,
// so we cannot call it resolutions
const overrides = manifest.pnpm?.overrides ?? manifest.resolutions
const neverBuiltDependencies = manifest.pnpm?.neverBuiltDependencies ?? []
const packageExtensions = manifest.pnpm?.packageExtensions
const peerDependencyRules = manifest.pnpm?.peerDependencyRules
return {
overrides,
neverBuiltDependencies,
packageExtensions,
peerDependencyRules,
}
}

View File

@@ -97,11 +97,17 @@ export type DependencyManifest = BaseManifest & Required<Pick<BaseManifest, 'nam
export type PackageExtension = Pick<BaseManifest, 'dependencies' | 'optionalDependencies' | 'peerDependencies' | 'peerDependenciesMeta'>
export interface PeerDependencyRules {
ignoreMissing?: string[]
allowedVersions?: Record<string, string>
}
export type ProjectManifest = BaseManifest & {
pnpm?: {
neverBuiltDependencies?: string[]
overrides?: Record<string, string>
packageExtensions?: Record<string, PackageExtension>
peerDependencyRules?: PeerDependencyRules
}
private?: boolean
resolutions?: Record<string, string>