fix: finish installation before throwing peer dependencies error (#4095)

This commit is contained in:
Zoltan Kochan
2021-12-09 03:51:47 +02:00
committed by GitHub
parent 25f0fa9fa7
commit 5af305f395
7 changed files with 73 additions and 59 deletions

View File

@@ -0,0 +1,7 @@
---
"@pnpm/core": patch
"@pnpm/resolve-dependencies": patch
"pnpm": patch
---
Installation should be finished before an error about bad/missing peer dependencies is printed and kills the process.

View File

@@ -74,6 +74,7 @@ import extendOptions, {
} from './extendInstallOptions'
import { getPreferredVersionsFromLockfile, getAllUniqueSpecs } from './getPreferredVersions'
import linkPackages from './link'
import reportPeerDependencyIssues from './reportPeerDependencyIssues'
const BROKEN_LOCKFILE_INTEGRITY_ERRORS = new Set([
'ERR_PNPM_UNEXPECTED_PKG_CONTENT_IN_STORE',
@@ -716,6 +717,7 @@ const _installInContext: InstallFunction = async (projects, ctx, opts) => {
linkedDependenciesByProjectId,
newLockfile,
outdatedDependencies,
peerDependencyIssues,
wantedToBeSkippedPackageIds,
waitTillAllFetchingsFinish,
} = await resolveDependencies(
@@ -739,7 +741,6 @@ const _installInContext: InstallFunction = async (projects, ctx, opts) => {
registries: ctx.registries,
saveWorkspaceProtocol: opts.saveWorkspaceProtocol,
storeController: opts.storeController,
strictPeerDependencies: opts.strictPeerDependencies,
tag: opts.tag,
updateMatching: opts.updateMatching,
virtualStoreDir: ctx.virtualStoreDir,
@@ -788,7 +789,6 @@ const _installInContext: InstallFunction = async (projects, ctx, opts) => {
symlink: opts.symlink,
skipped: ctx.skipped,
storeController: opts.storeController,
strictPeerDependencies: opts.strictPeerDependencies,
virtualStoreDir: ctx.virtualStoreDir,
wantedLockfile: newLockfile,
wantedToBeSkippedPackageIds,
@@ -961,6 +961,13 @@ const _installInContext: InstallFunction = async (projects, ctx, opts) => {
await opts.storeController.close()
if (peerDependencyIssues.length > 0) {
reportPeerDependencyIssues(peerDependencyIssues, {
lockfileDir: opts.lockfileDir,
strictPeerDependencies: opts.strictPeerDependencies,
})
}
return {
newLockfile,
projects: projects.map(({ manifest, rootDir }) => ({ rootDir, manifest })),

View File

@@ -64,7 +64,6 @@ export default async function linkPackages (
symlink: boolean
skipped: Set<string>
storeController: StoreController
strictPeerDependencies: boolean
virtualStoreDir: string
wantedLockfile: Lockfile
wantedToBeSkippedPackageIds: Set<string>

View File

@@ -1,21 +1,10 @@
import path from 'path'
import PnpmError from '@pnpm/error'
import logger from '@pnpm/logger'
import scan from 'ramda/src/scan'
import {
createNodeId,
splitNodeId,
} from './nodeIdUtils'
import {
DependenciesTree,
ResolvedPackage,
} from './resolveDependencies'
import { PartialResolvedPackage, PeerDependencyIssue } from './resolvePeers'
import { PeerDependencyIssue, PeerDependencyIssueLocation } from '@pnpm/resolve-dependencies'
export default function<T extends PartialResolvedPackage> (
export default function (
peerDependencyIssues: PeerDependencyIssue[],
opts: {
dependenciesTree: DependenciesTree<T>
lockfileDir: string
strictPeerDependencies: boolean
}
@@ -44,19 +33,13 @@ export default function<T extends PartialResolvedPackage> (
}
}
function peerDependencyIssueMessage<T extends PartialResolvedPackage> (
function peerDependencyIssueMessage (
opts: {
dependenciesTree: DependenciesTree<T>
lockfileDir: string
peerDependencyIssue: PeerDependencyIssue
}
) {
const friendlyPath = nodeIdToFriendlyPath({
dependenciesTree: opts.dependenciesTree,
lockfileDir: opts.lockfileDir,
nodeId: opts.peerDependencyIssue.nodeId,
rootDir: opts.peerDependencyIssue.rootDir,
})
const friendlyPath = locationToFriendlyPath(opts.peerDependencyIssue.location)
if (opts.peerDependencyIssue.foundPeerVersion) {
return `${friendlyPath ? `${friendlyPath}: ` : ''}${packageFriendlyId(opts.peerDependencyIssue.pkg)} \
requires a peer of ${opts.peerDependencyIssue.wantedPeer.name}@${opts.peerDependencyIssue.wantedPeer.range} but version ${opts.peerDependencyIssue.foundPeerVersion} was installed.`
@@ -69,26 +52,10 @@ function packageFriendlyId (manifest: {name: string, version: string}) {
return `${manifest.name}@${manifest.version}`
}
function nodeIdToFriendlyPath<T extends PartialResolvedPackage> (
{
dependenciesTree,
lockfileDir,
nodeId,
rootDir,
}: {
dependenciesTree: DependenciesTree<T>
lockfileDir: string
nodeId: string
rootDir: string
}
) {
const parts = splitNodeId(nodeId).slice(0, -1)
const result = scan((prevNodeId, pkgId) => createNodeId(prevNodeId, pkgId), '>', parts)
.slice(2)
.map((nid) => (dependenciesTree[nid].resolvedPackage as ResolvedPackage).name)
const projectPath = path.relative(lockfileDir, rootDir)
if (projectPath) {
result.unshift(projectPath)
function locationToFriendlyPath (location: PeerDependencyIssueLocation) {
const result = location.parents.map(({ name }) => name)
if (location.projectPath) {
result.unshift(location.projectPath)
}
return result.join(' > ')
}

View File

@@ -69,7 +69,6 @@ export async function listMissingPeers (
registries: ctx.registries,
saveWorkspaceProtocol: false, // this doesn't matter in our case. We won't write changes to package.json files
storeController: opts.storeController,
strictPeerDependencies: false,
tag: 'latest',
virtualStoreDir: ctx.virtualStoreDir,
wantedLockfile: ctx.wantedLockfile,

View File

@@ -21,7 +21,6 @@ import {
} from '@pnpm/types'
import difference from 'ramda/src/difference'
import depPathToRef from './depPathToRef'
import reportPeerDependencyIssues from './reportPeerDependencyIssues'
import resolveDependencyTree, {
Importer,
LinkedDependency,
@@ -32,6 +31,8 @@ import resolveDependencyTree, {
import resolvePeers, {
GenericDependenciesGraph,
GenericDependenciesGraphNode,
PeerDependencyIssue,
PeerDependencyIssueLocation,
} from './resolvePeers'
import toResolveImporter from './toResolveImporter'
import updateLockfile from './updateLockfile'
@@ -43,6 +44,8 @@ export type DependenciesGraphNode = GenericDependenciesGraphNode & ResolvedPacka
export {
LinkedDependency,
PeerDependencyIssue,
PeerDependencyIssueLocation,
ResolvedPackage,
}
@@ -77,7 +80,6 @@ export default async function (
defaultUpdateDepth: number
preserveWorkspaceProtocol: boolean
saveWorkspaceProtocol: boolean
strictPeerDependencies: boolean
}
) {
const _toResolveImporter = toResolveImporter.bind(null, {
@@ -166,14 +168,6 @@ export default async function (
virtualStoreDir: opts.virtualStoreDir,
})
if (peerDependencyIssues.length > 0) {
reportPeerDependencyIssues(peerDependencyIssues, {
dependenciesTree,
lockfileDir: opts.lockfileDir,
strictPeerDependencies: opts.strictPeerDependencies,
})
}
for (const { id, manifest } of projectsToLink) {
for (const [alias, depPath] of Object.entries(dependenciesByProjectId[id])) {
const projectSnapshot = opts.wantedLockfile.importers[id]

View File

@@ -5,16 +5,23 @@ import { depPathToFilename } from 'dependency-path'
import { KeyValuePair } from 'ramda'
import fromPairs from 'ramda/src/fromPairs'
import isEmpty from 'ramda/src/isEmpty'
import pick from 'ramda/src/pick'
import scan from 'ramda/src/scan'
import semver from 'semver'
import {
DependenciesTree,
DependenciesTreeNode,
ResolvedPackage,
} from './resolveDependencies'
import { splitNodeId } from './nodeIdUtils'
import { createNodeId, splitNodeId } from './nodeIdUtils'
export interface PeerDependencyIssueLocation {
parents: Array<{ name: string, version: string }>
projectPath: string
}
export interface PeerDependencyIssue {
nodeId: string
location: PeerDependencyIssueLocation
pkg: PartialResolvedPackage
rootDir: string
foundPeerVersion?: string
@@ -401,7 +408,12 @@ function resolvePeers<T extends PartialResolvedPackage> (
continue
}
ctx.peerDependencyIssues.push({
nodeId: ctx.nodeId,
location: getLocationFromNodeId({
dependenciesTree: ctx.dependenciesTree,
nodeId: ctx.nodeId,
lockfileDir: ctx.lockfileDir,
rootDir: ctx.rootDir,
}),
pkg: ctx.resolvedPackage,
rootDir: ctx.rootDir,
wantedPeer: {
@@ -414,7 +426,12 @@ function resolvePeers<T extends PartialResolvedPackage> (
if (!semver.satisfies(resolved.version, peerVersionRange, { loose: true })) {
ctx.peerDependencyIssues.push({
nodeId: ctx.nodeId,
location: getLocationFromNodeId({
dependenciesTree: ctx.dependenciesTree,
nodeId: ctx.nodeId,
lockfileDir: ctx.lockfileDir,
rootDir: ctx.rootDir,
}),
pkg: ctx.resolvedPackage,
rootDir: ctx.rootDir,
foundPeerVersion: resolved.version,
@@ -430,6 +447,30 @@ function resolvePeers<T extends PartialResolvedPackage> (
return { resolvedPeers, missingPeers }
}
function getLocationFromNodeId<T> (
{
dependenciesTree,
lockfileDir,
nodeId,
rootDir,
}: {
dependenciesTree: DependenciesTree<T>
lockfileDir: string
nodeId: string
rootDir: string
}
) {
const parts = splitNodeId(nodeId).slice(0, -1)
const parents = scan((prevNodeId, pkgId) => createNodeId(prevNodeId, pkgId), '>', parts)
.slice(2)
.map((nid) => pick(['name', 'version'], dependenciesTree[nid].resolvedPackage as ResolvedPackage))
const projectPath = path.relative(lockfileDir, rootDir)
return {
projectPath,
parents,
}
}
interface ParentRefs {
[name: string]: ParentRef
}