perf: reduce memory usage (#8084)

ref #8072
This commit is contained in:
Zoltan Kochan
2024-05-16 20:55:08 +02:00
committed by GitHub
parent 34bc8f48e1
commit ef73c19f15
13 changed files with 189 additions and 202 deletions

View File

@@ -0,0 +1,6 @@
---
"@pnpm/resolve-dependencies": patch
"pnpm": patch
---
Decrease memory consumption [#8084](https://github.com/pnpm/pnpm/pull/8084).

View File

@@ -53,7 +53,6 @@
"graph-cycles": "1.2.1", "graph-cycles": "1.2.1",
"is-inner-link": "^4.0.0", "is-inner-link": "^4.0.0",
"is-subdir": "^1.2.0", "is-subdir": "^1.2.0",
"mem": "^8.1.1",
"normalize-path": "^3.0.0", "normalize-path": "^3.0.0",
"p-defer": "^3.0.0", "p-defer": "^3.0.0",
"path-exists": "^4.0.0", "path-exists": "^4.0.0",

View File

@@ -129,7 +129,7 @@ export async function resolveDependencies (
dependenciesTree, dependenciesTree,
outdatedDependencies, outdatedDependencies,
resolvedImporters, resolvedImporters,
resolvedPackagesByDepPath, resolvedPkgsById,
wantedToBeSkippedPackageIds, wantedToBeSkippedPackageIds,
appliedPatches, appliedPatches,
time, time,
@@ -301,7 +301,7 @@ export async function resolveDependencies (
// waiting till package requests are finished // waiting till package requests are finished
async function waitTillAllFetchingsFinish (): Promise<void> { async function waitTillAllFetchingsFinish (): Promise<void> {
await Promise.all(Object.values(resolvedPackagesByDepPath).map(async ({ fetching }) => { await Promise.all(Object.values(resolvedPkgsById).map(async ({ fetching }) => {
try { try {
await fetching?.() await fetching?.()
} catch {} } catch {}

View File

@@ -0,0 +1,5 @@
let nodeIdCounter = 0
export function nextNodeId (): string {
return (++nodeIdCounter).toString()
}

View File

@@ -1,22 +0,0 @@
export function nodeIdContains (nodeId: string, pkgId: string): boolean {
const pkgIds = splitNodeId(nodeId)
return pkgIds.includes(pkgId)
}
export function nodeIdContainsSequence (nodeId: string, pkgId1: string, pkgId2: string): boolean {
const pkgIds = splitNodeId(nodeId)
pkgIds.pop()
const pkg1Index = pkgIds.indexOf(pkgId1)
if (pkg1Index === -1) return false
const pkg2Index = pkgIds.lastIndexOf(pkgId2)
return pkg1Index < pkg2Index
}
export function createNodeId (parentNodeId: string, pkgId: string): string {
// using ">" as a separator because it will never be used inside a package ID
return `${parentNodeId}${pkgId}>`
}
export function splitNodeId (nodeId: string): string[] {
return nodeId.split('>').slice(1, -1)
}

View File

@@ -0,0 +1,8 @@
export function parentIdsContainSequence (pkgIds: string[], pkgId1: string, pkgId2: string): boolean {
const pkg1Index = pkgIds.indexOf(pkgId1)
if (pkg1Index === -1 || pkg1Index === pkgIds.length - 1) {
return false
}
const pkg2Index = pkgIds.lastIndexOf(pkgId2)
return pkg1Index < pkg2Index && pkg2Index !== pkgIds.length - 1
}

View File

@@ -50,12 +50,8 @@ import semver from 'semver'
import { encodePkgId } from './encodePkgId' import { encodePkgId } from './encodePkgId'
import { getNonDevWantedDependencies, type WantedDependency } from './getNonDevWantedDependencies' import { getNonDevWantedDependencies, type WantedDependency } from './getNonDevWantedDependencies'
import { safeIntersect } from './mergePeers' import { safeIntersect } from './mergePeers'
import { import { nextNodeId } from './nextNodeId'
createNodeId, import { parentIdsContainSequence } from './parentIdsContainSequence'
nodeIdContainsSequence,
nodeIdContains,
splitNodeId,
} from './nodeIdUtils'
import { hoistPeers, getHoistableOptionalPeers } from './hoistPeers' import { hoistPeers, getHoistableOptionalPeers } from './hoistPeers'
import { wantedDepIsLocallyAvailable } from './wantedDepIsLocallyAvailable' import { wantedDepIsLocallyAvailable } from './wantedDepIsLocallyAvailable'
import { replaceVersionInPref } from './replaceVersionInPref' import { replaceVersionInPref } from './replaceVersionInPref'
@@ -64,13 +60,14 @@ const dependencyResolvedLogger = logger('_dependency_resolved')
const omitDepsFields = omit(['dependencies', 'optionalDependencies', 'peerDependencies', 'peerDependenciesMeta']) const omitDepsFields = omit(['dependencies', 'optionalDependencies', 'peerDependencies', 'peerDependenciesMeta'])
export function nodeIdToParents ( export function getPkgsInfoFromIds (
nodeId: string, ids: string[],
resolvedPackagesByDepPath: ResolvedPackagesByDepPath resolvedPkgsById: ResolvedPkgsById
): Array<{ id: string, name: string, version: string }> { ): Array<{ id: string, name: string, version: string }> {
return splitNodeId(nodeId).slice(1) return ids
.map((depPath) => { .slice(1)
const { id, name, version } = resolvedPackagesByDepPath[depPath] .map((id) => {
const { name, version } = resolvedPkgsById[id]
return { id, name, version } return { id, name, version }
}) })
} }
@@ -99,7 +96,7 @@ string,
DependenciesTreeNode<T> DependenciesTreeNode<T>
> >
export type ResolvedPackagesByDepPath = Record<string, ResolvedPackage> export type ResolvedPkgsById = Record<string, ResolvedPackage>
export interface LinkedDependency { export interface LinkedDependency {
isLinkedDependency: true isLinkedDependency: true
@@ -120,12 +117,13 @@ export interface PendingNode {
resolvedPackage: ResolvedPackage resolvedPackage: ResolvedPackage
depth: number depth: number
installable: boolean installable: boolean
parentIds: string[]
} }
export interface ChildrenByParentDepPath { export interface ChildrenByParentId {
[depPath: string]: Array<{ [id: string]: Array<{
alias: string alias: string
depPath: string id: string
}> }>
} }
@@ -142,9 +140,9 @@ export interface ResolutionContext {
dryRun: boolean dryRun: boolean
forceFullResolution: boolean forceFullResolution: boolean
ignoreScripts?: boolean ignoreScripts?: boolean
resolvedPackagesByDepPath: ResolvedPackagesByDepPath resolvedPkgsById: ResolvedPkgsById
outdatedDependencies: { [pkgId: string]: string } outdatedDependencies: { [pkgId: string]: string }
childrenByParentDepPath: ChildrenByParentDepPath childrenByParentId: ChildrenByParentId
patchedDependencies?: Record<string, PatchFile> patchedDependencies?: Record<string, PatchFile>
pendingNodes: PendingNode[] pendingNodes: PendingNode[]
wantedLockfile: Lockfile wantedLockfile: Lockfile
@@ -246,7 +244,7 @@ export interface ResolvedPackage {
parentImporterIds: Set<string> parentImporterIds: Set<string>
} }
type ParentPkg = Pick<PkgAddress, 'nodeId' | 'installable' | 'depPath' | 'rootDir' | 'optional'> type ParentPkg = Pick<PkgAddress, 'nodeId' | 'installable' | 'rootDir' | 'optional' | 'pkgId'>
export type ParentPkgAliases = Record<string, PkgAddress | true> export type ParentPkgAliases = Record<string, PkgAddress | true>
@@ -254,6 +252,7 @@ export type UpdateMatchingFunction = (pkgName: string) => boolean
interface ResolvedDependenciesOptions { interface ResolvedDependenciesOptions {
currentDepth: number currentDepth: number
parentIds: string[]
parentPkg: ParentPkg parentPkg: ParentPkg
parentPkgAliases: ParentPkgAliases parentPkgAliases: ParentPkgAliases
// If the package has been updated, the dependencies // If the package has been updated, the dependencies
@@ -377,12 +376,14 @@ interface PkgAddressesByImportersWithoutPeers extends PeersResolutionResult {
pkgAddresses: Array<PkgAddress | LinkedDependency> pkgAddresses: Array<PkgAddress | LinkedDependency>
} }
export type ImporterToResolveOptions = Omit<ResolvedDependenciesOptions, 'parentPkgAliases' | 'publishedBy'>
export interface ImporterToResolve { export interface ImporterToResolve {
updatePackageManifest: boolean updatePackageManifest: boolean
preferredVersions: PreferredVersions preferredVersions: PreferredVersions
parentPkgAliases: ParentPkgAliases parentPkgAliases: ParentPkgAliases
wantedDependencies: Array<WantedDependency & { updateDepth?: number }> wantedDependencies: Array<WantedDependency & { updateDepth?: number }>
options: Omit<ResolvedDependenciesOptions, 'parentPkgAliases' | 'publishedBy'> options: ImporterToResolveOptions
} }
interface ResolveDependenciesOfImportersResult { interface ResolveDependenciesOfImportersResult {
@@ -456,7 +457,7 @@ async function resolveDependenciesOfImporters (
if (pkgAddress.updated) { if (pkgAddress.updated) {
ctx.updatedSet.add(pkgAddress.alias) ctx.updatedSet.add(pkgAddress.alias)
} }
const resolvedPackage = ctx.resolvedPackagesByDepPath[pkgAddress.depPath] const resolvedPackage = ctx.resolvedPkgsById[pkgAddress.pkgId]
if (!resolvedPackage) continue // This will happen only with linked dependencies if (!resolvedPackage) continue // This will happen only with linked dependencies
if (!Object.prototype.hasOwnProperty.call(newPreferredVersions, resolvedPackage.name)) { if (!Object.prototype.hasOwnProperty.call(newPreferredVersions, resolvedPackage.name)) {
newPreferredVersions[resolvedPackage.name] = { ...importer.preferredVersions[resolvedPackage.name] } newPreferredVersions[resolvedPackage.name] = { ...importer.preferredVersions[resolvedPackage.name] }
@@ -586,7 +587,7 @@ export async function resolveDependencies (
if (pkgAddress.updated) { if (pkgAddress.updated) {
ctx.updatedSet.add(pkgAddress.alias) ctx.updatedSet.add(pkgAddress.alias)
} }
const resolvedPackage = ctx.resolvedPackagesByDepPath[pkgAddress.depPath] const resolvedPackage = ctx.resolvedPkgsById[pkgAddress.pkgId]
if (!resolvedPackage) continue // This will happen only with linked dependencies if (!resolvedPackage) continue // This will happen only with linked dependencies
if (!Object.prototype.hasOwnProperty.call(newPreferredVersions, resolvedPackage.name)) { if (!Object.prototype.hasOwnProperty.call(newPreferredVersions, resolvedPackage.name)) {
newPreferredVersions[resolvedPackage.name] = { ...preferredVersions[resolvedPackage.name] } newPreferredVersions[resolvedPackage.name] = { ...preferredVersions[resolvedPackage.name] }
@@ -741,6 +742,7 @@ async function resolveDependenciesOfDependency (
updateMatching: options.updateMatching, updateMatching: options.updateMatching,
supportedArchitectures: options.supportedArchitectures, supportedArchitectures: options.supportedArchitectures,
updateToLatest: options.updateToLatest, updateToLatest: options.updateToLatest,
parentIds: options.parentIds,
} }
const resolveDependencyResult = await resolveDependency(extendedWantedDep.wantedDependency, ctx, resolveDependencyOpts) const resolveDependencyResult = await resolveDependency(extendedWantedDep.wantedDependency, ctx, resolveDependencyOpts)
@@ -773,6 +775,7 @@ async function resolveDependenciesOfDependency (
parentPkg: resolveDependencyResult, parentPkg: resolveDependencyResult,
dependencyLockfile: extendedWantedDep.infoFromLockfile?.dependencyLockfile, dependencyLockfile: extendedWantedDep.infoFromLockfile?.dependencyLockfile,
parentDepth: options.currentDepth, parentDepth: options.currentDepth,
parentIds: [...options.parentIds, resolveDependencyResult.pkgId],
updateDepth, updateDepth,
prefix: options.prefix, prefix: options.prefix,
updateMatching: options.updateMatching, updateMatching: options.updateMatching,
@@ -820,6 +823,7 @@ async function resolveChildren (
ctx: ResolutionContext, ctx: ResolutionContext,
{ {
parentPkg, parentPkg,
parentIds,
dependencyLockfile, dependencyLockfile,
parentDepth, parentDepth,
updateDepth, updateDepth,
@@ -828,6 +832,7 @@ async function resolveChildren (
supportedArchitectures, supportedArchitectures,
}: { }: {
parentPkg: PkgAddress parentPkg: PkgAddress
parentIds: string[]
dependencyLockfile: PackageSnapshot | undefined dependencyLockfile: PackageSnapshot | undefined
parentDepth: number parentDepth: number
updateDepth: number updateDepth: number
@@ -881,11 +886,12 @@ async function resolveChildren (
updateDepth, updateDepth,
updateMatching, updateMatching,
supportedArchitectures, supportedArchitectures,
parentIds,
} }
) )
ctx.childrenByParentDepPath[parentPkg.depPath] = pkgAddresses.map((child) => ({ ctx.childrenByParentId[parentPkg.pkgId] = pkgAddresses.map((child) => ({
alias: child.alias, alias: child.alias,
depPath: child.depPath, id: child.pkgId,
})) }))
ctx.dependenciesTree.set(parentPkg.nodeId, { ctx.dependenciesTree.set(parentPkg.nodeId, {
children: pkgAddresses.reduce((chn, child) => { children: pkgAddresses.reduce((chn, child) => {
@@ -894,7 +900,7 @@ async function resolveChildren (
}, {} as Record<string, string>), }, {} as Record<string, string>),
depth: parentDepth, depth: parentDepth,
installable: parentPkg.installable, installable: parentPkg.installable,
resolvedPackage: ctx.resolvedPackagesByDepPath[parentPkg.depPath], resolvedPackage: ctx.resolvedPkgsById[parentPkg.pkgId],
}) })
return resolvingPeers return resolvingPeers
} }
@@ -1078,6 +1084,7 @@ interface ResolveDependencyOptions {
dependencyLockfile?: PackageSnapshot dependencyLockfile?: PackageSnapshot
} }
parentPkg: ParentPkg parentPkg: ParentPkg
parentIds: string[]
parentPkgAliases: ParentPkgAliases parentPkgAliases: ParentPkgAliases
preferredVersions: PreferredVersions preferredVersions: PreferredVersions
prefix: string prefix: string
@@ -1166,7 +1173,7 @@ async function resolveDependency (
supportedArchitectures: options.supportedArchitectures, supportedArchitectures: options.supportedArchitectures,
onFetchError: (err: any) => { // eslint-disable-line onFetchError: (err: any) => { // eslint-disable-line
err.prefix = options.prefix err.prefix = options.prefix
err.pkgsStack = nodeIdToParents(options.parentPkg.nodeId, ctx.resolvedPackagesByDepPath) err.pkgsStack = getPkgsInfoFromIds(options.parentIds, ctx.resolvedPkgsById)
return err return err
}, },
updateToLatest: options.updateToLatest, updateToLatest: options.updateToLatest,
@@ -1180,21 +1187,21 @@ async function resolveDependency (
pref: wantedDependency.pref, pref: wantedDependency.pref,
version: wantedDependency.alias ? wantedDependency.pref : undefined, version: wantedDependency.alias ? wantedDependency.pref : undefined,
}, },
parents: nodeIdToParents(options.parentPkg.nodeId, ctx.resolvedPackagesByDepPath), parents: getPkgsInfoFromIds(options.parentIds, ctx.resolvedPkgsById),
prefix: options.prefix, prefix: options.prefix,
reason: 'resolution_failure', reason: 'resolution_failure',
}) })
return null return null
} }
err.prefix = options.prefix err.prefix = options.prefix
err.pkgsStack = nodeIdToParents(options.parentPkg.nodeId, ctx.resolvedPackagesByDepPath) err.pkgsStack = getPkgsInfoFromIds(options.parentIds, ctx.resolvedPkgsById)
throw err throw err
} }
dependencyResolvedLogger.debug({ dependencyResolvedLogger.debug({
resolution: pkgResponse.body.id, resolution: pkgResponse.body.id,
wanted: { wanted: {
dependentId: options.parentPkg.depPath, dependentId: options.parentPkg.pkgId,
name: wantedDependency.alias, name: wantedDependency.alias,
rawSpec: wantedDependency.pref, rawSpec: wantedDependency.pref,
}, },
@@ -1289,11 +1296,11 @@ async function resolveDependency (
// because zoo is a new parent package: // because zoo is a new parent package:
// foo > bar > qar > zoo > qar // foo > bar > qar > zoo > qar
if ( if (
nodeIdContainsSequence( parentIdsContainSequence(
options.parentPkg.nodeId, options.parentIds,
options.parentPkg.depPath, options.parentPkg.pkgId,
depPath pkgResponse.body.id
) || depPath === options.parentPkg.depPath ) || pkgResponse.body.id === options.parentPkg.pkgId
) { ) {
return null return null
} }
@@ -1342,14 +1349,12 @@ async function resolveDependency (
} }
// In case of leaf dependencies (dependencies that have no prod deps or peer deps), // In case of leaf dependencies (dependencies that have no prod deps or peer deps),
// we only ever need to analyze one leaf dep in a graph, so the nodeId can be short and stateless. // we only ever need to analyze one leaf dep in a graph, so the nodeId can be short and stateless.
const nodeId = pkgIsLeaf(pkg) const nodeId = pkgIsLeaf(pkg) ? pkgResponse.body.id : nextNodeId()
? `>${depPath}>`
: createNodeId(options.parentPkg.nodeId, depPath)
const parentIsInstallable = options.parentPkg.installable === undefined || options.parentPkg.installable const parentIsInstallable = options.parentPkg.installable === undefined || options.parentPkg.installable
const installable = parentIsInstallable && pkgResponse.body.isInstallable !== false const installable = parentIsInstallable && pkgResponse.body.isInstallable !== false
const isNew = !ctx.resolvedPackagesByDepPath[depPath] const isNew = !ctx.resolvedPkgsById[pkgResponse.body.id]
const parentImporterId = options.parentPkg.nodeId.substring(0, options.parentPkg.nodeId.indexOf('>', 1) + 1) const parentImporterId = options.parentIds[0]
let resolveChildren = false let resolveChildren = false
const currentIsOptional = wantedDependency.optional || options.parentPkg.optional const currentIsOptional = wantedDependency.optional || options.parentPkg.optional
@@ -1379,7 +1384,7 @@ async function resolveDependency (
// WARN: It is very important to keep this sync // WARN: It is very important to keep this sync
// Otherwise, deprecation messages for the same package might get written several times // Otherwise, deprecation messages for the same package might get written several times
ctx.resolvedPackagesByDepPath[depPath] = getResolvedPackage({ ctx.resolvedPkgsById[pkgResponse.body.id] = getResolvedPackage({
allowBuild: ctx.allowBuild, allowBuild: ctx.allowBuild,
dependencyLockfile: currentPkg.dependencyLockfile, dependencyLockfile: currentPkg.dependencyLockfile,
depPath, depPath,
@@ -1394,17 +1399,17 @@ async function resolveDependency (
optional: currentIsOptional, optional: currentIsOptional,
}) })
} else { } else {
ctx.resolvedPackagesByDepPath[depPath].prod = ctx.resolvedPackagesByDepPath[depPath].prod || !wantedDependency.dev && !wantedDependency.optional ctx.resolvedPkgsById[pkgResponse.body.id].prod = ctx.resolvedPkgsById[pkgResponse.body.id].prod || !wantedDependency.dev && !wantedDependency.optional
ctx.resolvedPackagesByDepPath[depPath].dev = ctx.resolvedPackagesByDepPath[depPath].dev || wantedDependency.dev ctx.resolvedPkgsById[pkgResponse.body.id].dev = ctx.resolvedPkgsById[pkgResponse.body.id].dev || wantedDependency.dev
ctx.resolvedPackagesByDepPath[depPath].optional = ctx.resolvedPackagesByDepPath[depPath].optional && currentIsOptional ctx.resolvedPkgsById[pkgResponse.body.id].optional = ctx.resolvedPkgsById[pkgResponse.body.id].optional && currentIsOptional
if (ctx.hoistPeers) { if (ctx.hoistPeers) {
resolveChildren = !ctx.missingPeersOfChildrenByPkgId[pkgResponse.body.id].missingPeersOfChildren.resolved && resolveChildren = !ctx.missingPeersOfChildrenByPkgId[pkgResponse.body.id].missingPeersOfChildren.resolved &&
!ctx.resolvedPackagesByDepPath[depPath].parentImporterIds.has(parentImporterId) !ctx.resolvedPkgsById[pkgResponse.body.id].parentImporterIds.has(parentImporterId)
ctx.resolvedPackagesByDepPath[depPath].parentImporterIds.add(parentImporterId) ctx.resolvedPkgsById[pkgResponse.body.id].parentImporterIds.add(parentImporterId)
} }
if (ctx.resolvedPackagesByDepPath[depPath].fetching == null && pkgResponse.fetching != null) { if (ctx.resolvedPkgsById[pkgResponse.body.id].fetching == null && pkgResponse.fetching != null) {
ctx.resolvedPackagesByDepPath[depPath].fetching = pkgResponse.fetching ctx.resolvedPkgsById[pkgResponse.body.id].fetching = pkgResponse.fetching
ctx.resolvedPackagesByDepPath[depPath].filesIndexFile = pkgResponse.filesIndexFile! ctx.resolvedPkgsById[pkgResponse.body.id].filesIndexFile = pkgResponse.filesIndexFile!
} }
if (ctx.dependenciesTree.has(nodeId)) { if (ctx.dependenciesTree.has(nodeId)) {
@@ -1413,9 +1418,10 @@ async function resolveDependency (
ctx.pendingNodes.push({ ctx.pendingNodes.push({
alias: wantedDependency.alias || pkg.name, alias: wantedDependency.alias || pkg.name,
depth: options.currentDepth, depth: options.currentDepth,
parentIds: options.parentIds,
installable, installable,
nodeId, nodeId,
resolvedPackage: ctx.resolvedPackagesByDepPath[depPath], resolvedPackage: ctx.resolvedPkgsById[pkgResponse.body.id],
}) })
} }
} }
@@ -1424,9 +1430,9 @@ async function resolveDependency (
? path.resolve(ctx.lockfileDir, (pkgResponse.body.resolution as DirectoryResolution).directory) ? path.resolve(ctx.lockfileDir, (pkgResponse.body.resolution as DirectoryResolution).directory)
: options.prefix : options.prefix
let missingPeersOfChildren!: MissingPeersOfChildren | undefined let missingPeersOfChildren!: MissingPeersOfChildren | undefined
if (ctx.hoistPeers && !nodeIdContains(options.parentPkg.nodeId, depPath)) { if (ctx.hoistPeers && !options.parentIds.includes(pkgResponse.body.id)) {
if (ctx.missingPeersOfChildrenByPkgId[pkgResponse.body.id]) { if (ctx.missingPeersOfChildrenByPkgId[pkgResponse.body.id]) {
if (!options.parentPkg.nodeId.startsWith(ctx.missingPeersOfChildrenByPkgId[pkgResponse.body.id].parentImporterId)) { if (options.parentIds[0] !== ctx.missingPeersOfChildrenByPkgId[pkgResponse.body.id].parentImporterId) {
missingPeersOfChildren = ctx.missingPeersOfChildrenByPkgId[pkgResponse.body.id].missingPeersOfChildren missingPeersOfChildren = ctx.missingPeersOfChildrenByPkgId[pkgResponse.body.id].missingPeersOfChildren
} }
} else { } else {
@@ -1453,7 +1459,7 @@ async function resolveDependency (
pkgId: pkgResponse.body.id, pkgId: pkgResponse.body.id,
rootDir, rootDir,
missingPeers: getMissingPeers(pkg), missingPeers: getMissingPeers(pkg),
optional: ctx.resolvedPackagesByDepPath[depPath].optional, optional: ctx.resolvedPkgsById[pkgResponse.body.id].optional,
// Next fields are actually only needed when isNew = true // Next fields are actually only needed when isNew = true
installable, installable,

View File

@@ -11,25 +11,23 @@ import {
import partition from 'ramda/src/partition' import partition from 'ramda/src/partition'
import zipObj from 'ramda/src/zipObj' import zipObj from 'ramda/src/zipObj'
import { type WantedDependency } from './getNonDevWantedDependencies' import { type WantedDependency } from './getNonDevWantedDependencies'
import { nextNodeId } from './nextNodeId'
import { parentIdsContainSequence } from './parentIdsContainSequence'
import { import {
createNodeId, type ChildrenByParentId,
nodeIdContainsSequence,
} from './nodeIdUtils'
import {
type ChildrenByParentDepPath,
type DependenciesTree, type DependenciesTree,
type LinkedDependency, type LinkedDependency,
type ImporterToResolve, type ImporterToResolve,
type ImporterToResolveOptions,
type ParentPkgAliases, type ParentPkgAliases,
type PendingNode, type PendingNode,
type PkgAddress, type PkgAddress,
resolveRootDependencies, resolveRootDependencies,
type ResolvedPackage, type ResolvedPackage,
type ResolvedPackagesByDepPath, type ResolvedPkgsById,
type ResolutionContext, type ResolutionContext,
} from './resolveDependencies' } from './resolveDependencies'
export * from './nodeIdUtils'
export type { LinkedDependency, ResolvedPackage, DependenciesTree, DependenciesTreeNode } from './resolveDependencies' export type { LinkedDependency, ResolvedPackage, DependenciesTree, DependenciesTreeNode } from './resolveDependencies'
export interface ResolvedImporters { export interface ResolvedImporters {
@@ -113,7 +111,7 @@ export interface ResolveDependencyTreeResult {
[pkgId: string]: string [pkgId: string]: string
} }
resolvedImporters: ResolvedImporters resolvedImporters: ResolvedImporters
resolvedPackagesByDepPath: ResolvedPackagesByDepPath resolvedPkgsById: ResolvedPkgsById
wantedToBeSkippedPackageIds: Set<string> wantedToBeSkippedPackageIds: Set<string>
appliedPatches: Set<string> appliedPatches: Set<string>
time?: Record<string, string> time?: Record<string, string>
@@ -130,7 +128,7 @@ export async function resolveDependencyTree<T> (
autoInstallPeersFromHighestMatch: opts.autoInstallPeersFromHighestMatch === true, autoInstallPeersFromHighestMatch: opts.autoInstallPeersFromHighestMatch === true,
allowBuild: opts.allowBuild, allowBuild: opts.allowBuild,
allowedDeprecatedVersions: opts.allowedDeprecatedVersions, allowedDeprecatedVersions: opts.allowedDeprecatedVersions,
childrenByParentDepPath: {} as ChildrenByParentDepPath, childrenByParentId: {} as ChildrenByParentId,
currentLockfile: opts.currentLockfile, currentLockfile: opts.currentLockfile,
defaultTag: opts.tag, defaultTag: opts.tag,
dependenciesTree: new Map() as DependenciesTree<ResolvedPackage>, dependenciesTree: new Map() as DependenciesTree<ResolvedPackage>,
@@ -149,7 +147,7 @@ export async function resolveDependencyTree<T> (
preferWorkspacePackages: opts.preferWorkspacePackages, preferWorkspacePackages: opts.preferWorkspacePackages,
readPackageHook: opts.hooks.readPackage, readPackageHook: opts.hooks.readPackage,
registries: opts.registries, registries: opts.registries,
resolvedPackagesByDepPath: {} as ResolvedPackagesByDepPath, resolvedPkgsById: {} as ResolvedPkgsById,
resolutionMode: opts.resolutionMode, resolutionMode: opts.resolutionMode,
skipped: wantedToBeSkippedPackageIds, skipped: wantedToBeSkippedPackageIds,
storeController: opts.storeController, storeController: opts.storeController,
@@ -170,15 +168,16 @@ export async function resolveDependencyTree<T> (
// We only need to proceed resolving every dependency // We only need to proceed resolving every dependency
// if the newly added dependency has peer dependencies. // if the newly added dependency has peer dependencies.
const proceed = importer.id === '.' || importer.hasRemovedDependencies === true || importer.wantedDependencies.some((wantedDep: any) => wantedDep.isNew) // eslint-disable-line @typescript-eslint/no-explicit-any const proceed = importer.id === '.' || importer.hasRemovedDependencies === true || importer.wantedDependencies.some((wantedDep: any) => wantedDep.isNew) // eslint-disable-line @typescript-eslint/no-explicit-any
const resolveOpts = { const resolveOpts: ImporterToResolveOptions = {
currentDepth: 0, currentDepth: 0,
parentPkg: { parentPkg: {
installable: true, installable: true,
nodeId: `>${importer.id}>`, nodeId: `>${importer.id}>`,
optional: false, optional: false,
depPath: importer.id, pkgId: importer.id,
rootDir: importer.rootDir, rootDir: importer.rootDir,
}, },
parentIds: [importer.id],
proceed, proceed,
resolvedDependencies: { resolvedDependencies: {
...projectSnapshot.dependencies, ...projectSnapshot.dependencies,
@@ -206,8 +205,9 @@ export async function resolveDependencyTree<T> (
ctx.pendingNodes.forEach((pendingNode) => { ctx.pendingNodes.forEach((pendingNode) => {
ctx.dependenciesTree.set(pendingNode.nodeId, { ctx.dependenciesTree.set(pendingNode.nodeId, {
children: () => buildTree(ctx, pendingNode.nodeId, pendingNode.resolvedPackage.id, children: () => buildTree(ctx, pendingNode.resolvedPackage.id,
ctx.childrenByParentDepPath[pendingNode.resolvedPackage.depPath], pendingNode.depth + 1, pendingNode.installable), pendingNode.parentIds,
ctx.childrenByParentId[pendingNode.resolvedPackage.id], pendingNode.depth + 1, pendingNode.installable),
depth: pendingNode.depth, depth: pendingNode.depth,
installable: pendingNode.installable, installable: pendingNode.installable,
resolvedPackage: pendingNode.resolvedPackage, resolvedPackage: pendingNode.resolvedPackage,
@@ -250,7 +250,7 @@ export async function resolveDependencyTree<T> (
dependenciesTree: ctx.dependenciesTree, dependenciesTree: ctx.dependenciesTree,
outdatedDependencies: ctx.outdatedDependencies, outdatedDependencies: ctx.outdatedDependencies,
resolvedImporters, resolvedImporters,
resolvedPackagesByDepPath: ctx.resolvedPackagesByDepPath, resolvedPkgsById: ctx.resolvedPkgsById,
wantedToBeSkippedPackageIds, wantedToBeSkippedPackageIds,
appliedPatches: ctx.appliedPatches, appliedPatches: ctx.appliedPatches,
time, time,
@@ -260,40 +260,40 @@ export async function resolveDependencyTree<T> (
function buildTree ( function buildTree (
ctx: { ctx: {
childrenByParentDepPath: ChildrenByParentDepPath childrenByParentId: ChildrenByParentId
dependenciesTree: DependenciesTree<ResolvedPackage> dependenciesTree: DependenciesTree<ResolvedPackage>
resolvedPackagesByDepPath: ResolvedPackagesByDepPath resolvedPkgsById: ResolvedPkgsById
skipped: Set<string> skipped: Set<string>
}, },
parentNodeId: string,
parentId: string, parentId: string,
children: Array<{ alias: string, depPath: string }>, parentIds: string[],
children: Array<{ alias: string, id: string }>,
depth: number, depth: number,
installable: boolean installable: boolean
): Record<string, string> { ): Record<string, string> {
const childrenNodeIds: Record<string, string> = {} const childrenNodeIds: Record<string, string> = {}
for (const child of children) { for (const child of children) {
if (child.depPath.startsWith('link:')) { if (child.id.startsWith('link:')) {
childrenNodeIds[child.alias] = child.depPath childrenNodeIds[child.alias] = child.id
continue continue
} }
if (nodeIdContainsSequence(parentNodeId, parentId, child.depPath) || parentId === child.depPath) { if (parentIdsContainSequence(parentIds, parentId, child.id) || parentId === child.id) {
continue continue
} }
const childNodeId = createNodeId(parentNodeId, child.depPath) const childNodeId = nextNodeId()
childrenNodeIds[child.alias] = childNodeId childrenNodeIds[child.alias] = childNodeId
installable = installable || !ctx.skipped.has(child.depPath) installable = installable || !ctx.skipped.has(child.id)
ctx.dependenciesTree.set(childNodeId, { ctx.dependenciesTree.set(childNodeId, {
children: () => buildTree(ctx, children: () => buildTree(ctx,
childNodeId, child.id,
child.depPath, [...parentIds, child.id],
ctx.childrenByParentDepPath[child.depPath], ctx.childrenByParentId[child.id],
depth + 1, depth + 1,
installable installable
), ),
depth, depth,
installable, installable,
resolvedPackage: ctx.resolvedPackagesByDepPath[child.depPath], resolvedPackage: ctx.resolvedPkgsById[child.id],
}) })
} }
return childrenNodeIds return childrenNodeIds

View File

@@ -10,11 +10,9 @@ import type {
PeerDependencyIssuesByProjects, PeerDependencyIssuesByProjects,
} from '@pnpm/types' } from '@pnpm/types'
import { depPathToFilename, createPeersDirSuffix, type PeerId } from '@pnpm/dependency-path' import { depPathToFilename, createPeersDirSuffix, type PeerId } from '@pnpm/dependency-path'
import memoize from 'mem'
import mapValues from 'ramda/src/map' import mapValues from 'ramda/src/map'
import partition from 'ramda/src/partition' import partition from 'ramda/src/partition'
import pick from 'ramda/src/pick' import pick from 'ramda/src/pick'
import scan from 'ramda/src/scan'
import { import {
type ChildrenMap, type ChildrenMap,
type PeerDependencies, type PeerDependencies,
@@ -24,7 +22,6 @@ import {
} from './resolveDependencies' } from './resolveDependencies'
import { type ResolvedImporters } from './resolveDependencyTree' import { type ResolvedImporters } from './resolveDependencyTree'
import { mergePeers } from './mergePeers' import { mergePeers } from './mergePeers'
import { createNodeId, splitNodeId } from './nodeIdUtils'
import { dedupeInjectedDeps } from './dedupeInjectedDeps' import { dedupeInjectedDeps } from './dedupeInjectedDeps'
export interface GenericDependenciesGraphNode { export interface GenericDependenciesGraphNode {
@@ -107,10 +104,12 @@ export async function resolvePeers<T extends PartialResolvedPackage> (
// eslint-disable-next-line no-await-in-loop // eslint-disable-next-line no-await-in-loop
const { finishing } = await resolvePeersOfChildren(directNodeIdsByAlias, pkgsByName, { const { finishing } = await resolvePeersOfChildren(directNodeIdsByAlias, pkgsByName, {
allPeerDepNames: opts.allPeerDepNames, allPeerDepNames: opts.allPeerDepNames,
getParentPkgs: {}, parentPkgsOfNode: new Map(),
dependenciesTree: opts.dependenciesTree, dependenciesTree: opts.dependenciesTree,
depGraph, depGraph,
lockfileDir: opts.lockfileDir, lockfileDir: opts.lockfileDir,
parentNodeIds: [],
parentDepPathsChain: [],
pathsByNodeId, pathsByNodeId,
pathsByNodeIdPromises, pathsByNodeIdPromises,
depPathsByPkgId, depPathsByPkgId,
@@ -271,6 +270,7 @@ function createPkgsByName<T extends PartialResolvedPackage> (
alias, alias,
node: dependenciesTree.get(directNodeIdsByAlias[alias])!, node: dependenciesTree.get(directNodeIdsByAlias[alias])!,
nodeId: directNodeIdsByAlias[alias], nodeId: directNodeIdsByAlias[alias],
parentNodeIds: [],
})) }))
) )
const _updateParentRefs = updateParentRefs.bind(null, parentRefs) const _updateParentRefs = updateParentRefs.bind(null, parentRefs)
@@ -281,6 +281,7 @@ function createPkgsByName<T extends PartialResolvedPackage> (
depth: 0, depth: 0,
version, version,
nodeId: linkedDir, nodeId: linkedDir,
parentNodeIds: [],
} }
_updateParentRefs(name, pkg) _updateParentRefs(name, pkg)
if (alias && alias !== name) { if (alias && alias !== name) {
@@ -318,14 +319,17 @@ interface ParentPkgInfo {
depth?: number depth?: number
occurrence?: number occurrence?: number
} }
type GetParentPkgs = () => Record<string, ParentPkgInfo>
type ParentPkgsOfNode = Map<string, Record<string, ParentPkgInfo>>
async function resolvePeersOfNode<T extends PartialResolvedPackage> ( async function resolvePeersOfNode<T extends PartialResolvedPackage> (
nodeId: string, nodeId: string,
parentParentPkgs: ParentRefs, parentParentPkgs: ParentRefs,
ctx: ResolvePeersContext & { ctx: ResolvePeersContext & {
allPeerDepNames: Set<string> allPeerDepNames: Set<string>
getParentPkgs: Record<string, GetParentPkgs> parentPkgsOfNode: ParentPkgsOfNode
parentNodeIds: string[]
parentDepPathsChain: string[]
dependenciesTree: DependenciesTree<T> dependenciesTree: DependenciesTree<T>
depGraph: GenericDependenciesGraph<T> depGraph: GenericDependenciesGraph<T>
virtualStoreDir: string virtualStoreDir: string
@@ -352,6 +356,7 @@ async function resolvePeersOfNode<T extends PartialResolvedPackage> (
if (typeof node.children === 'function') { if (typeof node.children === 'function') {
node.children = node.children() node.children = node.children()
} }
const parentNodeIds = [...ctx.parentNodeIds, nodeId]
const children = node.children const children = node.children
let parentPkgs: ParentRefs let parentPkgs: ParentRefs
if (Object.keys(children).length === 0) { if (Object.keys(children).length === 0) {
@@ -365,6 +370,7 @@ async function resolvePeersOfNode<T extends PartialResolvedPackage> (
alias, alias,
node: ctx.dependenciesTree.get(nodeId)!, node: ctx.dependenciesTree.get(nodeId)!,
nodeId, nodeId,
parentNodeIds,
}) })
} }
} }
@@ -398,7 +404,11 @@ async function resolvePeersOfNode<T extends PartialResolvedPackage> (
resolvedPeers: unknownResolvedPeersOfChildren, resolvedPeers: unknownResolvedPeersOfChildren,
missingPeers: missingPeersOfChildren, missingPeers: missingPeersOfChildren,
finishing, finishing,
} = await resolvePeersOfChildren(children, parentPkgs, ctx) } = await resolvePeersOfChildren(children, parentPkgs, {
...ctx,
parentNodeIds,
parentDepPathsChain: ctx.parentDepPathsChain.includes(resolvedPackage.depPath) ? ctx.parentDepPathsChain : [...ctx.parentDepPathsChain, resolvedPackage.depPath],
})
const { resolvedPeers, missingPeers } = Object.keys(resolvedPackage.peerDependencies).length === 0 const { resolvedPeers, missingPeers } = Object.keys(resolvedPackage.peerDependencies).length === 0
? { resolvedPeers: new Map<string, string>(), missingPeers: new Set<string>() } ? { resolvedPeers: new Map<string, string>(), missingPeers: new Set<string>() }
@@ -411,6 +421,7 @@ async function resolvePeersOfNode<T extends PartialResolvedPackage> (
peerDependencyIssues: ctx.peerDependencyIssues, peerDependencyIssues: ctx.peerDependencyIssues,
resolvedPackage, resolvedPackage,
rootDir: ctx.rootDir, rootDir: ctx.rootDir,
parentNodeIds,
}) })
const allResolvedPeers = unknownResolvedPeersOfChildren const allResolvedPeers = unknownResolvedPeersOfChildren
@@ -539,7 +550,7 @@ async function resolvePeersOfNode<T extends PartialResolvedPackage> (
ctx.depGraph[depPath] = { ctx.depGraph[depPath] = {
...(node.resolvedPackage as T), ...(node.resolvedPackage as T),
children: Object.assign( children: Object.assign(
getPreviouslyResolvedChildren(nodeId, ctx.dependenciesTree), getPreviouslyResolvedChildren(ctx, (node.resolvedPackage as T).depPath),
children, children,
Object.fromEntries(resolvedPeers.entries()) Object.fromEntries(resolvedPeers.entries())
), ),
@@ -558,7 +569,7 @@ async function resolvePeersOfNode<T extends PartialResolvedPackage> (
} }
function findHit<T extends PartialResolvedPackage> (ctx: { function findHit<T extends PartialResolvedPackage> (ctx: {
getParentPkgs: Record<string, GetParentPkgs> parentPkgsOfNode: ParentPkgsOfNode
peersCache: PeersCache peersCache: PeersCache
purePkgs: Set<string> purePkgs: Set<string>
pathsByNodeId: Map<string, string> pathsByNodeId: Map<string, string>
@@ -599,12 +610,12 @@ function findHit<T extends PartialResolvedPackage> (ctx: {
} }
function parentPackagesMatch (ctx: { function parentPackagesMatch (ctx: {
getParentPkgs: Record<string, GetParentPkgs> parentPkgsOfNode: ParentPkgsOfNode
purePkgs: Set<string> purePkgs: Set<string>
}, cachedNodeId: string, checkedNodeId: string): boolean { }, cachedNodeId: string, checkedNodeId: string): boolean {
const cachedParentPkgs = ctx.getParentPkgs[cachedNodeId]?.() const cachedParentPkgs = ctx.parentPkgsOfNode.get(cachedNodeId)
if (!cachedParentPkgs) return false if (!cachedParentPkgs) return false
const checkedParentPkgs = ctx.getParentPkgs[checkedNodeId]?.() const checkedParentPkgs = ctx.parentPkgsOfNode.get(checkedNodeId)
if (!checkedParentPkgs) return false if (!checkedParentPkgs) return false
if (Object.keys(cachedParentPkgs).length !== Object.keys(checkedParentPkgs).length) return false if (Object.keys(cachedParentPkgs).length !== Object.keys(checkedParentPkgs).length) return false
const maxDepth = Object.values(checkedParentPkgs) const maxDepth = Object.values(checkedParentPkgs)
@@ -640,27 +651,34 @@ function parentPkgsHaveSingleOccurrence (parentPkgs: Record<string, ParentPkgInf
// from the dependencies of the parent package. // from the dependencies of the parent package.
// So we need to merge all the children of all the parent packages with same ID as the resolved package. // So we need to merge all the children of all the parent packages with same ID as the resolved package.
// This way we get all the children that were removed, when ending cycles. // This way we get all the children that were removed, when ending cycles.
function getPreviouslyResolvedChildren<T extends PartialResolvedPackage> (nodeId: string, dependenciesTree: DependenciesTree<T>): ChildrenMap { function getPreviouslyResolvedChildren<T extends PartialResolvedPackage> (
const parentIds = splitNodeId(nodeId) {
const ownId = parentIds.pop() parentNodeIds,
parentDepPathsChain,
dependenciesTree,
}: {
parentNodeIds: string[]
parentDepPathsChain: string[]
dependenciesTree: DependenciesTree<T>
},
currentDepPath: string
): ChildrenMap {
const allChildren: ChildrenMap = {} const allChildren: ChildrenMap = {}
if (!ownId || !parentIds.includes(ownId)) return allChildren if (!currentDepPath || !parentDepPathsChain.includes(currentDepPath)) return allChildren
const nodeIdChunks = parentIds.join('>').split(`>${ownId}>`) for (let i = parentNodeIds.length - 1; i >= 0; i--) {
nodeIdChunks.pop() const parentNode = dependenciesTree.get(parentNodeIds[i])!
nodeIdChunks.reduce((accNodeId, part) => { if ((parentNode.resolvedPackage as T).depPath === currentDepPath) {
accNodeId += `>${part}>${ownId}` if (typeof parentNode.children === 'function') {
const parentNode = dependenciesTree.get(`${accNodeId}>`)! parentNode.children = parentNode.children()
if (typeof parentNode.children === 'function') { }
parentNode.children = parentNode.children() Object.assign(
allChildren,
parentNode.children
)
} }
Object.assign( }
allChildren,
parentNode.children
)
return accNodeId
}, '')
return allChildren return allChildren
} }
@@ -671,7 +689,9 @@ async function resolvePeersOfChildren<T extends PartialResolvedPackage> (
parentPkgs: ParentRefs, parentPkgs: ParentRefs,
ctx: ResolvePeersContext & { ctx: ResolvePeersContext & {
allPeerDepNames: Set<string> allPeerDepNames: Set<string>
getParentPkgs: Record<string, GetParentPkgs> parentPkgsOfNode: ParentPkgsOfNode
parentNodeIds: string[]
parentDepPathsChain: string[]
peerDependencyIssues: Pick<PeerDependencyIssues, 'bad' | 'missing'> peerDependencyIssues: Pick<PeerDependencyIssues, 'bad' | 'missing'>
peersCache: PeersCache peersCache: PeersCache
virtualStoreDir: string virtualStoreDir: string
@@ -701,24 +721,21 @@ async function resolvePeersOfChildren<T extends PartialResolvedPackage> (
const calculateDepPaths: CalculateDepPath[] = [] const calculateDepPaths: CalculateDepPath[] = []
const graph = [] const graph = []
const finishingList: FinishingResolutionPromise[] = [] const finishingList: FinishingResolutionPromise[] = []
const cachedGetParentPkgs = memoize(() => { const parentDepPaths: Record<string, ParentPkgInfo> = {}
const parentDepPaths: Record<string, ParentPkgInfo> = {} for (const [name, parentPkg] of Object.entries(parentPkgs)) {
for (const [name, parentPkg] of Object.entries(parentPkgs)) { if (!ctx.allPeerDepNames.has(name)) continue
if (!ctx.allPeerDepNames.has(name)) continue if (parentPkg.nodeId && !parentPkg.nodeId.startsWith('link:')) {
if (parentPkg.nodeId && !parentPkg.nodeId.startsWith('link:')) { parentDepPaths[name] = {
parentDepPaths[name] = { depPath: (ctx.dependenciesTree.get(parentPkg.nodeId)!.resolvedPackage as T).depPath,
depPath: (ctx.dependenciesTree.get(parentPkg.nodeId)!.resolvedPackage as T).depPath, depth: parentPkg.depth,
depth: parentPkg.depth, occurrence: parentPkg.occurrence,
occurrence: parentPkg.occurrence,
}
} else {
parentDepPaths[name] = { version: parentPkg.version }
} }
} else {
parentDepPaths[name] = { version: parentPkg.version }
} }
return parentDepPaths }
})
for (const childNodeId of nodeIds) { for (const childNodeId of nodeIds) {
ctx.getParentPkgs[childNodeId] = cachedGetParentPkgs ctx.parentPkgsOfNode.set(childNodeId, parentDepPaths)
} }
for (const childNodeId of nodeIds) { for (const childNodeId of nodeIds) {
const { const {
@@ -765,6 +782,7 @@ function _resolvePeers<T extends PartialResolvedPackage> (
lockfileDir: string lockfileDir: string
nodeId: string nodeId: string
parentPkgs: ParentRefs parentPkgs: ParentRefs
parentNodeIds: string[]
resolvedPackage: T resolvedPackage: T
dependenciesTree: DependenciesTree<T> dependenciesTree: DependenciesTree<T>
rootDir: string rootDir: string
@@ -781,11 +799,7 @@ function _resolvePeers<T extends PartialResolvedPackage> (
if (!resolved) { if (!resolved) {
missingPeers.add(peerName) missingPeers.add(peerName)
const location = getLocationFromNodeIdAndPkg({ const location = getLocationFromParentNodeIds(ctx)
dependenciesTree: ctx.dependenciesTree,
nodeId: ctx.nodeId,
pkg: ctx.resolvedPackage,
})
if (!ctx.peerDependencyIssues.missing[peerName]) { if (!ctx.peerDependencyIssues.missing[peerName]) {
ctx.peerDependencyIssues.missing[peerName] = [] ctx.peerDependencyIssues.missing[peerName] = []
} }
@@ -798,19 +812,15 @@ function _resolvePeers<T extends PartialResolvedPackage> (
} }
if (!semverUtils.satisfiesWithPrereleases(resolved.version, peerVersionRange, true)) { if (!semverUtils.satisfiesWithPrereleases(resolved.version, peerVersionRange, true)) {
const location = getLocationFromNodeIdAndPkg({ const location = getLocationFromParentNodeIds(ctx)
dependenciesTree: ctx.dependenciesTree,
nodeId: ctx.nodeId,
pkg: ctx.resolvedPackage,
})
if (!ctx.peerDependencyIssues.bad[peerName]) { if (!ctx.peerDependencyIssues.bad[peerName]) {
ctx.peerDependencyIssues.bad[peerName] = [] ctx.peerDependencyIssues.bad[peerName] = []
} }
const peerLocation = resolved.nodeId == null const peerLocation = resolved.nodeId == null
? [] ? []
: getLocationFromNodeId({ : getLocationFromParentNodeIds({
dependenciesTree: ctx.dependenciesTree, dependenciesTree: ctx.dependenciesTree,
nodeId: resolved.nodeId, parentNodeIds: resolved.parentNodeIds,
}).parents }).parents
ctx.peerDependencyIssues.bad[peerName].push({ ctx.peerDependencyIssues.bad[peerName].push({
foundVersion: resolved.version, foundVersion: resolved.version,
@@ -831,41 +841,19 @@ interface Location {
parents: ParentPackages parents: ParentPackages
} }
function getLocationFromNodeIdAndPkg<T> ( function getLocationFromParentNodeIds<T> (
{ {
dependenciesTree, dependenciesTree,
nodeId, parentNodeIds,
pkg,
}: { }: {
dependenciesTree: DependenciesTree<T> dependenciesTree: DependenciesTree<T>
nodeId: string parentNodeIds: string[]
pkg: { name: string, version: string }
} }
): Location { ): Location {
const { projectId, parents } = getLocationFromNodeId({ dependenciesTree, nodeId }) const parents = parentNodeIds
parents.push({ name: pkg.name, version: pkg.version })
return {
projectId,
parents,
}
}
function getLocationFromNodeId<T> (
{
dependenciesTree,
nodeId,
}: {
dependenciesTree: DependenciesTree<T>
nodeId: string
}
): Location {
const parts = splitNodeId(nodeId).slice(0, -1)
const parents = scan((prevNodeId, pkgId) => createNodeId(prevNodeId, pkgId), '>', parts)
.slice(2)
.map((nid) => pick(['name', 'version'], dependenciesTree.get(nid)!.resolvedPackage as ResolvedPackage)) .map((nid) => pick(['name', 'version'], dependenciesTree.get(nid)!.resolvedPackage as ResolvedPackage))
return { return {
projectId: parts[0], projectId: '.',
parents, parents,
} }
} }
@@ -881,24 +869,27 @@ interface ParentRef {
nodeId?: string nodeId?: string
alias?: string alias?: string
occurrence: number occurrence: number
parentNodeIds: string[]
} }
interface ParentPkgNode<T> { interface ParentPkgNode<T> {
alias: string alias: string
nodeId: string nodeId: string
node: DependenciesTreeNode<T> node: DependenciesTreeNode<T>
parentNodeIds: string[]
} }
function toPkgByName<T extends PartialResolvedPackage> (nodes: Array<ParentPkgNode<T>>): ParentRefs { function toPkgByName<T extends PartialResolvedPackage> (nodes: Array<ParentPkgNode<T>>): ParentRefs {
const pkgsByName: ParentRefs = {} const pkgsByName: ParentRefs = {}
const _updateParentRefs = updateParentRefs.bind(null, pkgsByName) const _updateParentRefs = updateParentRefs.bind(null, pkgsByName)
for (const { alias, node, nodeId } of nodes) { for (const { alias, node, nodeId, parentNodeIds } of nodes) {
const pkg = { const pkg = {
alias, alias,
depth: node.depth, depth: node.depth,
nodeId, nodeId,
version: node.resolvedPackage.version, version: node.resolvedPackage.version,
occurrence: 0, occurrence: 0,
parentNodeIds,
} }
_updateParentRefs(alias, pkg) _updateParentRefs(alias, pkg)
if (alias !== node.resolvedPackage.name) { if (alias !== node.resolvedPackage.name) {

View File

@@ -61,8 +61,7 @@ function toLockfileDependency (
} }
): PackageSnapshot { ): PackageSnapshot {
const lockfileResolution = toLockfileResolution( const lockfileResolution = toLockfileResolution(
{ id: pkg.id, name: pkg.name, version: pkg.version }, { name: pkg.name, version: pkg.version },
opts.depPath,
pkg.resolution, pkg.resolution,
opts.registry, opts.registry,
opts.lockfileIncludeTarballUrl opts.lockfileIncludeTarballUrl
@@ -175,11 +174,9 @@ function updateResolvedDeps (
function toLockfileResolution ( function toLockfileResolution (
pkg: { pkg: {
id: string
name: string name: string
version: string version: string
}, },
depPath: string,
resolution: Resolution, resolution: Resolution,
registry: string, registry: string,
lockfileIncludeTarballUrl?: boolean lockfileIncludeTarballUrl?: boolean

View File

@@ -1,5 +0,0 @@
import { nodeIdContainsSequence } from '../lib/nodeIdUtils'
test('nodeIdContainsSequence()', () => {
expect(nodeIdContainsSequence('>.>b>a>c>b>a>', 'a', 'b')).toBeTruthy()
})

View File

@@ -0,0 +1,5 @@
import { parentIdsContainSequence } from '../lib/parentIdsContainSequence'
test('parentIdsContainSequence()', () => {
expect(parentIdsContainSequence(['.', 'b', 'a', 'c', 'b', 'a'], 'a', 'b')).toBeTruthy()
})

3
pnpm-lock.yaml generated
View File

@@ -4307,9 +4307,6 @@ importers:
is-subdir: is-subdir:
specifier: ^1.2.0 specifier: ^1.2.0
version: 1.2.0 version: 1.2.0
mem:
specifier: ^8.1.1
version: 8.1.1
normalize-path: normalize-path:
specifier: ^3.0.0 specifier: ^3.0.0
version: 3.0.0 version: 3.0.0