diff --git a/.changeset/gentle-peaches-cross.md b/.changeset/gentle-peaches-cross.md new file mode 100644 index 0000000000..e70b44c8a5 --- /dev/null +++ b/.changeset/gentle-peaches-cross.md @@ -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. diff --git a/packages/core/src/install/index.ts b/packages/core/src/install/index.ts index 390bd8e27a..ad068b2a89 100644 --- a/packages/core/src/install/index.ts +++ b/packages/core/src/install/index.ts @@ -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 })), diff --git a/packages/core/src/install/link.ts b/packages/core/src/install/link.ts index a61cf84074..9ba21ecb14 100644 --- a/packages/core/src/install/link.ts +++ b/packages/core/src/install/link.ts @@ -64,7 +64,6 @@ export default async function linkPackages ( symlink: boolean skipped: Set storeController: StoreController - strictPeerDependencies: boolean virtualStoreDir: string wantedLockfile: Lockfile wantedToBeSkippedPackageIds: Set diff --git a/packages/resolve-dependencies/src/reportPeerDependencyIssues.ts b/packages/core/src/install/reportPeerDependencyIssues.ts similarity index 56% rename from packages/resolve-dependencies/src/reportPeerDependencyIssues.ts rename to packages/core/src/install/reportPeerDependencyIssues.ts index a789783410..b342d48165 100644 --- a/packages/resolve-dependencies/src/reportPeerDependencyIssues.ts +++ b/packages/core/src/install/reportPeerDependencyIssues.ts @@ -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 ( +export default function ( peerDependencyIssues: PeerDependencyIssue[], opts: { - dependenciesTree: DependenciesTree lockfileDir: string strictPeerDependencies: boolean } @@ -44,19 +33,13 @@ export default function ( } } -function peerDependencyIssueMessage ( +function peerDependencyIssueMessage ( opts: { - dependenciesTree: DependenciesTree 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 ( - { - dependenciesTree, - lockfileDir, - nodeId, - rootDir, - }: { - dependenciesTree: DependenciesTree - 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(' > ') } diff --git a/packages/core/src/listMissingPeers.ts b/packages/core/src/listMissingPeers.ts index 10bf6e6779..ba41fa2350 100644 --- a/packages/core/src/listMissingPeers.ts +++ b/packages/core/src/listMissingPeers.ts @@ -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, diff --git a/packages/resolve-dependencies/src/index.ts b/packages/resolve-dependencies/src/index.ts index 95f3f8d710..c073701d3e 100644 --- a/packages/resolve-dependencies/src/index.ts +++ b/packages/resolve-dependencies/src/index.ts @@ -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] diff --git a/packages/resolve-dependencies/src/resolvePeers.ts b/packages/resolve-dependencies/src/resolvePeers.ts index 8bfcf3a02e..948387afd6 100644 --- a/packages/resolve-dependencies/src/resolvePeers.ts +++ b/packages/resolve-dependencies/src/resolvePeers.ts @@ -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 ( 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 ( 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 ( return { resolvedPeers, missingPeers } } +function getLocationFromNodeId ( + { + dependenciesTree, + lockfileDir, + nodeId, + rootDir, + }: { + dependenciesTree: DependenciesTree + 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 }