Skip to content

Commit

Permalink
feat: support resolving peers with npm aliases
Browse files Browse the repository at this point in the history
close pnpm#4301
  • Loading branch information
RexSkz committed Mar 15, 2023
1 parent f9c30c6 commit a5f2f19
Show file tree
Hide file tree
Showing 2 changed files with 189 additions and 40 deletions.
115 changes: 75 additions & 40 deletions pkg-manager/resolve-dependencies/src/resolvePeers.ts
Expand Up @@ -2,9 +2,11 @@ import filenamify from 'filenamify'
import path from 'path'
import { semverUtils } from '@yarnpkg/core'
import {
BadPeerDependencyIssue,
Dependencies,
PeerDependencyIssues,
PeerDependencyIssuesByProjects,
ProjectManifest,
} from '@pnpm/types'
import { depPathToFilename, createPeersFolderSuffix, createPeersFolderSuffixNewFormat } from '@pnpm/dependency-path'
import { KeyValuePair } from 'ramda'
Expand Down Expand Up @@ -48,6 +50,7 @@ export interface GenericDependenciesGraph<T extends PartialResolvedPackage> {
}

export interface ProjectToResolve {
manifest: ProjectManifest
directNodeIdsByAlias: { [alias: string]: string }
// only the top dependencies that were already installed
// to avoid warnings about unresolved peer dependencies
Expand Down Expand Up @@ -78,16 +81,30 @@ export function resolvePeers<T extends PartialResolvedPackage> (
const rootPkgsByName = opts.resolvePeersFromWorkspaceRoot ? getRootPkgsByName(opts.dependenciesTree, opts.projects) : {}
const peerDependencyIssuesByProjects: PeerDependencyIssuesByProjects = {}

for (const { directNodeIdsByAlias, topParents, rootDir, id } of opts.projects) {
for (const { directNodeIdsByAlias, manifest, topParents, rootDir, id } of opts.projects) {
const peerDependencyIssues: Pick<PeerDependencyIssues, 'bad' | 'missing'> = { bad: {}, missing: {} }
const pkgsByName = {
...rootPkgsByName,
..._createPkgsByName({ directNodeIdsByAlias, topParents }),
}

const npmAliases: Record<string, string[]> = {}
const allDependencies = {
...manifest.dependencies,
...manifest.devDependencies,
}
for (const [pkgName, version] of Object.entries(allDependencies)) {
if (version.startsWith('npm:')) {
const aliasedPkgName = version.substring(4).split('@')[0]
npmAliases[aliasedPkgName] = npmAliases[aliasedPkgName] ?? []
npmAliases[aliasedPkgName].push(pkgName)
}
}

resolvePeersOfChildren(directNodeIdsByAlias, pkgsByName, {
dependenciesTree: opts.dependenciesTree,
depGraph,
npmAliases,
lockfileDir: opts.lockfileDir,
pathsByNodeId,
depPathsByPkgId,
Expand Down Expand Up @@ -234,6 +251,7 @@ function resolvePeersOfNode<T extends PartialResolvedPackage> (
ctx: ResolvePeersContext & {
dependenciesTree: DependenciesTree<T>
depGraph: GenericDependenciesGraph<T>
npmAliases: Record<string, string[]>
virtualStoreDir: string
peerDependencyIssues: Pick<PeerDependencyIssues, 'bad' | 'missing'>
peersCache: PeersCache
Expand Down Expand Up @@ -306,6 +324,7 @@ function resolvePeersOfNode<T extends PartialResolvedPackage> (
currentDepth: node.depth,
dependenciesTree: ctx.dependenciesTree,
lockfileDir: ctx.lockfileDir,
npmAliases: ctx.npmAliases,
nodeId,
parentPkgs,
peerDependencyIssues: ctx.peerDependencyIssues,
Expand Down Expand Up @@ -437,6 +456,7 @@ function resolvePeersOfChildren<T extends PartialResolvedPackage> (
},
parentPkgs: ParentRefs,
ctx: ResolvePeersContext & {
npmAliases: Record<string, string[]>
peerDependencyIssues: Pick<PeerDependencyIssues, 'bad' | 'missing'>
peersCache: PeersCache
virtualStoreDir: string
Expand Down Expand Up @@ -466,6 +486,7 @@ function _resolvePeers<T extends PartialResolvedPackage> (
ctx: {
currentDepth: number
lockfileDir: string
npmAliases: Record<string, string[]>
nodeId: string
parentPkgs: ParentRefs
resolvedPackage: T
Expand All @@ -479,52 +500,66 @@ function _resolvePeers<T extends PartialResolvedPackage> (
for (const peerName in ctx.resolvedPackage.peerDependencies) { // eslint-disable-line:forin
const peerVersionRange = ctx.resolvedPackage.peerDependencies[peerName].replace(/^workspace:/, '')

const resolved = ctx.parentPkgs[peerName]
const pkgsToCheck = [
ctx.parentPkgs[peerName],
...(ctx.npmAliases[peerName] || []).map((alias) => ctx.parentPkgs[alias]),
].filter(Boolean)

const optionalPeer = ctx.resolvedPackage.peerDependenciesMeta?.[peerName]?.optional === true
const badPeerIssues: BadPeerDependencyIssue[] = []
let foundPeer = false

if (!resolved) {
missingPeers.push(peerName)
const location = getLocationFromNodeIdAndPkg({
dependenciesTree: ctx.dependenciesTree,
nodeId: ctx.nodeId,
pkg: ctx.resolvedPackage,
})
if (!ctx.peerDependencyIssues.missing[peerName]) {
ctx.peerDependencyIssues.missing[peerName] = []
for (const resolved of pkgsToCheck) {
if (!semverUtils.satisfiesWithPrereleases(resolved.version, peerVersionRange, true)) {
const location = getLocationFromNodeIdAndPkg({
dependenciesTree: ctx.dependenciesTree,
nodeId: ctx.nodeId,
pkg: ctx.resolvedPackage,
})
const peerLocation = resolved.nodeId == null
? []
: getLocationFromNodeId({
dependenciesTree: ctx.dependenciesTree,
nodeId: resolved.nodeId,
}).parents
badPeerIssues.push({
foundVersion: resolved.version,
resolvedFrom: peerLocation,
parents: location.parents,
optional: optionalPeer,
wantedRange: peerVersionRange,
})
} else {
if (resolved?.nodeId) resolvedPeers[peerName] = resolved.nodeId
foundPeer = true
break
}
ctx.peerDependencyIssues.missing[peerName].push({
parents: location.parents,
optional: optionalPeer,
wantedRange: peerVersionRange,
})
continue
}

if (!semverUtils.satisfiesWithPrereleases(resolved.version, peerVersionRange, true)) {
const location = getLocationFromNodeIdAndPkg({
dependenciesTree: ctx.dependenciesTree,
nodeId: ctx.nodeId,
pkg: ctx.resolvedPackage,
})
if (!ctx.peerDependencyIssues.bad[peerName]) {
ctx.peerDependencyIssues.bad[peerName] = []
}
const peerLocation = resolved.nodeId == null
? []
: getLocationFromNodeId({
if (!foundPeer) {
if (badPeerIssues.length) {
if (!ctx.peerDependencyIssues.bad[peerName]) {
ctx.peerDependencyIssues.bad[peerName] = []
}
ctx.peerDependencyIssues.bad[peerName].push(...badPeerIssues)
if (pkgsToCheck[0]?.nodeId) resolvedPeers[peerName] = pkgsToCheck[0].nodeId
} else {
missingPeers.push(peerName)
const location = getLocationFromNodeIdAndPkg({
dependenciesTree: ctx.dependenciesTree,
nodeId: resolved.nodeId,
}).parents
ctx.peerDependencyIssues.bad[peerName].push({
foundVersion: resolved.version,
resolvedFrom: peerLocation,
parents: location.parents,
optional: optionalPeer,
wantedRange: peerVersionRange,
})
nodeId: ctx.nodeId,
pkg: ctx.resolvedPackage,
})
if (!ctx.peerDependencyIssues.missing[peerName]) {
ctx.peerDependencyIssues.missing[peerName] = []
}
ctx.peerDependencyIssues.missing[peerName].push({
parents: location.parents,
optional: optionalPeer,
wantedRange: peerVersionRange,
})
}
}

if (resolved?.nodeId) resolvedPeers[peerName] = resolved.nodeId
}
return { resolvedPeers, missingPeers }
}
Expand Down
114 changes: 114 additions & 0 deletions pkg-manager/resolve-dependencies/test/resolvePeers.ts
Expand Up @@ -23,6 +23,7 @@ test('resolve peer dependencies of cyclic dependencies', () => {
const { dependenciesGraph } = resolvePeers({
projects: [
{
manifest: {},
directNodeIdsByAlias: {
foo: '>foo/1.0.0>',
},
Expand Down Expand Up @@ -130,6 +131,7 @@ test('when a package is referenced twice in the dependencies graph and one of th
const { dependenciesGraph } = resolvePeers({
projects: [
{
manifest: {},
directNodeIdsByAlias: {
zoo: '>zoo/1.0.0>',
bar: '>bar/1.0.0>',
Expand Down Expand Up @@ -256,6 +258,7 @@ describe('peer dependency issues', () => {
const { peerDependencyIssuesByProjects } = resolvePeers({
projects: [
{
manifest: {},
directNodeIdsByAlias: {
foo: '>project1>foo/1.0.0>',
},
Expand All @@ -264,6 +267,7 @@ describe('peer dependency issues', () => {
id: 'project1',
},
{
manifest: {},
directNodeIdsByAlias: {
bar: '>project2>bar/1.0.0>',
},
Expand All @@ -272,6 +276,7 @@ describe('peer dependency issues', () => {
id: 'project2',
},
{
manifest: {},
directNodeIdsByAlias: {
foo: '>project3>foo/1.0.0>',
bar: '>project3>bar/1.0.0>',
Expand All @@ -281,6 +286,7 @@ describe('peer dependency issues', () => {
id: 'project3',
},
{
manifest: {},
directNodeIdsByAlias: {
bar: '>project4>bar/1.0.0>',
qar: '>project4>qar/1.0.0>',
Expand All @@ -290,6 +296,7 @@ describe('peer dependency issues', () => {
id: 'project4',
},
{
manifest: {},
directNodeIdsByAlias: {
foo: '>project5>foo/1.0.0>',
bar: '>project5>bar/2.0.0>',
Expand All @@ -299,6 +306,7 @@ describe('peer dependency issues', () => {
id: 'project5',
},
{
manifest: {},
directNodeIdsByAlias: {
foo: '>project6>foo/2.0.0>',
bar: '>project6>bar/2.0.0>',
Expand Down Expand Up @@ -398,6 +406,7 @@ describe('unmet peer dependency issues', () => {
const { peerDependencyIssuesByProjects } = resolvePeers({
projects: [
{
manifest: {},
directNodeIdsByAlias: {
foo: '>project1>foo/1.0.0>',
peer1: '>project1>peer1/1.0.0-rc.0>',
Expand Down Expand Up @@ -461,6 +470,7 @@ describe('unmet peer dependency issue resolved from subdependency', () => {
const { peerDependencyIssuesByProjects } = resolvePeers({
projects: [
{
manifest: {},
directNodeIdsByAlias: {
foo: '>project>foo/1.0.0>',
},
Expand Down Expand Up @@ -516,3 +526,107 @@ describe('unmet peer dependency issue resolved from subdependency', () => {
expect(peerDependencyIssuesByProjects.project.bad.dep[0].resolvedFrom).toStrictEqual([{ name: 'foo', version: '1.0.0' }])
})
})

test('resolve peer dependencies with npm aliases', () => {
const fooPkg = {
name: 'foo',
depPath: 'foo/1.0.0',
version: '1.0.0',
peerDependencies: {
bar: '1.0.0',
},
}
const fooAliasPkg = {
name: 'foo',
depPath: 'foo/2.0.0',
version: '2.0.0',
peerDependencies: {
bar: '2.0.0',
},
}
const barPkg = {
name: 'bar',
depPath: 'bar/1.0.0',
version: '1.0.0',
peerDependencies: {},
}
const barAliasPkg = {
name: 'bar',
depPath: 'bar/2.0.0',
version: '2.0.0',
peerDependencies: {},
}
const { dependenciesGraph } = resolvePeers({
projects: [
{
manifest: {
dependencies: {
foo: '^1.0.0',
bar: '^1.0.0',
'foo-next': 'npm:foo@^2.0.0',
'bar-next': 'npm:bar@^2.0.0',
},
},
directNodeIdsByAlias: {
foo: '>foo/1.0.0>',
bar: '>bar/1.0.0>',
'foo-next': '>foo/2.0.0>',
'bar-next': '>bar/2.0.0>',
},
topParents: [],
rootDir: '',
id: '',
},
],
dependenciesTree: {
'>foo/1.0.0>': {
children: {
bar: '>foo/1.0.0>bar/1.0.0>',
},
installable: true,
resolvedPackage: fooPkg,
depth: 0,
},
'>foo/1.0.0>bar/1.0.0>': {
children: {},
installable: true,
resolvedPackage: barPkg,
depth: 1,
},
'>foo/2.0.0>': {
children: {
bar: '>foo/2.0.0>bar/2.0.0>',
},
installable: true,
resolvedPackage: fooAliasPkg,
depth: 0,
},
'>foo/2.0.0>bar/2.0.0>': {
children: {},
installable: true,
resolvedPackage: barAliasPkg,
depth: 1,
},
'>bar/1.0.0>': {
children: {},
installable: true,
resolvedPackage: barPkg,
depth: 0,
},
'>bar/2.0.0>': {
children: {},
installable: true,
resolvedPackage: barAliasPkg,
depth: 0,
},
},
virtualStoreDir: '',
lockfileDir: '',
})
expect(Object.keys(dependenciesGraph)).toStrictEqual([
'bar/1.0.0',
'foo/1.0.0_bar@1.0.0',
'bar/2.0.0',
'foo/2.0.0_bar@2.0.0',
])
})

0 comments on commit a5f2f19

Please sign in to comment.