feat: detect conflicting peer dependencies per project (#4111)

This commit is contained in:
Zoltan Kochan
2021-12-12 19:13:08 +02:00
committed by GitHub
parent f82cc7f774
commit 89dcc1115a
17 changed files with 350 additions and 114 deletions

View File

@@ -2,4 +2,4 @@
"@pnpm/core": minor
---
New function added to the core API: `listPeerDependencyIssues()`.
New function added to the core API: `getPeerDependencyIssues()`.

View File

@@ -62,7 +62,6 @@
"ramda": "^0.27.1",
"run-groups": "^3.0.1",
"semver": "^7.3.4",
"semver-intersect": "^1.4.0",
"version-selector-type": "^3.0.0"
},
"devDependencies": {

View File

@@ -3,7 +3,7 @@ import link from './link'
export * from './install'
export { PeerDependencyIssuesError } from './install/reportPeerDependencyIssues'
export * from './link'
export * from './listPeerDependencyIssues'
export * from './getPeerDependencyIssues'
export {
link,
}

View File

@@ -6,7 +6,6 @@ import { createReadPackageHook } from './install'
import { getPreferredVersionsFromLockfile } from './install/getPreferredVersions'
import { InstallOptions } from './install/extendInstallOptions'
import { DEFAULT_REGISTRIES } from '@pnpm/normalize-registries'
import { intersect } from 'semver-intersect'
export type ListMissingPeersOptions = Partial<GetContextOptions>
& Pick<InstallOptions, 'hooks'
@@ -21,10 +20,10 @@ export type ListMissingPeersOptions = Partial<GetContextOptions>
>
& Pick<GetContextOptions, 'storeDir'>
export async function listPeerDependencyIssues (
export async function getPeerDependencyIssues (
projects: ProjectOptions[],
opts: ListMissingPeersOptions
) {
): Promise<PeerDependencyIssues> {
const lockfileDir = opts.lockfileDir ?? process.cwd()
const ctx = await getContext(projects, {
force: false,
@@ -78,34 +77,7 @@ export async function listPeerDependencyIssues (
}
)
const conflicts = getPeerDependencyConflicts(peerDependencyIssues)
await waitTillAllFetchingsFinish()
return {
issues: peerDependencyIssues,
conflicts,
}
}
function getPeerDependencyConflicts (peerDependencyIssues: PeerDependencyIssues) {
const missingPeers = new Map<string, string[]>()
for (const [peerName, issues] of Object.entries(peerDependencyIssues.missing)) {
missingPeers.set(peerName, issues.map(({ wantedRange }) => wantedRange))
}
const conflicts = [] as string[]
for (const [peerName, ranges] of missingPeers) {
if (!intersectSafe(ranges)) {
conflicts.push(peerName)
}
}
return conflicts
}
function intersectSafe (ranges: string[]) {
try {
return intersect(...ranges)
} catch {
return false
}
return peerDependencyIssues
}

View File

@@ -1,11 +1,11 @@
import { listPeerDependencyIssues } from '@pnpm/core'
import { getPeerDependencyIssues } from '@pnpm/core'
import { prepareEmpty } from '@pnpm/prepare'
import { testDefaults } from './utils'
test('cannot resolve peer dependency for top-level dependency', async () => {
prepareEmpty()
const peerDependencyIssues = await listPeerDependencyIssues([
const peerDependencyIssues = await getPeerDependencyIssues([
{
manifest: {
dependencies: {
@@ -16,13 +16,13 @@ test('cannot resolve peer dependency for top-level dependency', async () => {
},
], await testDefaults())
expect(peerDependencyIssues.issues.missing).toHaveProperty('ajv')
expect(peerDependencyIssues.missing).toHaveProperty('ajv')
})
test('a conflict is detected when the same peer is required with ranges that do not overlap', async () => {
prepareEmpty()
const peerDependencyIssues = await listPeerDependencyIssues([
const peerDependencyIssues = await getPeerDependencyIssues([
{
manifest: {
dependencies: {
@@ -34,5 +34,5 @@ test('a conflict is detected when the same peer is required with ranges that do
},
], await testDefaults())
expect(peerDependencyIssues.conflicts.length).toBe(1)
expect(peerDependencyIssues.missingMergedByProjects['.'].conflicts.length).toBe(1)
})

View File

@@ -169,7 +169,7 @@ test('warning is reported when cannot resolve peer dependency for top-level depe
missing: {
ajv: [{
location: {
projectPath: '',
projectId: '.',
parents: [
{
name: 'ajv-keywords',
@@ -180,6 +180,14 @@ test('warning is reported when cannot resolve peer dependency for top-level depe
wantedRange: '>=4.10.0',
}],
},
missingMergedByProjects: {
'.': {
conflicts: [],
intersections: [
{ peerName: 'ajv', versionRange: '>=4.10.0' },
],
},
},
})
)
})
@@ -199,7 +207,7 @@ test('strict-peer-dependencies: error is thrown when cannot resolve peer depende
missing: {
ajv: [{
location: {
projectPath: '',
projectId: '.',
parents: [
{
name: 'ajv-keywords',
@@ -210,6 +218,14 @@ test('strict-peer-dependencies: error is thrown when cannot resolve peer depende
wantedRange: '>=4.10.0',
}],
},
missingMergedByProjects: {
'.': {
conflicts: [],
intersections: [
{ peerName: 'ajv', versionRange: '>=4.10.0' },
],
},
},
})
})
@@ -315,7 +331,7 @@ test('warning is reported when cannot resolve peer dependency for non-top-level
missing: {
'peer-c': [{
location: {
projectPath: '',
projectId: '.',
parents: [
{
name: 'abc-grand-parent-without-c',
@@ -334,6 +350,14 @@ test('warning is reported when cannot resolve peer dependency for non-top-level
wantedRange: '^1.0.0',
}],
},
missingMergedByProjects: {
'.': {
conflicts: [],
intersections: [
{ peerName: 'peer-c', versionRange: '^1.0.0' },
],
},
},
})
)
})
@@ -353,7 +377,7 @@ test('warning is reported when bad version of resolved peer dependency for non-t
bad: {
'peer-c': [{
location: {
projectPath: '',
projectId: '.',
parents: [
{
name: 'abc-grand-parent-without-c',
@@ -374,6 +398,7 @@ test('warning is reported when bad version of resolved peer dependency for non-t
}],
},
missing: {},
missingMergedByProjects: {},
})
)
})
@@ -393,7 +418,7 @@ test('strict-peer-dependencies: error is thrown when bad version of resolved pee
bad: {
'peer-c': [{
location: {
projectPath: '',
projectId: '.',
parents: [
{
name: 'abc-grand-parent-without-c',
@@ -414,6 +439,7 @@ test('strict-peer-dependencies: error is thrown when bad version of resolved pee
}],
},
missing: {},
missingMergedByProjects: {},
})
})
@@ -987,7 +1013,7 @@ test('warning is not reported when cannot resolve optional peer dependency', asy
bad: {
'peer-c': [{
location: {
projectPath: '',
projectId: '.',
parents: [
{
name: 'abc-optional-peers',
@@ -1002,7 +1028,7 @@ test('warning is not reported when cannot resolve optional peer dependency', asy
missing: {
'peer-a': [{
location: {
projectPath: '',
projectId: '.',
parents: [
{
name: 'abc-optional-peers',
@@ -1013,6 +1039,17 @@ test('warning is not reported when cannot resolve optional peer dependency', asy
wantedRange: '^1.0.0',
}],
},
missingMergedByProjects: {
'.': {
conflicts: [],
intersections: [
{
peerName: 'peer-a',
versionRange: '^1.0.0',
},
],
},
},
})
)
@@ -1043,7 +1080,7 @@ test('warning is not reported when cannot resolve optional peer dependency (spec
missing: {
'peer-a': [{
location: {
projectPath: '',
projectId: '.',
parents: [
{
name: 'abc-optional-peers-meta-only',
@@ -1054,6 +1091,17 @@ test('warning is not reported when cannot resolve optional peer dependency (spec
wantedRange: '^1.0.0',
}],
},
missingMergedByProjects: {
'.': {
conflicts: [],
intersections: [
{
peerName: 'peer-a',
versionRange: '^1.0.0',
},
],
},
},
})
)

View File

@@ -25,13 +25,19 @@ test('print peer dependency issues warning', (done) => {
version: '1.0.0',
},
],
projectPath: '',
projectId: '.',
},
foundVersion: '2',
wantedRange: '3',
},
],
},
missingMergedByProjects: {
'.': {
conflicts: [],
intersections: [],
},
},
})
expect.assertions(1)
@@ -40,7 +46,7 @@ test('print peer dependency issues warning', (done) => {
complete: () => done(),
error: done,
next: output => {
expect(output).toContain('<ROOT>')
expect(output).toContain('.')
},
})
})
@@ -65,12 +71,18 @@ test('print peer dependency issues error', (done) => {
version: '1.0.0',
},
],
projectPath: '',
projectId: '.',
},
wantedRange: '3',
},
],
},
missingMergedByProjects: {
'.': {
conflicts: [],
intersections: [],
},
},
}
logger.error(err, err)
@@ -82,7 +94,7 @@ test('print peer dependency issues error', (done) => {
complete: () => done(),
error: done,
next: output => {
expect(output).toContain('<ROOT>')
expect(output).toContain('.')
},
})
})

View File

@@ -2,32 +2,45 @@ import { PeerDependencyIssues } from '@pnpm/types'
import archy from 'archy'
import chalk from 'chalk'
const ROOT_LABEL = '<ROOT>'
export default function (peerDependencyIssues: PeerDependencyIssues) {
export default function (
{
bad,
missing,
missingMergedByProjects,
}: PeerDependencyIssues
) {
const projects = {} as Record<string, PkgNode>
for (const [peerName, issues] of Object.entries(peerDependencyIssues.missing)) {
for (const [peerName, issues] of Object.entries(missing)) {
for (const issue of issues) {
const projectPath = issue.location.projectPath || ROOT_LABEL
if (!projects[projectPath]) {
projects[projectPath] = { dependencies: {}, peerIssues: [] }
const projectId = issue.location.projectId
if (!projects[projectId]) {
projects[projectId] = { dependencies: {}, peerIssues: [] }
}
createTree(projects[projectPath], issue.location.parents, `${chalk.red('✕ missing peer')} ${peerName}@"${issue.wantedRange}"`)
createTree(projects[projectId], issue.location.parents, `${chalk.red('✕ missing peer')} ${peerName}@"${issue.wantedRange}"`)
}
}
for (const [peerName, issues] of Object.entries(peerDependencyIssues.bad)) {
for (const [peerName, issues] of Object.entries(bad)) {
for (const issue of issues) {
const projectPath = issue.location.projectPath || ROOT_LABEL
if (!projects[projectPath]) {
projects[projectPath] = { dependencies: {}, peerIssues: [] }
const projectId = issue.location.projectId
if (!projects[projectId]) {
projects[projectId] = { dependencies: {}, peerIssues: [] }
}
// eslint-disable-next-line
createTree(projects[projectPath], issue.location.parents, `${chalk.red('✕ unmet peer')} ${peerName}@"${issue.wantedRange}": found ${issue.foundVersion}`)
createTree(projects[projectId], issue.location.parents, `${chalk.red('✕ unmet peer')} ${peerName}@"${issue.wantedRange}": found ${issue.foundVersion}`)
}
}
return Object.entries(projects)
.sort(([projectKey1], [projectKey2]) => projectKey1.localeCompare(projectKey2))
.map(([projectKey, project]) => archy(toArchyData(projectKey, project))).join('')
.map(([projectKey, project]) => {
let label = projectKey
for (const conflict of missingMergedByProjects[projectKey].conflicts) {
label += `\n${chalk.red(`✕ conflicting ranges for ${conflict}`)}`
}
for (const { peerName, versionRange } of missingMergedByProjects[projectKey].intersections) {
label += `\nadd ${peerName}@"${versionRange}"`
}
return archy(toArchyData(label, project))
}).join('')
}
interface PkgNode {

View File

@@ -1,14 +1,14 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renderPeerIssues() 1`] = `
"/packages/0
└─┬ zzz
└── ✕ missing peer ddd@\\"^1.0.0\\"
<ROOT>
".
└─┬ xxx
├── ✕ unmet peer bbb@\\"^1.0.0\\": found 2
└─┬ yyy
├── ✕ missing peer aaa@\\"^1.0.0\\"
└── ✕ unmet peer ccc@\\"^1.0.0\\": found 2
packages/0
└─┬ zzz
└── ✕ missing peer ddd@\\"^1.0.0\\"
"
`;

View File

@@ -17,7 +17,7 @@ test('renderPeerIssues()', () => {
version: '1.0.0',
},
],
projectPath: '',
projectId: '.',
},
wantedRange: '^1.0.0',
},
@@ -31,7 +31,7 @@ test('renderPeerIssues()', () => {
version: '1.0.0',
},
],
projectPath: '/packages/0',
projectId: 'packages/0',
},
wantedRange: '^1.0.0',
},
@@ -47,7 +47,7 @@ test('renderPeerIssues()', () => {
version: '1.0.0',
},
],
projectPath: '',
projectId: '.',
},
foundVersion: '2',
wantedRange: '^1.0.0',
@@ -66,12 +66,22 @@ test('renderPeerIssues()', () => {
version: '1.0.0',
},
],
projectPath: '',
projectId: '.',
},
foundVersion: '2',
wantedRange: '^1.0.0',
},
],
},
missingMergedByProjects: {
'.': {
conflicts: [],
intersections: [],
},
'packages/0': {
conflicts: [],
intersections: [],
},
},
}))).toMatchSnapshot()
})

View File

@@ -52,6 +52,7 @@
"ramda": "^0.27.1",
"replace-string": "^3.1.0",
"semver": "^7.3.4",
"semver-range-intersect": "^0.3.1",
"version-selector-type": "^3.0.0"
},
"devDependencies": {

View File

@@ -0,0 +1,31 @@
import { MergedPeersByProjects } from '@pnpm/types'
import { intersect } from 'semver-range-intersect'
export function mergePeersByProjects (missingPeersByProject: Record<string, Record<string, string[]>>): MergedPeersByProjects {
const mergedPeersByProjects: MergedPeersByProjects = {}
for (const [projectPath, rangesByPeerNames] of Object.entries(missingPeersByProject)) {
mergedPeersByProjects[projectPath] = {
conflicts: [],
intersections: [],
}
for (const [peerName, ranges] of Object.entries(rangesByPeerNames)) {
if (ranges.length === 1) {
mergedPeersByProjects[projectPath].intersections.push({
peerName,
versionRange: ranges[0],
})
continue
}
const intersection = intersect(...ranges)
if (intersection === null) {
mergedPeersByProjects[projectPath].conflicts.push(peerName)
} else {
mergedPeersByProjects[projectPath].intersections.push({
peerName,
versionRange: intersection,
})
}
}
}
return mergedPeersByProjects
}

View File

@@ -1,6 +1,11 @@
import crypto from 'crypto'
import path from 'path'
import { Dependencies, PeerDependencyIssues } from '@pnpm/types'
import {
BadPeerIssuesByPeerName,
MissingPeerIssuesByPeerName,
Dependencies,
PeerDependencyIssues,
} from '@pnpm/types'
import { depPathToFilename } from 'dependency-path'
import { KeyValuePair } from 'ramda'
import fromPairs from 'ramda/src/fromPairs'
@@ -13,6 +18,7 @@ import {
DependenciesTreeNode,
ResolvedPackage,
} from './resolveDependencies'
import { mergePeersByProjects } from './mergePeersByProjects'
import { createNodeId, splitNodeId } from './nodeIdUtils'
export interface GenericDependenciesGraphNode {
@@ -64,10 +70,9 @@ export default function<T extends PartialResolvedPackage> (
const _createPkgsByName = createPkgsByName.bind(null, opts.dependenciesTree)
const rootProject = opts.projects.length > 1 ? opts.projects.find(({ id }) => id === '.') : null
const rootPkgsByName = rootProject == null ? {} : _createPkgsByName(rootProject)
const peerDependencyIssues: PeerDependencyIssues = {
bad: {},
missing: {},
}
const badPeers: BadPeerIssuesByPeerName = {}
const missingPeers: MissingPeerIssuesByPeerName = {}
const missingPeersByProject = {} as Record<string, Record<string, string[]>>
for (const { directNodeIdsByAlias, topParents, rootDir } of opts.projects) {
const pkgsByName = {
@@ -76,11 +81,13 @@ export default function<T extends PartialResolvedPackage> (
}
resolvePeersOfChildren(directNodeIdsByAlias, pkgsByName, {
badPeers,
missingPeers,
dependenciesTree: opts.dependenciesTree,
depGraph,
lockfileDir: opts.lockfileDir,
missingPeersByProject,
pathsByNodeId,
peerDependencyIssues,
peersCache: new Map(),
purePkgs: new Set(),
rootDir,
@@ -105,7 +112,11 @@ export default function<T extends PartialResolvedPackage> (
return {
dependenciesGraph: depGraph,
dependenciesByProjectId,
peerDependencyIssues,
peerDependencyIssues: {
bad: badPeers,
missing: missingPeers,
missingMergedByProjects: mergePeersByProjects(missingPeersByProject),
},
}
}
@@ -159,7 +170,9 @@ function resolvePeersOfNode<T extends PartialResolvedPackage> (
pathsByNodeId: {[nodeId: string]: string}
depGraph: GenericDependenciesGraph<T>
virtualStoreDir: string
peerDependencyIssues: PeerDependencyIssues
badPeers: BadPeerIssuesByPeerName
missingPeers: MissingPeerIssuesByPeerName
missingPeersByProject: Record<string, Record<string, string[]>>
peersCache: PeersCache
purePkgs: Set<string> // pure packages are those that don't rely on externally resolved peers
rootDir: string
@@ -231,7 +244,9 @@ function resolvePeersOfNode<T extends PartialResolvedPackage> (
lockfileDir: ctx.lockfileDir,
nodeId,
parentPkgs,
peerDependencyIssues: ctx.peerDependencyIssues,
badPeers: ctx.badPeers,
missingPeers: ctx.missingPeers,
missingPeersByProject: ctx.missingPeersByProject,
resolvedPackage,
rootDir: ctx.rootDir,
})
@@ -339,7 +354,9 @@ function resolvePeersOfChildren<T extends PartialResolvedPackage> (
parentPkgs: ParentRefs,
ctx: {
pathsByNodeId: {[nodeId: string]: string}
peerDependencyIssues: PeerDependencyIssues
badPeers: BadPeerIssuesByPeerName
missingPeers: MissingPeerIssuesByPeerName
missingPeersByProject: Record<string, Record<string, string[]>>
peersCache: PeersCache
virtualStoreDir: string
purePkgs: Set<string>
@@ -377,7 +394,9 @@ function resolvePeers<T extends PartialResolvedPackage> (
resolvedPackage: T
dependenciesTree: DependenciesTree<T>
rootDir: string
peerDependencyIssues: PeerDependencyIssues
badPeers: BadPeerIssuesByPeerName
missingPeers: MissingPeerIssuesByPeerName
missingPeersByProject: Record<string, Record<string, string[]>>
}
): PeersResolution {
const resolvedPeers: {[alias: string]: string} = {}
@@ -394,32 +413,36 @@ function resolvePeers<T extends PartialResolvedPackage> (
) {
continue
}
if (!ctx.peerDependencyIssues.missing[peerName]) {
ctx.peerDependencyIssues.missing[peerName] = []
if (!ctx.missingPeers[peerName]) {
ctx.missingPeers[peerName] = []
}
ctx.peerDependencyIssues.missing[peerName].push({
const issue = {
location: getLocationFromNodeId({
dependenciesTree: ctx.dependenciesTree,
nodeId: ctx.nodeId,
lockfileDir: ctx.lockfileDir,
rootDir: ctx.rootDir,
pkg: ctx.resolvedPackage,
}),
wantedRange: peerVersionRange,
})
}
ctx.missingPeers[peerName].push(issue)
if (!ctx.missingPeersByProject[issue.location.projectId]) {
ctx.missingPeersByProject[issue.location.projectId] = {}
}
if (!ctx.missingPeersByProject[issue.location.projectId][peerName]) {
ctx.missingPeersByProject[issue.location.projectId][peerName] = []
}
ctx.missingPeersByProject[issue.location.projectId][peerName].push(peerVersionRange)
continue
}
if (!semver.satisfies(resolved.version, peerVersionRange, { loose: true })) {
if (!ctx.peerDependencyIssues.bad[peerName]) {
ctx.peerDependencyIssues.bad[peerName] = []
if (!ctx.badPeers[peerName]) {
ctx.badPeers[peerName] = []
}
ctx.peerDependencyIssues.bad[peerName].push({
ctx.badPeers[peerName].push({
location: getLocationFromNodeId({
dependenciesTree: ctx.dependenciesTree,
nodeId: ctx.nodeId,
lockfileDir: ctx.lockfileDir,
rootDir: ctx.rootDir,
pkg: ctx.resolvedPackage,
}),
foundVersion: resolved.version,
@@ -435,16 +458,12 @@ function resolvePeers<T extends PartialResolvedPackage> (
function getLocationFromNodeId<T> (
{
dependenciesTree,
lockfileDir,
nodeId,
rootDir,
pkg,
}: {
dependenciesTree: DependenciesTree<T>
lockfileDir: string
nodeId: string
pkg: PartialResolvedPackage
rootDir: string
}
) {
const parts = splitNodeId(nodeId).slice(0, -1)
@@ -452,9 +471,8 @@ function getLocationFromNodeId<T> (
.slice(2)
.map((nid) => pick(['name', 'version'], dependenciesTree[nid].resolvedPackage as ResolvedPackage))
parents.push({ name: pkg.name, version: pkg.version })
const projectPath = path.relative(lockfileDir, rootDir)
return {
projectPath,
projectId: parts[0],
parents,
}
}

View File

@@ -201,3 +201,121 @@ test('when a package is referenced twice in the dependencies graph and one of th
'bar/1.0.0',
])
})
describe('peer dependency issues', () => {
const fooPkg = {
name: 'foo',
depPath: 'foo/1.0.0',
version: '1.0.0',
peerDependencies: {
peer: '1',
},
}
const barPkg = {
name: 'bar',
depPath: 'bar/1.0.0',
version: '1.0.0',
peerDependencies: {
peer: '2',
},
}
const qarPkg = {
name: 'qar',
depPath: 'qar/1.0.0',
version: '1.0.0',
peerDependencies: {
peer: '^2.2.0',
},
}
const { peerDependencyIssues } = resolvePeers({
projects: [
{
directNodeIdsByAlias: {
foo: '>project1>foo/1.0.0',
},
topParents: [],
rootDir: '',
id: 'project1',
},
{
directNodeIdsByAlias: {
bar: '>project2>bar/1.0.0',
},
topParents: [],
rootDir: '',
id: 'project2',
},
{
directNodeIdsByAlias: {
foo: '>project3>foo/1.0.0',
bar: '>project3>bar/1.0.0',
},
topParents: [],
rootDir: '',
id: 'project3',
},
{
directNodeIdsByAlias: {
bar: '>project4>bar/1.0.0',
qar: '>project4>qar/1.0.0',
},
topParents: [],
rootDir: '',
id: 'project4',
},
],
dependenciesTree: {
'>project1>foo/1.0.0': {
children: {},
installable: true,
resolvedPackage: fooPkg,
depth: 0,
},
'>project2>bar/1.0.0': {
children: {},
installable: true,
resolvedPackage: barPkg,
depth: 0,
},
'>project3>foo/1.0.0': {
children: {},
installable: true,
resolvedPackage: fooPkg,
depth: 0,
},
'>project3>bar/1.0.0': {
children: {},
installable: true,
resolvedPackage: barPkg,
depth: 0,
},
'>project4>bar/1.0.0': {
children: {},
installable: true,
resolvedPackage: barPkg,
depth: 0,
},
'>project4>qar/1.0.0': {
children: {},
installable: true,
resolvedPackage: qarPkg,
depth: 0,
},
},
virtualStoreDir: '',
lockfileDir: '',
})
it('should find peer dependency conflicts', () => {
expect(peerDependencyIssues.missingMergedByProjects['project3'].conflicts).toStrictEqual(['peer'])
})
it('should pick the single wanted peer dependency range', () => {
expect(peerDependencyIssues.missingMergedByProjects['project1'].intersections)
.toStrictEqual([{ peerName: 'peer', versionRange: '1' }])
expect(peerDependencyIssues.missingMergedByProjects['project2'].intersections)
.toStrictEqual([{ peerName: 'peer', versionRange: '2' }])
})
it('should return the intersection of two compatible ranges', () => {
expect(peerDependencyIssues.missingMergedByProjects['project4'].intersections)
.toStrictEqual([{ peerName: 'peer', versionRange: '>=2.2.0 <3.0.0' }])
})
})

View File

@@ -1,6 +1,6 @@
export interface PeerDependencyIssueLocation {
parents: Array<{ name: string, version: string }>
projectPath: string
projectId: string
}
export interface MissingPeerDependencyIssue {
@@ -8,11 +8,28 @@ export interface MissingPeerDependencyIssue {
wantedRange: string
}
export type MissingPeerIssuesByPeerName = Record<string, MissingPeerDependencyIssue[]>
export interface BadPeerDependencyIssue extends MissingPeerDependencyIssue {
foundVersion: string
}
export type BadPeerIssuesByPeerName = Record<string, BadPeerDependencyIssue[]>
export interface PeerDependencyIssues {
bad: Record<string, BadPeerDependencyIssue[]>
missing: Record<string, MissingPeerDependencyIssue[]>
bad: BadPeerIssuesByPeerName
missing: MissingPeerIssuesByPeerName
missingMergedByProjects: MergedPeersByProjects
}
export type MergedPeersByProjects = Record<string, MergedPeers>
export interface MergedPeers {
conflicts: string[]
intersections: PeerIntersection[]
}
export interface PeerIntersection {
peerName: string
versionRange: string
}

13
pnpm-lock.yaml generated
View File

@@ -451,7 +451,6 @@ importers:
resolve-link-target: ^2.0.0
run-groups: ^3.0.1
semver: ^7.3.4
semver-intersect: ^1.4.0
sinon: ^11.1.1
symlink-dir: ^5.0.0
version-selector-type: ^3.0.0
@@ -505,7 +504,6 @@ importers:
ramda: 0.27.1
run-groups: 3.0.1
semver: 7.3.5
semver-intersect: 1.4.0
version-selector-type: 3.0.0
devDependencies:
'@pnpm/assert-project': link:../../privatePackages/assert-project
@@ -3104,6 +3102,7 @@ importers:
ramda: ^0.27.1
replace-string: ^3.1.0
semver: ^7.3.4
semver-range-intersect: ^0.3.1
version-selector-type: ^3.0.0
dependencies:
'@pnpm/constants': link:../constants
@@ -3129,6 +3128,7 @@ importers:
ramda: 0.27.1
replace-string: 3.1.0
semver: 7.3.5
semver-range-intersect: 0.3.1
version-selector-type: 3.0.0
devDependencies:
'@pnpm/logger': 4.0.0
@@ -5331,7 +5331,6 @@ packages:
/@types/semver/6.2.3:
resolution: {integrity: sha512-KQf+QAMWKMrtBMsB8/24w53tEsxllMj6TuA80TT/5igJalLI/zm0L3oXRbIAl4Ohfc85gyHX/jhMwsVkmhLU4A==}
dev: true
/@types/semver/7.3.9:
resolution: {integrity: sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ==}
@@ -13836,10 +13835,12 @@ packages:
xmlchars: 2.2.0
dev: true
/semver-intersect/1.4.0:
resolution: {integrity: sha512-d8fvGg5ycKAq0+I6nfWeCx6ffaWJCsBYU0H2Rq56+/zFePYfT8mXkB3tWBSjR5BerkHNZ5eTPIk1/LBYas35xQ==}
/semver-range-intersect/0.3.1:
resolution: {integrity: sha512-dZAVI9Gdl3uBvs1CBK1KHeCyiZDn4X14DW4C+QFQj+0k+l9L+pY1swt4KVt1hGU2dP77but4vx+N5XeYQsDteQ==}
engines: {node: '>=8.3.0'}
dependencies:
semver: 5.7.1
'@types/semver': 6.2.3
semver: 6.3.0
dev: false
/semver-utils/1.1.4:

4
typings/typed.d.ts vendored
View File

@@ -60,7 +60,3 @@ declare module 'bin-links/lib/fix-bin' {
declare namespace NodeJS.Module {
function _nodeModulePaths(from: string): string[]
}
declare module 'semver-intersect' {
export function intersect (...range: string[]): string
}