@pnpm/deps.inspection.tree-builder 1101.0.7 → 1101.0.9

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/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # @pnpm/reviewing.dependencies-hierarchy
2
2
 
3
+ ## 1101.0.9
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies:
8
+ - @pnpm/config.matcher@1100.0.3
9
+ - @pnpm/lockfile.fs@1100.2.12
10
+ - @pnpm/store.cafs@1100.3.6
11
+
12
+ ## 1101.0.8
13
+
14
+ ### Patch Changes
15
+
16
+ - `pnpm list` now shows an unsaved dependency named `constructor`, and `pnpm why` no longer labels a dependency aliased `constructor` as a dev dependency.
17
+
18
+ - Updated dependencies:
19
+ - @pnpm/lockfile.fs@1100.2.11
20
+ - @pnpm/store.index@1101.0.0
21
+
3
22
  ## 1101.0.7
4
23
 
5
24
  ### Patch Changes
@@ -33,102 +33,121 @@ async function buildProjectsTrees(projectPaths, maybeOpts, linkedWalk) {
33
33
  const modulesDir = await realpathMissing(pathAbsolute(maybeOpts.modulesDir ?? 'node_modules', maybeOpts.lockfileDir));
34
34
  const modules = await readModulesManifest(modulesDir);
35
35
  const registriesByScope = normalizeRegistriesByScope(maybeOpts?.registriesByScope);
36
- const internalPnpmDir = path.join(modulesDir, '.pnpm');
37
- const currentLockfile = await readCurrentLockfile(internalPnpmDir, { ignoreIncompatible: false });
38
- const wantedLockfile = await readWantedLockfile(maybeOpts.lockfileDir, { ignoreIncompatible: false });
39
- if (projectPaths == null) {
40
- projectPaths = Object.keys(wantedLockfile?.importers ?? {})
41
- .map((id) => path.join(maybeOpts.lockfileDir, id));
42
- }
43
- const result = {};
36
+ const { currentLockfile, wantedLockfile } = await readLockfiles(modulesDir, maybeOpts.lockfileDir);
37
+ const projectDirs = projectPaths ?? getImporterDirs(wantedLockfile, maybeOpts.lockfileDir);
44
38
  const lockfileToUse = maybeOpts.checkWantedLockfileOnly ? wantedLockfile : (currentLockfile ?? wantedLockfile);
45
39
  if (!lockfileToUse) {
46
- for (const projectPath of projectPaths) {
47
- result[projectPath] = {};
48
- }
49
- return result;
40
+ return Object.fromEntries(projectDirs.map((projectPath) => [projectPath, {}]));
50
41
  }
51
- const storeDir = modules?.storeDir;
52
- const storeIndex = storeDir ? new StoreIndex(storeDir) : undefined;
53
- const opts = {
54
- depth: maybeOpts.depth || 0,
55
- excludePeerDependencies: maybeOpts.excludePeerDependencies,
56
- include: maybeOpts.include ?? {
57
- dependencies: true,
58
- devDependencies: true,
59
- optionalDependencies: true,
60
- },
61
- lockfileDir: maybeOpts.lockfileDir,
62
- checkWantedLockfileOnly: maybeOpts.checkWantedLockfileOnly,
63
- onlyProjects: maybeOpts.onlyProjects,
42
+ const storeIndex = modules?.storeDir ? new StoreIndex(modules.storeDir) : undefined;
43
+ const ctx = createHierarchyContext({
44
+ lockfile: lockfileToUse,
45
+ wantedLockfile,
46
+ projectDirs,
47
+ treeOpts: maybeOpts,
48
+ modules,
49
+ modulesDir,
64
50
  registriesByScope,
65
- registriesByPrefix: maybeOpts.registriesByPrefix,
66
- search: maybeOpts.search,
67
- showDedupedSearchMatches: maybeOpts.showDedupedSearchMatches ?? (maybeOpts.search != null),
68
- skipped: new Set(modules?.skipped ?? []),
69
- storeDir,
70
51
  storeIndex,
71
- modulesDir,
72
- virtualStoreDir: modules?.virtualStoreDir,
73
- virtualStoreDirMaxLength: modules?.virtualStoreDirMaxLength ?? maybeOpts.virtualStoreDirMaxLength,
74
- };
52
+ });
53
+ const pairs = await Promise.all(projectDirs.map(async (projectPath) => {
54
+ return [
55
+ projectPath,
56
+ await dependenciesHierarchyForPackage(ctx, projectPath),
57
+ ];
58
+ }));
59
+ storeIndex?.close();
60
+ if (ctx.onlyProjects) {
61
+ await expandLinkedProjectsOfEach(pairs, {
62
+ importers: lockfileToUse.importers,
63
+ lockfileDir: ctx.lockfileDir,
64
+ depth: ctx.depth,
65
+ treeOpts: maybeOpts,
66
+ linkedWalk,
67
+ });
68
+ }
69
+ return Object.fromEntries(pairs);
70
+ }
71
+ async function readLockfiles(modulesDir, lockfileDir) {
72
+ const installStateDir = path.join(modulesDir, '.pnpm');
73
+ const currentLockfile = await readCurrentLockfile(installStateDir, { ignoreIncompatible: false });
74
+ const wantedLockfile = await readWantedLockfile(lockfileDir, { ignoreIncompatible: false });
75
+ return { currentLockfile, wantedLockfile };
76
+ }
77
+ function getImporterDirs(wantedLockfile, lockfileDir) {
78
+ return Object.keys(wantedLockfile?.importers ?? {})
79
+ .map((id) => path.join(lockfileDir, id));
80
+ }
81
+ function createHierarchyContext(sources) {
82
+ const { lockfile, treeOpts } = sources;
83
+ const opts = createTreeOptions(sources);
75
84
  // Build the dependency graph ONCE for all importers and share a single
76
85
  // MaterializationCache so that identical subtrees are only materialized once.
77
86
  const allRootIds = [];
78
- for (const projectPath of projectPaths) {
87
+ for (const projectPath of sources.projectDirs) {
79
88
  const importerId = getLockfileImporterId(opts.lockfileDir, projectPath);
80
- if (lockfileToUse.importers[importerId]) {
89
+ if (lockfile.importers[importerId]) {
81
90
  allRootIds.push({ type: 'importer', importerId });
82
91
  }
83
92
  }
84
93
  const sharedGraph = buildDependencyGraph(allRootIds, {
85
- currentPackages: lockfileToUse.packages ?? {},
86
- importers: lockfileToUse.importers,
94
+ currentPackages: lockfile.packages ?? {},
95
+ importers: lockfile.importers,
87
96
  include: opts.include,
88
97
  lockfileDir: opts.lockfileDir,
89
98
  onlyProjects: opts.onlyProjects,
90
- peerSatisfactionEdges: getPeerSatisfactionEdgesToSkip(lockfileToUse, {
99
+ peerSatisfactionEdges: getPeerSatisfactionEdgesToSkip(lockfile, {
91
100
  include: opts.include,
92
- resolvePeersFromWorkspaceRoot: maybeOpts.resolvePeersFromWorkspaceRoot,
101
+ resolvePeersFromWorkspaceRoot: treeOpts.resolvePeersFromWorkspaceRoot,
93
102
  }),
94
103
  });
95
104
  const sharedMaterializationCache = new Map();
96
- const sharedDepTypes = detectDepTypes(lockfileToUse, maybeOpts);
97
- const ctx = {
98
- currentLockfile: lockfileToUse,
99
- wantedLockfile,
105
+ const sharedDepTypes = detectDepTypes(lockfile, treeOpts);
106
+ return {
107
+ currentLockfile: lockfile,
108
+ wantedLockfile: sources.wantedLockfile,
100
109
  ...opts,
101
110
  graph: sharedGraph,
102
111
  materializationCache: sharedMaterializationCache,
103
112
  depTypes: sharedDepTypes,
104
113
  };
105
- const getHierarchy = dependenciesHierarchyForPackage.bind(null, ctx);
106
- const pairs = await Promise.all(projectPaths.map(async (projectPath) => {
107
- return [
108
- projectPath,
109
- await getHierarchy(projectPath),
110
- ];
111
- }));
112
- storeIndex?.close();
114
+ }
115
+ function createTreeOptions({ treeOpts, modules, modulesDir, registriesByScope, storeIndex }) {
116
+ return {
117
+ depth: treeOpts.depth || 0,
118
+ excludePeerDependencies: treeOpts.excludePeerDependencies,
119
+ include: treeOpts.include ?? {
120
+ dependencies: true,
121
+ devDependencies: true,
122
+ optionalDependencies: true,
123
+ },
124
+ lockfileDir: treeOpts.lockfileDir,
125
+ checkWantedLockfileOnly: treeOpts.checkWantedLockfileOnly,
126
+ onlyProjects: treeOpts.onlyProjects,
127
+ registriesByScope,
128
+ registriesByPrefix: treeOpts.registriesByPrefix,
129
+ search: treeOpts.search,
130
+ showDedupedSearchMatches: treeOpts.showDedupedSearchMatches ?? (treeOpts.search != null),
131
+ skipped: new Set(modules?.skipped ?? []),
132
+ storeDir: modules?.storeDir,
133
+ storeIndex,
134
+ modulesDir,
135
+ virtualStoreDir: modules?.virtualStoreDir,
136
+ virtualStoreDirMaxLength: modules?.virtualStoreDirMaxLength ?? treeOpts.virtualStoreDirMaxLength,
137
+ };
138
+ }
139
+ async function expandLinkedProjectsOfEach(pairs, opts) {
140
+ const { linkedWalk, ...ctx } = opts;
141
+ // Sequential, so that the first occurrence of a linked project is the
142
+ // one expanded, as with the deduplication of a shared lockfile.
113
143
  for (const [projectPath, dependenciesHierarchy] of pairs) {
114
- result[projectPath] = dependenciesHierarchy;
115
- }
116
- if (opts.onlyProjects) {
117
- // Sequential, so that the first occurrence of a linked project is the
118
- // one expanded, as with the deduplication of a shared lockfile.
119
- for (const [projectPath, dependenciesHierarchy] of pairs) {
120
- // eslint-disable-next-line no-await-in-loop
121
- await expandLinkedProjects(dependenciesHierarchy, {
122
- importers: lockfileToUse.importers,
123
- lockfileDir: opts.lockfileDir,
124
- depth: opts.depth,
125
- treeOpts: maybeOpts,
126
- walk: { ...linkedWalk, ancestors: new Set([...linkedWalk.ancestors, projectPath]) },
127
- rewriteLinkVersionDir: projectPath,
128
- });
129
- }
144
+ // eslint-disable-next-line no-await-in-loop -- the first occurrence of a linked project must be the one expanded, so projects are expanded in order
145
+ await expandLinkedProjects(dependenciesHierarchy, {
146
+ ...ctx,
147
+ walk: { ...linkedWalk, ancestors: new Set([...linkedWalk.ancestors, projectPath]) },
148
+ rewriteLinkVersionDir: projectPath,
149
+ });
130
150
  }
131
- return result;
132
151
  }
133
152
  /**
134
153
  * Attaches the project dependencies of every linked workspace project that
@@ -139,7 +158,7 @@ async function buildProjectsTrees(projectPaths, maybeOpts, linkedWalk) {
139
158
  async function expandLinkedProjects(tree, ctx) {
140
159
  for (const field of DEPENDENCIES_FIELDS) {
141
160
  if (tree[field] != null) {
142
- // eslint-disable-next-line no-await-in-loop
161
+ // eslint-disable-next-line no-await-in-loop -- the walk is sequential so the first occurrence of a linked project is the one expanded
143
162
  tree[field] = await expandLinkedProjectNodes(tree[field], 0, ctx);
144
163
  }
145
164
  }
@@ -149,11 +168,11 @@ async function expandLinkedProjectNodes(nodes, level, ctx) {
149
168
  for (const node of nodes) {
150
169
  let expandedNode = node;
151
170
  if (node.dependencies != null) {
152
- // eslint-disable-next-line no-await-in-loop
171
+ // eslint-disable-next-line no-await-in-loop -- the walk is sequential so the first occurrence of a linked project is the one expanded
153
172
  expandedNode = keepSearched({ ...node, dependencies: await expandLinkedProjectNodes(node.dependencies, level + 1, ctx) }, ctx);
154
173
  }
155
174
  else if (!node.circular && ctx.importers[getLockfileImporterId(ctx.lockfileDir, node.path)] == null) {
156
- // eslint-disable-next-line no-await-in-loop
175
+ // eslint-disable-next-line no-await-in-loop -- the walk is sequential so the first occurrence of a linked project is the one expanded
157
176
  expandedNode = await expandLinkedProject(node, level, ctx);
158
177
  }
159
178
  if (expandedNode != null)
@@ -212,38 +231,24 @@ function rewriteLinkVersions(nodes, linkedProjectDir, rewriteLinkVersionDir) {
212
231
  async function dependenciesHierarchyForPackage(opts, projectPath) {
213
232
  const { currentLockfile, wantedLockfile } = opts;
214
233
  const importerId = getLockfileImporterId(opts.lockfileDir, projectPath);
215
- if (!currentLockfile.importers[importerId])
234
+ const projectSnapshot = currentLockfile.importers[importerId];
235
+ if (!projectSnapshot)
216
236
  return {};
217
237
  const modulesDir = opts.modulesDir && path.isAbsolute(opts.modulesDir)
218
238
  ? opts.modulesDir
219
239
  : path.join(projectPath, opts.modulesDir ?? 'node_modules');
220
- const currentPackages = currentLockfile.packages ?? {};
221
- const wantedPackages = wantedLockfile?.packages ?? {};
222
- // Build a map from alias → dependency field for post-categorization.
223
- const result = {};
224
- const fieldMap = new Map();
225
- for (const field of DEPENDENCIES_FIELDS.sort().filter(f => opts.include[f])) {
226
- result[field] = [];
227
- const fieldDeps = currentLockfile.importers[importerId][field] ?? {};
228
- for (const alias in fieldDeps) {
229
- fieldMap.set(alias, field);
230
- }
231
- }
232
- const parentId = { type: 'importer', importerId };
233
- // Materialize the tree rooted at this importer in a single getTree call.
234
- // materializeChildren handles all dedup, search, and circular detection.
240
+ const { result, fieldMap } = initDependencyFields(projectSnapshot, opts.include);
235
241
  // The depth is incremented by 1 because the importer itself is one level;
236
242
  // opts.depth controls how deep *below* the direct dependencies we go.
237
243
  const nodes = getTree({
238
244
  ...opts,
239
- currentPackages,
245
+ currentPackages: currentLockfile.packages ?? {},
240
246
  importers: currentLockfile.importers,
241
247
  rewriteLinkVersionDir: projectPath,
242
248
  maxDepth: opts.depth + 1,
243
- wantedPackages,
249
+ wantedPackages: wantedLockfile?.packages ?? {},
244
250
  modulesDir,
245
- }, parentId);
246
- // Categorize the materialized nodes into their dependency fields.
251
+ }, { type: 'importer', importerId });
247
252
  for (const node of nodes) {
248
253
  const field = fieldMap.get(node.alias);
249
254
  if (field != null) {
@@ -255,36 +260,54 @@ async function dependenciesHierarchyForPackage(opts, projectPath) {
255
260
  // graph and can't have dependency subtrees showing paths to the search target.
256
261
  // They aren't workspace projects either, which is all onlyProjects lists.
257
262
  if (!opts.search && !opts.onlyProjects) {
258
- const savedDeps = getAllDirectDependencies(currentLockfile.importers[importerId]);
259
- const unsavedDeps = ((await readModulesDir(modulesDir)) ?? []).filter((directDep) => !savedDeps[directDep]);
260
- if (unsavedDeps.length > 0)
261
- await Promise.all(unsavedDeps.map((unsavedDep) => limitUnsavedReads(async () => {
262
- let pkgPath = path.join(modulesDir, unsavedDep);
263
- let version;
264
- try {
265
- pkgPath = await resolveLinkTarget(pkgPath);
266
- version = `link:${normalizePath(path.relative(projectPath, pkgPath))}`;
267
- }
268
- catch {
269
- // if error happened. The package is not a link
270
- const pkg = await safeReadPackageJsonFromDir(pkgPath);
271
- version = pkg?.version ?? 'undefined';
272
- }
273
- const pkg = {
274
- alias: unsavedDep,
275
- isMissing: false,
276
- isPeer: false,
277
- isSkipped: false,
278
- name: unsavedDep,
279
- path: pkgPath,
280
- version,
281
- };
282
- result.unsavedDependencies = result.unsavedDependencies ?? [];
283
- result.unsavedDependencies.push(pkg);
284
- })));
263
+ await addUnsavedDependencies(result, { modulesDir, projectPath, projectSnapshot });
285
264
  }
286
265
  return result;
287
266
  }
267
+ function initDependencyFields(projectSnapshot, include) {
268
+ const result = {};
269
+ const fieldMap = new Map();
270
+ for (const field of DEPENDENCIES_FIELDS.sort().filter(f => include[f])) {
271
+ result[field] = [];
272
+ for (const alias in projectSnapshot[field] ?? {}) {
273
+ fieldMap.set(alias, field);
274
+ }
275
+ }
276
+ return { result, fieldMap };
277
+ }
278
+ async function addUnsavedDependencies(result, { modulesDir, projectPath, projectSnapshot }) {
279
+ const savedDeps = getAllDirectDependencies(projectSnapshot);
280
+ const unsavedDeps = ((await readModulesDir(modulesDir)) ?? []).filter((directDep) => !Object.hasOwn(savedDeps, directDep));
281
+ if (unsavedDeps.length === 0)
282
+ return;
283
+ await Promise.all(unsavedDeps.map((unsavedDep) => limitUnsavedReads(async () => {
284
+ const pkg = await readUnsavedDependency(unsavedDep, { modulesDir, projectPath });
285
+ result.unsavedDependencies = result.unsavedDependencies ?? [];
286
+ result.unsavedDependencies.push(pkg);
287
+ })));
288
+ }
289
+ async function readUnsavedDependency(unsavedDep, { modulesDir, projectPath }) {
290
+ let pkgPath = path.join(modulesDir, unsavedDep);
291
+ let version;
292
+ try {
293
+ pkgPath = await resolveLinkTarget(pkgPath);
294
+ version = `link:${normalizePath(path.relative(projectPath, pkgPath))}`;
295
+ }
296
+ catch {
297
+ // if error happened. The package is not a link
298
+ const pkg = await safeReadPackageJsonFromDir(pkgPath);
299
+ version = pkg?.version ?? 'undefined';
300
+ }
301
+ return {
302
+ alias: unsavedDep,
303
+ isMissing: false,
304
+ isPeer: false,
305
+ isSkipped: false,
306
+ name: unsavedDep,
307
+ path: pkgPath,
308
+ version,
309
+ };
310
+ }
288
311
  function getAllDirectDependencies(projectSnapshot) {
289
312
  return {
290
313
  ...projectSnapshot.dependencies,
@@ -17,7 +17,7 @@ interface DependencyGraphNode {
17
17
  export interface DependencyGraph {
18
18
  nodes: Map<string, DependencyGraphNode>;
19
19
  }
20
- export declare function buildDependencyGraph(rootIds: TreeNodeId[], opts: {
20
+ interface DependencyGraphOptions {
21
21
  currentPackages: PackageSnapshots;
22
22
  importers: Record<string, ProjectSnapshot>;
23
23
  include: {
@@ -29,7 +29,8 @@ export declare function buildDependencyGraph(rootIds: TreeNodeId[], opts: {
29
29
  onlyProjects?: boolean;
30
30
  /** Edges left out of the graph, from `getPeerSatisfactionEdgesToSkip`. */
31
31
  peerSatisfactionEdges?: PeerSatisfactionEdges;
32
- }): DependencyGraph;
32
+ }
33
+ export declare function buildDependencyGraph(rootIds: TreeNodeId[], opts: DependencyGraphOptions): DependencyGraph;
33
34
  /**
34
35
  * Whether an edge may lead to a project. Besides the importers of the lockfile,
35
36
  * this includes a project's `link:` dependency on a directory outside the
@@ -12,63 +12,81 @@ export function buildDependencyGraph(rootIds, opts) {
12
12
  if (visited.has(serialized))
13
13
  continue;
14
14
  visited.add(serialized);
15
- const snapshot = getSnapshot(nodeId, opts);
16
- if (!snapshot) {
17
- graph.nodes.set(serialized, { nodeId, edges: [], peers: new Set() });
18
- continue;
19
- }
20
- // For importers, only include the dependency fields the caller selected.
21
- // For packages, devDependencies don't exist in the lockfile.
22
- const deps = nodeId.type === 'importer'
23
- ? {
24
- ...(opts.include.dependencies !== false ? snapshot.dependencies : undefined),
25
- ...(opts.include.devDependencies !== false ? snapshot.devDependencies : undefined),
26
- ...(opts.include.dependencies !== false && opts.include.optionalDependencies ? snapshot.optionalDependencies : undefined),
27
- }
28
- : !opts.include.optionalDependencies
29
- ? snapshot.dependencies
30
- : {
31
- ...snapshot.dependencies,
32
- ...snapshot.optionalDependencies,
33
- };
34
- const peers = new Set(Object.keys(nodeId.type === 'package'
35
- ? (opts.currentPackages[nodeId.depPath]?.peerDependencies ?? {})
36
- : {}));
37
- const edges = [];
38
- if (deps != null) {
39
- for (const alias in deps) {
40
- const rawRef = deps[alias];
41
- // Lockfile may expose ref as string (version) or inline { version, specifier }
42
- const ref = typeof rawRef === 'string'
43
- ? rawRef
44
- : rawRef?.version;
45
- if (ref == null)
46
- continue;
47
- if (nodeId.type === 'package' && isPeerSatisfactionEdge(opts.peerSatisfactionEdges, nodeId.depPath, alias))
48
- continue;
49
- const targetNodeId = getTreeNodeChildId({
50
- parentId: nodeId,
51
- dep: { alias, ref },
52
- lockfileDir: opts.lockfileDir,
53
- importers: opts.importers,
54
- });
55
- const target = targetNodeId != null
56
- ? { id: serializeTreeNodeId(targetNodeId), nodeId: targetNodeId }
57
- : undefined;
58
- const edge = { alias, ref, target };
59
- if (opts.onlyProjects && !isProjectEdge(nodeId, edge)) {
60
- continue;
61
- }
62
- edges.push(edge);
63
- if (target && !visited.has(target.id)) {
64
- queue.push(target.nodeId);
65
- }
15
+ const graphNode = createGraphNode(nodeId, opts);
16
+ for (const { target } of graphNode.edges) {
17
+ if (target && !visited.has(target.id)) {
18
+ queue.push(target.nodeId);
66
19
  }
67
20
  }
68
- graph.nodes.set(serialized, { nodeId, edges, peers });
21
+ graph.nodes.set(serialized, graphNode);
69
22
  }
70
23
  return graph;
71
24
  }
25
+ function createGraphNode(nodeId, opts) {
26
+ const snapshot = getSnapshot(nodeId, opts);
27
+ if (!snapshot) {
28
+ return { nodeId, edges: [], peers: new Set() };
29
+ }
30
+ // For importers, only include the dependency fields the caller selected.
31
+ // For packages, devDependencies don't exist in the lockfile.
32
+ const deps = nodeId.type === 'importer'
33
+ ? selectImporterDependencies(snapshot, opts.include)
34
+ : selectPackageDependencies(snapshot, opts.include);
35
+ const peers = new Set(Object.keys(nodeId.type === 'package'
36
+ ? (opts.currentPackages[nodeId.depPath]?.peerDependencies ?? {})
37
+ : {}));
38
+ return { nodeId, edges: createEdges(nodeId, deps, opts), peers };
39
+ }
40
+ function selectImporterDependencies(snapshot, include) {
41
+ return {
42
+ ...(include.dependencies !== false ? snapshot.dependencies : undefined),
43
+ ...(include.devDependencies !== false ? snapshot.devDependencies : undefined),
44
+ ...(include.dependencies !== false && include.optionalDependencies ? snapshot.optionalDependencies : undefined),
45
+ };
46
+ }
47
+ function selectPackageDependencies(snapshot, include) {
48
+ if (!include.optionalDependencies)
49
+ return snapshot.dependencies;
50
+ return {
51
+ ...snapshot.dependencies,
52
+ ...snapshot.optionalDependencies,
53
+ };
54
+ }
55
+ function createEdges(nodeId, deps, opts) {
56
+ const edges = [];
57
+ if (deps == null)
58
+ return edges;
59
+ for (const alias in deps) {
60
+ const edge = createEdge(nodeId, { alias, rawRef: deps[alias] }, opts);
61
+ if (edge == null)
62
+ continue;
63
+ if (opts.onlyProjects && !isProjectEdge(nodeId, edge)) {
64
+ continue;
65
+ }
66
+ edges.push(edge);
67
+ }
68
+ return edges;
69
+ }
70
+ function createEdge(nodeId, { alias, rawRef }, opts) {
71
+ // Lockfile may expose ref as string (version) or inline { version, specifier }
72
+ const ref = typeof rawRef === 'string'
73
+ ? rawRef
74
+ : rawRef?.version;
75
+ if (ref == null)
76
+ return undefined;
77
+ if (nodeId.type === 'package' && isPeerSatisfactionEdge(opts.peerSatisfactionEdges, nodeId.depPath, alias))
78
+ return undefined;
79
+ const targetNodeId = getTreeNodeChildId({
80
+ parentId: nodeId,
81
+ dep: { alias, ref },
82
+ lockfileDir: opts.lockfileDir,
83
+ importers: opts.importers,
84
+ });
85
+ const target = targetNodeId != null
86
+ ? { id: serializeTreeNodeId(targetNodeId), nodeId: targetNodeId }
87
+ : undefined;
88
+ return { alias, ref, target };
89
+ }
72
90
  /**
73
91
  * Whether an edge may lead to a project. Besides the importers of the lockfile,
74
92
  * this includes a project's `link:` dependency on a directory outside the
@@ -28,7 +28,12 @@ export interface ImporterInfo {
28
28
  name: string;
29
29
  version: string;
30
30
  }
31
- export declare function buildDependentsTree(packages: string[], projectPaths: string[], opts: {
31
+ type NameFormatter = (info: {
32
+ name: string;
33
+ version: string;
34
+ manifest: DependencyManifest;
35
+ }) => string | undefined;
36
+ interface BuildDependentsTreeOptions {
32
37
  lockfileDir: string;
33
38
  include?: {
34
39
  [field in DependenciesField]?: boolean;
@@ -39,10 +44,8 @@ export declare function buildDependentsTree(packages: string[], projectPaths: st
39
44
  finders?: Finder[];
40
45
  importerInfoMap: Map<string, ImporterInfo>;
41
46
  lockfile: LockfileObject;
42
- nameFormatter?: (info: {
43
- name: string;
44
- version: string;
45
- manifest: DependencyManifest;
46
- }) => string | undefined;
47
+ nameFormatter?: NameFormatter;
47
48
  resolvePeersFromWorkspaceRoot?: boolean;
48
- }): Promise<DependentsTree[]>;
49
+ }
50
+ export declare function buildDependentsTree(packages: string[], projectPaths: string[], opts: BuildDependentsTreeOptions): Promise<DependentsTree[]>;
51
+ export {};