fix: resolve peer dependencies from the root of the workspace (#3549)

This commit is contained in:
Zoltan Kochan
2021-06-21 00:48:56 +03:00
committed by GitHub
parent 6c418943c3
commit c1cdc0184f
5 changed files with 97 additions and 67 deletions

View File

@@ -0,0 +1,5 @@
---
"@pnpm/resolve-dependencies": patch
---
Peer dependencies should get resolved from the workspace root.

View File

@@ -44,7 +44,6 @@
"dependency-path": "workspace:8.0.1",
"encode-registry": "^3.0.0",
"get-npm-tarball-url": "^2.0.2",
"import-from": "^3.0.0",
"path-exists": "^4.0.0",
"ramda": "^0.27.1",
"replace-string": "^3.1.0",

View File

@@ -4,7 +4,6 @@ import PnpmError from '@pnpm/error'
import logger from '@pnpm/logger'
import { Dependencies } from '@pnpm/types'
import { depPathToFilename } from 'dependency-path'
import importFrom from 'import-from'
import { KeyValuePair } from 'ramda'
import fromPairs from 'ramda/src/fromPairs'
import isEmpty from 'ramda/src/isEmpty'
@@ -66,28 +65,15 @@ export default function<T extends PartialResolvedPackage> (
} {
const depGraph: GenericDependenciesGraph<T> = {}
const pathsByNodeId = {}
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)
for (const { directNodeIdsByAlias, topParents, rootDir } of opts.projects) {
const pkgsByName = Object.assign(
fromPairs(
topParents.map(({ name, version }: {name: string, version: string}): KeyValuePair<string, ParentRef> => [
name,
{
depth: 0,
version,
},
])
),
toPkgByName(
Object
.keys(directNodeIdsByAlias)
.map((alias) => ({
alias,
node: opts.dependenciesTree[directNodeIdsByAlias[alias]],
nodeId: directNodeIdsByAlias[alias],
}))
)
)
const pkgsByName = {
...rootPkgsByName,
..._createPkgsByName({ directNodeIdsByAlias, topParents }),
}
resolvePeersOfChildren(directNodeIdsByAlias, pkgsByName, {
dependenciesTree: opts.dependenciesTree,
@@ -122,6 +108,35 @@ export default function<T extends PartialResolvedPackage> (
}
}
function createPkgsByName<T extends PartialResolvedPackage> (
dependenciesTree: DependenciesTree<T>,
{ directNodeIdsByAlias, topParents }: {
directNodeIdsByAlias: {[alias: string]: string}
topParents: Array<{name: string, version: string}>
}
) {
return Object.assign(
fromPairs(
topParents.map(({ name, version }): KeyValuePair<string, ParentRef> => [
name,
{
depth: 0,
version,
},
])
),
toPkgByName(
Object
.keys(directNodeIdsByAlias)
.map((alias) => ({
alias,
node: dependenciesTree[directNodeIdsByAlias[alias]],
nodeId: directNodeIdsByAlias[alias],
}))
)
)
}
interface PeersCacheItem {
depPath: string
resolvedPeers: Array<[string, string]>
@@ -367,34 +382,26 @@ function resolvePeers<T extends PartialResolvedPackage> (
for (const peerName in ctx.resolvedPackage.peerDependencies) { // eslint-disable-line:forin
const peerVersionRange = ctx.resolvedPackage.peerDependencies[peerName]
let resolved = ctx.parentPkgs[peerName]
const resolved = ctx.parentPkgs[peerName]
if (!resolved) {
missingPeers.push(peerName)
try {
const { version } = importFrom(ctx.rootDir, `${peerName}/package.json`) as { version: string }
resolved = {
depth: -1,
version,
}
} catch (err) {
if (
ctx.resolvedPackage.peerDependenciesMeta?.[peerName]?.optional === true
) {
continue
}
const friendlyPath = nodeIdToFriendlyPath(ctx.nodeId, ctx.dependenciesTree)
const message = `${friendlyPath ? `${friendlyPath}: ` : ''}${packageFriendlyId(ctx.resolvedPackage)} \
requires a peer of ${peerName}@${peerVersionRange} but none was installed.`
if (ctx.strictPeerDependencies) {
throw new PnpmError('MISSING_PEER_DEPENDENCY', message)
}
logger.warn({
message,
prefix: ctx.rootDir,
})
if (
ctx.resolvedPackage.peerDependenciesMeta?.[peerName]?.optional === true
) {
continue
}
const friendlyPath = nodeIdToFriendlyPath(ctx.nodeId, ctx.dependenciesTree)
const message = `${friendlyPath ? `${friendlyPath}: ` : ''}${packageFriendlyId(ctx.resolvedPackage)} \
requires a peer of ${peerName}@${peerVersionRange} but none was installed.`
if (ctx.strictPeerDependencies) {
throw new PnpmError('MISSING_PEER_DEPENDENCY', message)
}
logger.warn({
message,
prefix: ctx.rootDir,
})
continue
}
if (!semver.satisfies(resolved.version, peerVersionRange)) {

View File

@@ -178,25 +178,53 @@ test('strict-peer-dependencies: error is thrown when cannot resolve peer depende
).rejects.toThrow(/ajv-keywords@1.5.0 requires a peer of ajv@>=4.10.0 but none was installed./)
})
test('warning is not reported if the peer dependency can be required from a node_modules of a parent directory', async () => {
prepareEmpty()
test('peer dependency is resolved from the dependencies of the workspace root project', async () => {
const projects = preparePackages([
{
location: '.',
package: { name: 'root' },
},
{
location: 'pkg',
package: {},
},
])
const reporter = jest.fn()
await mutateModules([
{
buildIndex: 0,
manifest: {
name: 'root',
version: '1.0.0',
const manifest = await addDependenciesToPackage({}, ['ajv@4.10.0'], await testDefaults())
dependencies: {
ajv: '4.10.0',
},
},
mutation: 'install',
rootDir: process.cwd(),
},
{
buildIndex: 0,
manifest: {
name: 'root',
version: '1.0.0',
await fs.mkdir('pkg')
dependencies: {
'ajv-keywords': '1.5.0',
},
},
mutation: 'install',
rootDir: path.resolve('pkg'),
},
], await testDefaults({ reporter }))
process.chdir('pkg')
const reporter = sinon.spy()
await addDependenciesToPackage(manifest, ['ajv-keywords@1.5.0'], await testDefaults({ reporter }))
const logMatcher = sinon.match({
expect(reporter).not.toHaveBeenCalledWith({
message: 'ajv-keywords@1.5.0 requires a peer of ajv@>=4.10.0 but none was installed.',
})
const reportedTimes = reporter.withArgs(logMatcher).callCount
expect(reportedTimes).toBe(0)
const lockfile = await projects.root.readLockfile()
expect(lockfile.importers.pkg?.dependencies?.['ajv-keywords']).toBe('1.5.0_ajv@4.10.0')
})
test('warning is reported when cannot resolve peer dependency for non-top-level dependency', async () => {

9
pnpm-lock.yaml generated
View File

@@ -2748,7 +2748,6 @@ importers:
dependency-path: workspace:8.0.1
encode-registry: ^3.0.0
get-npm-tarball-url: ^2.0.2
import-from: ^3.0.0
path-exists: ^4.0.0
ramda: ^0.27.1
replace-string: ^3.1.0
@@ -2772,7 +2771,6 @@ importers:
dependency-path: link:../dependency-path
encode-registry: 3.0.0
get-npm-tarball-url: 2.0.2
import-from: 3.0.0
path-exists: 4.0.0
ramda: 0.27.1
replace-string: 3.1.0
@@ -8811,13 +8809,6 @@ packages:
resolve-from: 4.0.0
dev: true
/import-from/3.0.0:
resolution: {integrity: sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ==}
engines: {node: '>=8'}
dependencies:
resolve-from: 5.0.0
dev: false
/import-local/3.0.2:
resolution: {integrity: sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA==}
engines: {node: '>=8'}