@pnpm/installing.deps-resolver 1100.1.5 → 1100.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/hoistPeers.js CHANGED
@@ -62,7 +62,8 @@ export function getHoistableOptionalPeers(allMissingOptionalPeers, allPreferredV
62
62
  if (!allPreferredVersions[missingOptionalPeerName])
63
63
  continue;
64
64
  let maxSatisfyingVersion;
65
- for (const [version, specType] of Object.entries(allPreferredVersions[missingOptionalPeerName])) {
65
+ for (const [version, selector] of Object.entries(allPreferredVersions[missingOptionalPeerName])) {
66
+ const specType = typeof selector === 'string' ? selector : selector.selectorType;
66
67
  if (specType === 'version' &&
67
68
  ranges.every(range => semver.satisfies(version, range)) &&
68
69
  (!maxSatisfyingVersion || semver.gt(version, maxSatisfyingVersion))) {
package/lib/index.js CHANGED
@@ -84,7 +84,14 @@ export async function resolveDependencies(importers, opts) {
84
84
  }
85
85
  return {
86
86
  binsDir: project.binsDir,
87
+ declaredDirectDependencies: new Set([
88
+ ...Object.keys(project.manifest == null ? {} : getAllDependenciesFromManifest(project.manifest)),
89
+ ...project.wantedDependencies.flatMap(({ alias, isNew }) => isNew && alias != null ? [alias] : []),
90
+ ]),
87
91
  directNodeIdsByAlias: resolvedImporter.directNodeIdsByAlias,
92
+ explicitlyRequestedDirectDependencies: new Set(project.wantedDependencies.flatMap(({ alias, bareSpecifier, isNew, prevSpecifier, updateSpec }) => alias != null && (isNew === true || updateSpec === true || (prevSpecifier != null && bareSpecifier !== prevSpecifier))
93
+ ? [alias]
94
+ : [])),
88
95
  id: project.id,
89
96
  linkedDependencies: resolvedImporter.linkedDependencies,
90
97
  manifest: project.manifest,
@@ -93,7 +100,7 @@ export async function resolveDependencies(importers, opts) {
93
100
  topParents,
94
101
  };
95
102
  }));
96
- const { dependenciesGraph, dependenciesByProjectId, peerDependencyIssuesByProjects, } = await resolvePeers({
103
+ const peerResolutionOpts = {
97
104
  allPeerDepNames,
98
105
  dependenciesTree,
99
106
  dedupePeerDependents: opts.dedupePeerDependents,
@@ -107,7 +114,19 @@ export async function resolveDependencies(importers, opts) {
107
114
  resolvedImporters,
108
115
  peersSuffixMaxLength: opts.peersSuffixMaxLength,
109
116
  workspaceProjectIds: new Set([...opts.allProjectIds, ...Object.keys(opts.wantedLockfile.importers)]),
110
- });
117
+ };
118
+ const initiallyResolvedPeers = await resolvePeers(peerResolutionOpts);
119
+ // A second pass reuses the peer contexts already recorded in the lockfile so a
120
+ // writable install does not rewrite dependency instances whose locked provider
121
+ // is still valid and present. It can only differ from the first pass for nodes
122
+ // that carry a locked peer context, so it is skipped when none do (e.g. a fresh
123
+ // install) to avoid resolving peers twice for no benefit.
124
+ const { dependenciesGraph, dependenciesByProjectId, peerDependencyIssuesByProjects, } = treeHasLockedPeerContexts(dependenciesTree)
125
+ ? await resolvePeers({
126
+ ...peerResolutionOpts,
127
+ resolvedPeerProviderPaths: initiallyResolvedPeers.pathsByNodeId,
128
+ })
129
+ : initiallyResolvedPeers;
111
130
  const linkedDependenciesByProjectId = {};
112
131
  await Promise.all(projectsToResolve.map(async (project, index) => {
113
132
  const resolvedImporter = resolvedImporters[project.id];
@@ -237,6 +256,13 @@ export async function resolveDependencies(importers, opts) {
237
256
  resolutionPolicyViolations,
238
257
  };
239
258
  }
259
+ function treeHasLockedPeerContexts(dependenciesTree) {
260
+ for (const node of dependenciesTree.values()) {
261
+ if (node.lockedPeerContext != null)
262
+ return true;
263
+ }
264
+ return false;
265
+ }
240
266
  function addDirectDependenciesToLockfile(newManifest, projectSnapshot, linkedPackages, directDependencies, excludeLinksFromLockfile) {
241
267
  const newProjectSnapshot = {
242
268
  dependencies: {},
@@ -4,7 +4,7 @@ import { type PatchGroupRecord } from '@pnpm/patching.config';
4
4
  import type { PatchInfo } from '@pnpm/patching.types';
5
5
  import { type DirectoryResolution, type PkgResolutionId, type PreferredVersions, type Resolution, type ResolutionPolicyViolation, type WorkspacePackages } from '@pnpm/resolving.resolver-base';
6
6
  import type { PackageResponse, PkgRequestFetchResult, StoreController } from '@pnpm/store.controller-types';
7
- import type { AllowBuild, AllowedDeprecatedVersions, PackageManifest, PackageVersionPolicy, PinnedVersion, PkgIdWithPatchHash, ReadPackageHook, Registries, SupportedArchitectures, TrustPolicy } from '@pnpm/types';
7
+ import type { AllowBuild, AllowedDeprecatedVersions, DepPath, PackageManifest, PackageVersionPolicy, PinnedVersion, PkgIdWithPatchHash, ReadPackageHook, Registries, SupportedArchitectures, TrustPolicy } from '@pnpm/types';
8
8
  import { type WantedDependency } from './getNonDevWantedDependencies.js';
9
9
  import { type NodeId } from './nextNodeId.js';
10
10
  import type { CatalogLookupMetadata } from './resolveDependencyTree.js';
@@ -20,6 +20,9 @@ export interface ChildrenMap {
20
20
  export type DependenciesTreeNode<T> = {
21
21
  children: (() => ChildrenMap) | ChildrenMap;
22
22
  installable: boolean;
23
+ dependencyNamesWhoseCurrentProviderMustWin?: Set<string>;
24
+ lockedPeerContext?: LockedPeerContext;
25
+ previousDepPath?: DepPath;
23
26
  } & ({
24
27
  resolvedPackage: T & {
25
28
  name: string;
@@ -56,6 +59,8 @@ export interface PendingNode {
56
59
  resolvedPackage: ResolvedPackage;
57
60
  depth: number;
58
61
  installable: boolean;
62
+ lockedPeerContext?: LockedPeerContext;
63
+ previousDepPath?: DepPath;
59
64
  parentIds: PkgResolutionId[];
60
65
  }
61
66
  export interface ChildrenByParentId {
@@ -153,6 +158,8 @@ export interface PkgAddress extends PkgAddressOrLinkBase {
153
158
  missingPeersOfChildren?: MissingPeersOfChildren;
154
159
  publishedAt?: string;
155
160
  saveCatalogName?: string;
161
+ lockedPeerContext?: LockedPeerContext;
162
+ previousDepPath?: DepPath;
156
163
  }
157
164
  export type PkgAddressOrLink = PkgAddress | LinkedDependency;
158
165
  export interface PeerDependency {
@@ -192,7 +199,7 @@ export interface ResolvedPackage {
192
199
  libc?: string[];
193
200
  };
194
201
  }
195
- type ParentPkg = Pick<PkgAddress, 'nodeId' | 'installable' | 'rootDir' | 'optional' | 'pkgId' | 'resolvedVia'>;
202
+ type ParentPkg = Pick<PkgAddress, 'nodeId' | 'installable' | 'rootDir' | 'optional' | 'pkgId' | 'resolvedVia' | 'lockedPeerContext' | 'previousDepPath'>;
196
203
  export type ParentPkgAliases = Record<string, PkgAddress | true>;
197
204
  export type UpdateMatchingFunction = (pkgName: string, version?: string) => boolean;
198
205
  interface ResolvedDependenciesOptions {
@@ -245,9 +252,14 @@ export declare function resolveDependencies(ctx: ResolutionContext, preferredVer
245
252
  updateDepth?: number;
246
253
  }>, options: ResolvedDependenciesOptions): Promise<ResolvedDependenciesResult>;
247
254
  export declare function createNodeIdForLinkedLocalPkg(lockfileDir: string, pkgDir: string): NodeId;
255
+ export interface LockedPeerContext {
256
+ [peerName: string]: DepPath;
257
+ }
248
258
  type InfoFromLockfile = {
259
+ depPath: DepPath;
249
260
  pkgId: PkgResolutionId;
250
261
  dependencyLockfile?: PackageSnapshot;
262
+ lockedPeerContext?: LockedPeerContext;
251
263
  name?: string;
252
264
  version?: string;
253
265
  resolution?: Resolution;
@@ -505,6 +505,10 @@ async function resolveDependenciesOfDependency(ctx, preferredVersions, options,
505
505
  });
506
506
  return { resolveDependencyResult };
507
507
  }
508
+ if (update === false && extendedWantedDep.infoFromLockfile != null) {
509
+ resolveDependencyResult.previousDepPath = extendedWantedDep.infoFromLockfile.depPath;
510
+ resolveDependencyResult.lockedPeerContext = extendedWantedDep.infoFromLockfile.lockedPeerContext;
511
+ }
508
512
  if (!resolveDependencyResult.isNew) {
509
513
  return {
510
514
  resolveDependencyResult,
@@ -600,6 +604,17 @@ async function resolveChildren(ctx, { parentPkg, parentIds, dependencyLockfile,
600
604
  }, {}),
601
605
  depth: parentDepth,
602
606
  installable: parentPkg.installable,
607
+ dependencyNamesWhoseCurrentProviderMustWin: new Set(pkgAddresses
608
+ .filter((child) => {
609
+ const previousRef = dependencyLockfile?.dependencies?.[child.alias] ??
610
+ dependencyLockfile?.optionalDependencies?.[child.alias];
611
+ if (previousRef == null || child.isLinkedDependency)
612
+ return true;
613
+ return child.previousDepPath !== dp.refToRelative(previousRef, child.alias);
614
+ })
615
+ .map(({ alias }) => alias)),
616
+ lockedPeerContext: parentPkg.lockedPeerContext,
617
+ previousDepPath: parentPkg.previousDepPath,
603
618
  resolvedPackage: ctx.resolvedPkgsById[parentPkg.pkgId],
604
619
  });
605
620
  return resolvingPeers;
@@ -678,6 +693,18 @@ function referenceSatisfiesWantedSpec(opts, wantedDep, preferredRef) {
678
693
  }
679
694
  return semver.satisfies(version, wantedDep.bareSpecifier, true);
680
695
  }
696
+ function getLockedPeerContext(dependencyLockfile) {
697
+ if (dependencyLockfile.peerDependencies == null)
698
+ return undefined;
699
+ const lockedPeerContext = {};
700
+ for (const peerName of Object.keys(dependencyLockfile.peerDependencies)) {
701
+ const ref = dependencyLockfile.dependencies?.[peerName] ?? dependencyLockfile.optionalDependencies?.[peerName];
702
+ const depPath = ref == null ? null : dp.refToRelative(ref, peerName);
703
+ if (depPath != null)
704
+ lockedPeerContext[peerName] = depPath;
705
+ }
706
+ return Object.keys(lockedPeerContext).length === 0 ? undefined : lockedPeerContext;
707
+ }
681
708
  function getInfoFromLockfile(lockfile, registries, reference, alias) {
682
709
  if (!reference || !alias) {
683
710
  return undefined;
@@ -688,6 +715,7 @@ function getInfoFromLockfile(lockfile, registries, reference, alias) {
688
715
  }
689
716
  let dependencyLockfile = lockfile.packages?.[depPath];
690
717
  if (dependencyLockfile != null) {
718
+ const lockedPeerContext = getLockedPeerContext(dependencyLockfile);
691
719
  if ((dependencyLockfile.peerDependencies != null) && (dependencyLockfile.dependencies != null)) {
692
720
  // This is done to guarantee that the dependency will be relinked with the
693
721
  // up-to-date peer dependencies
@@ -705,9 +733,11 @@ function getInfoFromLockfile(lockfile, registries, reference, alias) {
705
733
  }
706
734
  const { name, version, nonSemverVersion } = nameVerFromPkgSnapshot(depPath, dependencyLockfile);
707
735
  return {
736
+ depPath,
708
737
  name,
709
738
  version,
710
739
  dependencyLockfile,
740
+ lockedPeerContext,
711
741
  pkgId: nonSemverVersion ?? `${name}@${version}`,
712
742
  // resolution may not exist if lockfile is broken, and an unexpected error will be thrown
713
743
  // if resolution does not exist, return undefined so it can be autofixed later
@@ -717,6 +747,7 @@ function getInfoFromLockfile(lockfile, registries, reference, alias) {
717
747
  else {
718
748
  const parsed = dp.parse(depPath);
719
749
  return {
750
+ depPath,
720
751
  pkgId: parsed.nonSemverVersion ?? (parsed.name && parsed.version ? `${parsed.name}@${parsed.version}` : depPath), // Does it make sense to set pkgId when we're not sure?
721
752
  };
722
753
  }
@@ -1027,6 +1058,8 @@ async function resolveDependency(wantedDependency, ctx, options) {
1027
1058
  depth: options.currentDepth,
1028
1059
  parentIds: options.parentIds,
1029
1060
  installable,
1061
+ lockedPeerContext: currentPkg.lockedPeerContext,
1062
+ previousDepPath: currentPkg.depPath,
1030
1063
  nodeId,
1031
1064
  resolvedPackage: ctx.resolvedPkgsById[pkgResponse.body.id],
1032
1065
  });
@@ -132,6 +132,8 @@ export async function resolveDependencyTree(importers, opts) {
132
132
  children: () => buildTree(ctx, pendingNode.resolvedPackage.id, pendingNode.parentIds, ctx.childrenByParentId[pendingNode.resolvedPackage.id], pendingNode.depth + 1, pendingNode.installable),
133
133
  depth: pendingNode.depth,
134
134
  installable: pendingNode.installable,
135
+ lockedPeerContext: pendingNode.lockedPeerContext,
136
+ previousDepPath: pendingNode.previousDepPath,
135
137
  resolvedPackage: pendingNode.resolvedPackage,
136
138
  });
137
139
  }
@@ -30,6 +30,8 @@ export interface GenericDependenciesGraphWithResolvedChildren<T extends PartialR
30
30
  }
31
31
  export interface ProjectToResolve {
32
32
  directNodeIdsByAlias: Map<string, NodeId>;
33
+ declaredDirectDependencies?: Set<string>;
34
+ explicitlyRequestedDirectDependencies?: Set<string>;
33
35
  topParents: Array<{
34
36
  name: string;
35
37
  version: string;
@@ -53,8 +55,10 @@ export declare function resolvePeers<T extends PartialResolvedPackage>(opts: {
53
55
  resolvedImporters: ResolvedImporters;
54
56
  peersSuffixMaxLength: number;
55
57
  workspaceProjectIds: Set<string>;
58
+ resolvedPeerProviderPaths?: Map<NodeId, DepPath>;
56
59
  }): Promise<{
57
60
  dependenciesGraph: GenericDependenciesGraphWithResolvedChildren<T>;
58
61
  dependenciesByProjectId: DependenciesByProjectId;
59
62
  peerDependencyIssuesByProjects: PeerDependencyIssuesByProjects;
63
+ pathsByNodeId: Map<NodeId, DepPath>;
60
64
  }>;
@@ -1,5 +1,5 @@
1
1
  import path from 'node:path';
2
- import { createPeerDepGraphHash, depPathToFilename } from '@pnpm/deps.path';
2
+ import { createPeerDepGraphHash, depPathToFilename, parseDepPath } from '@pnpm/deps.path';
3
3
  import * as semverUtils from '@yarnpkg/core/semverUtils';
4
4
  import { analyzeGraph } from 'graph-cycles';
5
5
  import pDefer, {} from 'p-defer';
@@ -13,13 +13,31 @@ export async function resolvePeers(opts) {
13
13
  const pathsByNodeId = new Map();
14
14
  const pathsByNodeIdPromises = new Map();
15
15
  const depPathsByPkgId = new Map();
16
+ const nodeIdsByPreviousDepPath = opts.resolvedPeerProviderPaths == null
17
+ ? new Map()
18
+ : getNodeIdsByPreviousDepPath(opts.dependenciesTree);
16
19
  const _createPkgsByName = createPkgsByName.bind(null, opts.dependenciesTree);
17
- const rootPkgsByName = opts.resolvePeersFromWorkspaceRoot ? getRootPkgsByName(opts.dependenciesTree, opts.projects) : {};
20
+ const workspaceRootProject = opts.resolvePeersFromWorkspaceRoot && opts.projects.length > 1
21
+ ? opts.projects.find(({ id }) => id === '.')
22
+ : undefined;
23
+ const rootPkgsByName = workspaceRootProject == null ? {} : _createPkgsByName(workspaceRootProject);
18
24
  const peerDependencyIssuesByProjects = {};
19
25
  const finishingList = [];
20
26
  const peersCache = new Map();
21
27
  const purePkgs = new Set();
22
- for (const { directNodeIdsByAlias, topParents, rootDir, id } of opts.projects) {
28
+ for (const { directNodeIdsByAlias, declaredDirectDependencies, explicitlyRequestedDirectDependencies, topParents, rootDir, id } of opts.projects) {
29
+ const currentProviderSources = [{
30
+ directNodeIdsByAlias,
31
+ declaredDirectDependencies: declaredDirectDependencies ?? new Set(),
32
+ explicitlyRequestedDirectDependencies: explicitlyRequestedDirectDependencies ?? new Set(),
33
+ }];
34
+ if (workspaceRootProject != null && workspaceRootProject.id !== id) {
35
+ currentProviderSources.push({
36
+ directNodeIdsByAlias: workspaceRootProject.directNodeIdsByAlias,
37
+ declaredDirectDependencies: workspaceRootProject.declaredDirectDependencies ?? new Set(),
38
+ explicitlyRequestedDirectDependencies: workspaceRootProject.explicitlyRequestedDirectDependencies ?? new Set(),
39
+ });
40
+ }
23
41
  const peerDependencyIssues = { bad: {}, missing: {} };
24
42
  const pkgsByName = Object.fromEntries(Object.entries({
25
43
  ...rootPkgsByName,
@@ -42,6 +60,9 @@ export async function resolvePeers(opts) {
42
60
  pathsByNodeId,
43
61
  pathsByNodeIdPromises,
44
62
  depPathsByPkgId,
63
+ nodeIdsByPreviousDepPath,
64
+ resolvedPeerProviderPaths: opts.resolvedPeerProviderPaths,
65
+ currentProviderSources,
45
66
  peersCache,
46
67
  peerDependencyIssues,
47
68
  purePkgs,
@@ -104,6 +125,7 @@ export async function resolvePeers(opts) {
104
125
  dependenciesGraph: depGraphWithResolvedChildren,
105
126
  dependenciesByProjectId,
106
127
  peerDependencyIssuesByProjects,
128
+ pathsByNodeId,
107
129
  };
108
130
  }
109
131
  function nodeDepsCount(node) {
@@ -177,10 +199,6 @@ function isCompatibleAndHasMoreDeps(depGraph, depPath1, depPath2) {
177
199
  }
178
200
  return true;
179
201
  }
180
- function getRootPkgsByName(dependenciesTree, projects) {
181
- const rootProject = projects.length > 1 ? projects.find(({ id }) => id === '.') : null;
182
- return rootProject == null ? {} : createPkgsByName(dependenciesTree, rootProject);
183
- }
184
202
  function createPkgsByName(dependenciesTree, { directNodeIdsByAlias, topParents }) {
185
203
  const parentRefs = toPkgByName(Array.from(directNodeIdsByAlias.entries())
186
204
  .map(([alias, nodeId]) => ({
@@ -244,7 +262,8 @@ async function resolvePeersOfNode(currentAlias, nodeId, parentParentPkgs, ctx) {
244
262
  const _parentPkgsMatch = parentPkgsMatch.bind(null, ctx.dependenciesTree);
245
263
  for (const [newParentPkgName, newParentPkg] of Object.entries(newParentPkgs)) {
246
264
  if (parentPkgs[newParentPkgName]) {
247
- if (!_parentPkgsMatch(parentPkgs[newParentPkgName], newParentPkg)) {
265
+ if (!_parentPkgsMatch(parentPkgs[newParentPkgName], newParentPkg) ||
266
+ inheritedParentPkgBreaksPeerDiamond(ctx, parentPkgs, parentPkgs[newParentPkgName], newParentPkg, children)) {
248
267
  newParentPkg.occurrence = parentPkgs[newParentPkgName].occurrence + 1;
249
268
  parentPkgs[newParentPkgName] = newParentPkg;
250
269
  }
@@ -254,6 +273,46 @@ async function resolvePeersOfNode(currentAlias, nodeId, parentParentPkgs, ctx) {
254
273
  }
255
274
  }
256
275
  }
276
+ if (node.lockedPeerContext != null && ctx.resolvedPeerProviderPaths != null) {
277
+ for (const [peerName, previousPeerDepPath] of Object.entries(node.lockedPeerContext)) {
278
+ const peerNodeId = ctx.nodeIdsByPreviousDepPath.get(previousPeerDepPath);
279
+ const peerDependency = resolvedPackage.peerDependencies[peerName];
280
+ if (peerNodeId == null || peerDependency == null)
281
+ continue;
282
+ if (ctx.resolvedPeerProviderPaths?.get(peerNodeId) !== previousPeerDepPath)
283
+ continue;
284
+ // Only pin providers that have no peer context of their own. A provider
285
+ // whose locked depPath carries a peer suffix is itself context-dependent
286
+ // — and self-referential for cyclic peers — so reusing it can re-expand
287
+ // cyclic suffixes and break the deterministic resolution that
288
+ // https://github.com/pnpm/pnpm/issues/8155 established. Stable leaf
289
+ // providers are the ones worth pinning; anything else falls back to
290
+ // fresh resolution.
291
+ if (parseDepPath(previousPeerDepPath).peerDepGraphHash !== '')
292
+ continue;
293
+ // A provider that already resolved to a different path this pass no longer
294
+ // matches its locked context; pinning it would leave pathsByNodeId
295
+ // pointing at a depPath that was never added to the graph.
296
+ const currentPeerDepPath = ctx.pathsByNodeId.get(peerNodeId);
297
+ if (currentPeerDepPath != null && currentPeerDepPath !== previousPeerDepPath)
298
+ continue;
299
+ if (hasCurrentPeerProviderThatMustWin(peerName, parentPkgs, ctx))
300
+ continue;
301
+ const lockedPeer = toPkgByName([{
302
+ alias: peerName,
303
+ node: ctx.dependenciesTree.get(peerNodeId),
304
+ nodeId: peerNodeId,
305
+ parentNodeIds,
306
+ }])[peerName];
307
+ if (!semverUtils.satisfiesWithPrereleases(lockedPeer.version, peerDependency.version.replace(/^workspace:/, ''), true))
308
+ continue;
309
+ const peerPathPromise = ctx.pathsByNodeIdPromises.get(peerNodeId) ?? pDefer();
310
+ ctx.pathsByNodeIdPromises.set(peerNodeId, peerPathPromise);
311
+ ctx.pathsByNodeId.set(peerNodeId, previousPeerDepPath);
312
+ peerPathPromise.resolve(previousPeerDepPath);
313
+ parentPkgs[peerName] = lockedPeer;
314
+ }
315
+ }
257
316
  const hit = findHit(ctx, parentPkgs, resolvedPackage.pkgIdWithPatchHash);
258
317
  if (hit != null) {
259
318
  for (const [peerName, { range: wantedRange, optional }] of hit.missingPeers.entries()) {
@@ -439,6 +498,40 @@ async function resolvePeersOfNode(currentAlias, nodeId, parentParentPkgs, ctx) {
439
498
  }
440
499
  }
441
500
  }
501
+ function getNodeIdsByPreviousDepPath(dependenciesTree) {
502
+ const nodeIdsByPreviousDepPath = new Map();
503
+ for (const [nodeId, node] of dependenciesTree.entries()) {
504
+ if (node.previousDepPath != null && !nodeIdsByPreviousDepPath.has(node.previousDepPath)) {
505
+ nodeIdsByPreviousDepPath.set(node.previousDepPath, nodeId);
506
+ }
507
+ }
508
+ return nodeIdsByPreviousDepPath;
509
+ }
510
+ function hasCurrentPeerProviderThatMustWin(peerName, parentPkgs, ctx) {
511
+ const peerNodeId = parentPkgs[peerName]?.nodeId;
512
+ if (peerNodeId == null)
513
+ return false;
514
+ for (const source of ctx.currentProviderSources) {
515
+ for (const [alias, directNodeId] of source.directNodeIdsByAlias) {
516
+ if (directNodeId === peerNodeId &&
517
+ (alias !== peerName ||
518
+ source.explicitlyRequestedDirectDependencies.has(alias) ||
519
+ (source.declaredDirectDependencies.has(alias) &&
520
+ ctx.dependenciesTree.get(peerNodeId)?.previousDepPath == null)))
521
+ return true;
522
+ }
523
+ }
524
+ for (const parentNodeId of ctx.parentNodeIds) {
525
+ const parentNode = ctx.dependenciesTree.get(parentNodeId);
526
+ if (parentNode == null)
527
+ continue;
528
+ const children = typeof parentNode.children === 'function' ? parentNode.children() : parentNode.children;
529
+ parentNode.children = children;
530
+ if ([...(parentNode.dependencyNamesWhoseCurrentProviderMustWin ?? [])].some((alias) => children[alias] === peerNodeId))
531
+ return true;
532
+ }
533
+ return false;
534
+ }
442
535
  function parentPkgsMatch(dependenciesTree, currentParentPkg, newParentPkg) {
443
536
  if (currentParentPkg.version !== newParentPkg.version ||
444
537
  currentParentPkg.alias !== newParentPkg.alias) {
@@ -452,6 +545,59 @@ function parentPkgsMatch(dependenciesTree, currentParentPkg, newParentPkg) {
452
545
  return true;
453
546
  return currentParentResolvedPkg.name === newParentResolvedPkg.name;
454
547
  }
548
+ // A package that is both inherited from an ancestor and present among the
549
+ // current node's own children is normally not duplicated: the inherited
550
+ // instance is reused (see https://github.com/pnpm/pnpm/issues/8370). That reuse
551
+ // is unsafe when a sibling peer-depends both this package and one of this
552
+ // package's own peer dependencies. The sibling must see a single, consistent
553
+ // instance of that shared peer, but the inherited instance resolved it in a
554
+ // different context. In that case the node's own child has to be used instead.
555
+ // See https://github.com/pnpm/pnpm/issues/12079
556
+ function inheritedParentPkgBreaksPeerDiamond(ctx, parentPkgs, inheritedParentPkg, ownChildParentPkg, children) {
557
+ if (inheritedParentPkg.nodeId == null || ownChildParentPkg.nodeId == null)
558
+ return false;
559
+ if (inheritedParentPkg.nodeId === ownChildParentPkg.nodeId)
560
+ return false;
561
+ const inheritedContext = ctx.parentPkgsOfNode.get(inheritedParentPkg.nodeId);
562
+ if (inheritedContext == null)
563
+ return false;
564
+ const parentPkg = ctx.dependenciesTree.get(ownChildParentPkg.nodeId)?.resolvedPackage;
565
+ if (parentPkg == null)
566
+ return false;
567
+ const conflictingPeers = new Set();
568
+ for (const peerName of Object.keys(parentPkg.peerDependencies)) {
569
+ if (!ctx.allPeerDepNames.has(peerName))
570
+ continue;
571
+ const inheritedPeer = inheritedContext[peerName];
572
+ const currentPeer = parentPkgs[peerName];
573
+ if (inheritedPeer == null || currentPeer == null)
574
+ continue;
575
+ if (parentPeerDiffers(ctx.dependenciesTree, currentPeer, inheritedPeer)) {
576
+ conflictingPeers.add(peerName);
577
+ }
578
+ }
579
+ if (conflictingPeers.size === 0)
580
+ return false;
581
+ for (const childNodeId of Object.values(children)) {
582
+ const childPeerDependencies = ctx.dependenciesTree.get(childNodeId)?.resolvedPackage?.peerDependencies;
583
+ if (childPeerDependencies == null || childPeerDependencies[parentPkg.name] == null)
584
+ continue;
585
+ for (const peerName of conflictingPeers) {
586
+ if (childPeerDependencies[peerName] != null)
587
+ return true;
588
+ }
589
+ }
590
+ return false;
591
+ }
592
+ function parentPeerDiffers(dependenciesTree, currentPeer, inheritedPeer) {
593
+ if (inheritedPeer.pkgIdWithPatchHash != null) {
594
+ if (currentPeer.nodeId == null || (typeof currentPeer.nodeId === 'string' && currentPeer.nodeId.startsWith('link:'))) {
595
+ return true;
596
+ }
597
+ return dependenciesTree.get(currentPeer.nodeId)?.resolvedPackage?.pkgIdWithPatchHash !== inheritedPeer.pkgIdWithPatchHash;
598
+ }
599
+ return currentPeer.version !== inheritedPeer.version;
600
+ }
455
601
  function findHit(ctx, parentPkgs, pkgIdWithPatchHash) {
456
602
  const cacheItems = ctx.peersCache.get(pkgIdWithPatchHash);
457
603
  if (!cacheItems)
@@ -25,7 +25,19 @@ export function updateLockfile({ dependenciesGraph, lockfile, prefix, registries
25
25
  return pruneSharedLockfile(lockfile, { warn, dependenciesGraph });
26
26
  }
27
27
  function toLockfileDependency(pkg, opts) {
28
- const lockfileResolution = toLockfileResolution({ name: pkg.name, version: pkg.version }, pkg.resolution, opts.registry, opts.lockfileIncludeTarballUrl);
28
+ let lockfileResolution = toLockfileResolution({ name: pkg.name, version: pkg.version }, pkg.resolution, opts.registry, opts.lockfileIncludeTarballUrl);
29
+ if ('tarball' in lockfileResolution &&
30
+ lockfileResolution.integrity == null &&
31
+ lockfileResolution.type === undefined) {
32
+ const prevResolution = opts.prevSnapshot?.resolution;
33
+ if (prevResolution != null &&
34
+ 'tarball' in prevResolution &&
35
+ prevResolution.type === undefined &&
36
+ prevResolution.tarball === lockfileResolution.tarball &&
37
+ prevResolution.integrity != null) {
38
+ lockfileResolution = { ...lockfileResolution, integrity: prevResolution.integrity };
39
+ }
40
+ }
29
41
  const newResolvedDeps = updateResolvedDeps(opts.updatedDeps, opts.depGraph);
30
42
  const newResolvedOptionalDeps = updateResolvedDeps(opts.updatedOptionalDeps, opts.depGraph);
31
43
  const result = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pnpm/installing.deps-resolver",
3
- "version": "1100.1.5",
3
+ "version": "1100.2.0",
4
4
  "description": "Resolves dependency graph of a package",
5
5
  "keywords": [
6
6
  "pnpm",
@@ -8,7 +8,10 @@
8
8
  ],
9
9
  "license": "MIT",
10
10
  "funding": "https://opencollective.com/pnpm",
11
- "repository": "https://github.com/pnpm/pnpm/tree/main/installing/deps-resolver",
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "https://github.com/pnpm/pnpm/tree/main/installing/deps-resolver"
14
+ },
12
15
  "homepage": "https://github.com/pnpm/pnpm/tree/main/installing/deps-resolver#readme",
13
16
  "bugs": {
14
17
  "url": "https://github.com/pnpm/pnpm/issues"
@@ -32,7 +35,7 @@
32
35
  "normalize-path": "^3.0.0",
33
36
  "p-defer": "^4.0.1",
34
37
  "path-exists": "^5.0.0",
35
- "promise-share": "^2.0.0",
38
+ "promise-share": "^2.0.1",
36
39
  "ramda": "npm:@pnpm/ramda@0.28.1",
37
40
  "rename-overwrite": "^7.0.1",
38
41
  "safe-promise-defer": "^2.0.0",
@@ -41,29 +44,29 @@
41
44
  "validate-npm-package-name": "7.0.2",
42
45
  "version-selector-type": "^3.0.0",
43
46
  "@pnpm/catalogs.types": "1100.0.0",
44
- "@pnpm/config.version-policy": "1100.1.2",
45
47
  "@pnpm/catalogs.resolver": "1100.0.0",
48
+ "@pnpm/config.version-policy": "1100.1.3",
46
49
  "@pnpm/constants": "1100.0.0",
47
- "@pnpm/deps.graph-hasher": "1100.2.2",
48
- "@pnpm/deps.path": "1100.0.5",
49
- "@pnpm/core-loggers": "1100.1.2",
50
- "@pnpm/deps.peer-range": "1100.0.1",
51
- "@pnpm/fetching.pick-fetcher": "1100.0.9",
50
+ "@pnpm/core-loggers": "1100.1.3",
51
+ "@pnpm/deps.path": "1100.0.6",
52
+ "@pnpm/deps.graph-hasher": "1100.2.3",
52
53
  "@pnpm/error": "1100.0.0",
53
- "@pnpm/hooks.types": "1100.0.9",
54
- "@pnpm/lockfile.pruner": "1100.0.8",
55
- "@pnpm/lockfile.preferred-versions": "1100.0.12",
56
- "@pnpm/lockfile.types": "1100.0.8",
57
- "@pnpm/patching.config": "1100.0.5",
54
+ "@pnpm/deps.peer-range": "1100.0.1",
55
+ "@pnpm/fetching.pick-fetcher": "1100.0.10",
56
+ "@pnpm/lockfile.types": "1100.0.9",
57
+ "@pnpm/patching.config": "1100.0.6",
58
+ "@pnpm/lockfile.preferred-versions": "1100.0.13",
59
+ "@pnpm/pkg-manifest.utils": "1100.2.2",
60
+ "@pnpm/lockfile.utils": "1100.0.11",
61
+ "@pnpm/resolving.resolver-base": "1100.4.0",
62
+ "@pnpm/pkg-manifest.reader": "1100.0.6",
58
63
  "@pnpm/patching.types": "1100.0.0",
59
- "@pnpm/lockfile.utils": "1100.0.10",
60
- "@pnpm/pkg-manifest.reader": "1100.0.5",
61
- "@pnpm/resolving.npm-resolver": "1101.4.0",
62
- "@pnpm/pkg-manifest.utils": "1100.2.1",
63
- "@pnpm/resolving.resolver-base": "1100.3.1",
64
- "@pnpm/types": "1101.2.0",
64
+ "@pnpm/hooks.types": "1100.0.10",
65
+ "@pnpm/lockfile.pruner": "1100.0.9",
65
66
  "@pnpm/workspace.spec-parser": "1100.0.0",
66
- "@pnpm/store.controller-types": "1100.1.2"
67
+ "@pnpm/types": "1101.3.0",
68
+ "@pnpm/resolving.npm-resolver": "1101.5.0",
69
+ "@pnpm/store.controller-types": "1100.1.3"
67
70
  },
68
71
  "peerDependencies": {
69
72
  "@pnpm/logger": "^1001.0.1"
@@ -74,8 +77,8 @@
74
77
  "@types/ramda": "0.31.1",
75
78
  "@types/semver": "7.7.1",
76
79
  "@types/validate-npm-package-name": "^4.0.2",
77
- "@pnpm/installing.deps-resolver": "1100.1.5",
78
- "@pnpm/logger": "1100.0.0"
80
+ "@pnpm/logger": "1100.0.0",
81
+ "@pnpm/installing.deps-resolver": "1100.2.0"
79
82
  },
80
83
  "engines": {
81
84
  "node": ">=22.13"