@pnpm/deps.inspection.tree-builder 1101.0.6 → 1101.0.8

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,37 @@
1
1
  # @pnpm/reviewing.dependencies-hierarchy
2
2
 
3
+ ## 1101.0.8
4
+
5
+ ### Patch Changes
6
+
7
+ - `pnpm list` now shows an unsaved dependency named `constructor`, and `pnpm why` no longer labels a dependency aliased `constructor` as a dev dependency.
8
+
9
+ - Updated dependencies:
10
+ - @pnpm/lockfile.fs@1100.2.11
11
+ - @pnpm/store.index@1101.0.0
12
+
13
+ ## 1101.0.7
14
+
15
+ ### Patch Changes
16
+
17
+ - `pnpm install --dev` and `pnpm fetch --dev` now install the optional dependencies of devDependencies, such as the platform binaries of Biome and oxlint. The project's own `optionalDependencies` are still skipped [#9678](https://github.com/pnpm/pnpm/issues/9678).
18
+
19
+ - `pnpm install` no longer fails when a package from the registry declares a `file:` dependency on a directory inside itself, such as `"@types/css-tree": "file:./typings/css-tree"`. pnpm links that dependency to the directory inside the package, as npm and Yarn do. The lockfile records it as `link:<root>/typings/css-tree` [#9141](https://github.com/pnpm/pnpm/issues/9141).
20
+
21
+ - `pnpm list` now shows the correct path of a `link:` dependency that points to a directory on another drive on Windows. The path used to be appended to the project directory, such as `C:\project\D:\lib`, and `pnpm list --long` could not show the package's details [#10362](https://github.com/pnpm/pnpm/issues/10362).
22
+
23
+ - Updated dependencies:
24
+ - @pnpm/deps.path@1101.0.5
25
+ - @pnpm/fs.read-modules-dir@1100.0.3
26
+ - @pnpm/installing.modules-yaml@1101.0.5
27
+ - @pnpm/lockfile.detect-dep-types@1100.0.26
28
+ - @pnpm/lockfile.fs@1100.2.10
29
+ - @pnpm/lockfile.peer-edges@1100.0.1
30
+ - @pnpm/lockfile.utils@1102.1.5
31
+ - @pnpm/pkg-manifest.reader@1100.0.21
32
+ - @pnpm/store.cafs@1100.3.5
33
+ - @pnpm/store.index@1100.3.3
34
+
3
35
  ## 1101.0.6
4
36
 
5
37
  ### 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 internalPnpmDir = path.join(modulesDir, '.pnpm');
73
+ const currentLockfile = await readCurrentLockfile(internalPnpmDir, { 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;
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
+ });
115
150
  }
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
- }
130
- }
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,37 +231,26 @@ 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 };
240
+ const { result, fieldMap } = initDependencyFields(projectSnapshot, opts.include);
233
241
  // Materialize the tree rooted at this importer in a single getTree call.
234
242
  // materializeChildren handles all dedup, search, and circular detection.
235
243
  // The depth is incremented by 1 because the importer itself is one level;
236
244
  // opts.depth controls how deep *below* the direct dependencies we go.
237
245
  const nodes = getTree({
238
246
  ...opts,
239
- currentPackages,
247
+ currentPackages: currentLockfile.packages ?? {},
240
248
  importers: currentLockfile.importers,
241
249
  rewriteLinkVersionDir: projectPath,
242
250
  maxDepth: opts.depth + 1,
243
- wantedPackages,
251
+ wantedPackages: wantedLockfile?.packages ?? {},
244
252
  modulesDir,
245
- }, parentId);
253
+ }, { type: 'importer', importerId });
246
254
  // Categorize the materialized nodes into their dependency fields.
247
255
  for (const node of nodes) {
248
256
  const field = fieldMap.get(node.alias);
@@ -255,36 +263,55 @@ async function dependenciesHierarchyForPackage(opts, projectPath) {
255
263
  // graph and can't have dependency subtrees showing paths to the search target.
256
264
  // They aren't workspace projects either, which is all onlyProjects lists.
257
265
  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
- })));
266
+ await addUnsavedDependencies(result, { modulesDir, projectPath, projectSnapshot });
285
267
  }
286
268
  return result;
287
269
  }
270
+ function initDependencyFields(projectSnapshot, include) {
271
+ // Build a map from alias → dependency field for post-categorization.
272
+ const result = {};
273
+ const fieldMap = new Map();
274
+ for (const field of DEPENDENCIES_FIELDS.sort().filter(f => include[f])) {
275
+ result[field] = [];
276
+ for (const alias in projectSnapshot[field] ?? {}) {
277
+ fieldMap.set(alias, field);
278
+ }
279
+ }
280
+ return { result, fieldMap };
281
+ }
282
+ async function addUnsavedDependencies(result, { modulesDir, projectPath, projectSnapshot }) {
283
+ const savedDeps = getAllDirectDependencies(projectSnapshot);
284
+ const unsavedDeps = ((await readModulesDir(modulesDir)) ?? []).filter((directDep) => !Object.hasOwn(savedDeps, directDep));
285
+ if (unsavedDeps.length === 0)
286
+ return;
287
+ await Promise.all(unsavedDeps.map((unsavedDep) => limitUnsavedReads(async () => {
288
+ const pkg = await readUnsavedDependency(unsavedDep, { modulesDir, projectPath });
289
+ result.unsavedDependencies = result.unsavedDependencies ?? [];
290
+ result.unsavedDependencies.push(pkg);
291
+ })));
292
+ }
293
+ async function readUnsavedDependency(unsavedDep, { modulesDir, projectPath }) {
294
+ let pkgPath = path.join(modulesDir, unsavedDep);
295
+ let version;
296
+ try {
297
+ pkgPath = await resolveLinkTarget(pkgPath);
298
+ version = `link:${normalizePath(path.relative(projectPath, pkgPath))}`;
299
+ }
300
+ catch {
301
+ // if error happened. The package is not a link
302
+ const pkg = await safeReadPackageJsonFromDir(pkgPath);
303
+ version = pkg?.version ?? 'undefined';
304
+ }
305
+ return {
306
+ alias: unsavedDep,
307
+ isMissing: false,
308
+ isPeer: false,
309
+ isSkipped: false,
310
+ name: unsavedDep,
311
+ path: pkgPath,
312
+ version,
313
+ };
314
+ }
288
315
  function getAllDirectDependencies(projectSnapshot) {
289
316
  return {
290
317
  ...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.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 {};