@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/lib/getTree.js CHANGED
@@ -12,11 +12,6 @@ export function getTree(opts, parentId) {
12
12
  ancestors,
13
13
  };
14
14
  const result = materializeChildren(ctx, parentId, opts.maxDepth, opts.parentDir);
15
- // Mark circular back-edges. materializeChildren truncates dependencies
16
- // at cycle boundaries but does not set the `circular` flag, so that cached
17
- // subtrees stay context-independent. fixCircularRefs walks the final tree
18
- // and adds `circular: true` wherever a node's path matches an ancestor.
19
- //
20
15
  // Seed the ancestors with parentDir (the filesystem path of parentId) so
21
16
  // that back-edges to the root of this subtree are detected — the root
22
17
  // itself does not appear as a node in the tree, only its children do.
@@ -54,14 +49,21 @@ function materializeChildren(ctx, parentId, maxDepth, parentDir) {
54
49
  if (!graphNode) {
55
50
  throw new Error(`Node ${parentSerialized} not found in the dependency graph`);
56
51
  }
57
- const childTreeMaxDepth = maxDepth - 1;
58
- const linkedPathBaseDir = parentId.type === 'importer'
59
- ? path.join(ctx.lockfileDir, parentId.importerId)
60
- : ctx.lockfileDir;
61
- const resultDependencies = [];
62
- let resultCount = 0;
63
- let resultHasSearchMatch = false;
64
- const resultSearchMessages = ctx.showDedupedSearchMatches ? [] : undefined;
52
+ const parent = {
53
+ id: parentId,
54
+ dir: parentDir,
55
+ peers: graphNode.peers,
56
+ childTreeMaxDepth: maxDepth - 1,
57
+ linkedPathBaseDir: parentId.type === 'importer'
58
+ ? path.join(ctx.lockfileDir, parentId.importerId)
59
+ : ctx.lockfileDir,
60
+ };
61
+ const result = {
62
+ nodes: [],
63
+ count: 0,
64
+ hasSearchMatch: false,
65
+ searchMessages: ctx.showDedupedSearchMatches ? [] : undefined,
66
+ };
65
67
  // Sort edges by alias so that deduplication is deterministic:
66
68
  // the alphabetically-first dependency always gets fully expanded.
67
69
  const sortedEdges = [...graphNode.edges].sort((a, b) => lexCompare(a.alias, b.alias));
@@ -69,132 +71,146 @@ function materializeChildren(ctx, parentId, maxDepth, parentDir) {
69
71
  if (ctx.onlyProjects && !isProjectEdge(parentId, edge)) {
70
72
  continue;
71
73
  }
72
- const { pkgInfo: packageInfo, readManifest } = getPkgInfo({
73
- ...ctx,
74
- alias: edge.alias,
75
- ref: edge.ref,
76
- peers: graphNode.peers,
77
- linkedPathBaseDir,
78
- parentDir,
79
- });
80
- // A project linked through its publish directory is listed at its own
81
- // directory.
82
- if (ctx.onlyProjects && edge.target?.nodeId.type === 'importer') {
83
- packageInfo.path = path.join(ctx.lockfileDir, edge.target.nodeId.importerId);
84
- }
85
- const searchMatch = ctx.search?.({
86
- alias: edge.alias,
87
- name: packageInfo.name,
88
- version: packageInfo.version,
89
- readManifest,
90
- });
91
- let newEntry = null;
92
- let childCount = 0;
93
- let dedupedHasSearchMatch = false;
94
- let dedupedSearchMessages = [];
95
- if (edge.target == null) {
96
- // External link or unresolvable — no traversal possible. With
97
- // onlyProjects, this is a link to a project outside the lockfile, which
98
- // buildDependenciesTree walks and prunes for the search afterwards.
99
- if (ctx.search == null || searchMatch || ctx.onlyProjects) {
100
- newEntry = packageInfo;
101
- }
102
- else {
103
- continue;
104
- }
105
- }
106
- else {
107
- let dependencies;
108
- let childHasSearchMatch = false;
109
- let childSearchMessages = [];
110
- let dedupedCount;
111
- const circular = ctx.ancestors.has(edge.target.id);
112
- if (circular) {
113
- dependencies = [];
114
- }
115
- else {
116
- const cacheKey = materializeCacheKey(edge.target.id, childTreeMaxDepth);
117
- const cached = ctx.materializationCache.get(cacheKey);
118
- if (cached !== undefined) {
119
- // This subtree was already returned to a parent elsewhere in
120
- // the output tree — elide it to avoid repeating the same nodes.
121
- dependencies = [];
122
- if (cached.count > 0) {
123
- dedupedCount = cached.count;
124
- }
125
- if (ctx.showDedupedSearchMatches) {
126
- dedupedHasSearchMatch = cached.hasSearchMatch;
127
- dedupedSearchMessages = cached.searchMessages;
128
- }
129
- }
130
- else {
131
- ctx.ancestors.add(edge.target.id);
132
- const childResult = materializeChildren(ctx, edge.target.nodeId, childTreeMaxDepth, packageInfo.path);
133
- ctx.ancestors.delete(edge.target.id);
134
- dependencies = childResult.nodes;
135
- childCount = childResult.count;
136
- childHasSearchMatch = childResult.hasSearchMatch;
137
- childSearchMessages = childResult.searchMessages;
138
- // Always cache — even results with circular truncations.
139
- ctx.materializationCache.set(cacheKey, {
140
- count: childCount,
141
- hasSearchMatch: childHasSearchMatch,
142
- searchMessages: childSearchMessages,
143
- });
144
- }
145
- if (childHasSearchMatch || dedupedHasSearchMatch) {
146
- resultHasSearchMatch = true;
147
- }
148
- resultSearchMessages?.push(...childSearchMessages, ...dedupedSearchMessages);
149
- }
150
- if (dependencies.length > 0) {
151
- newEntry = {
152
- ...packageInfo,
153
- dependencies,
154
- };
155
- }
156
- else if (ctx.search == null || searchMatch || dedupedHasSearchMatch) {
157
- newEntry = packageInfo;
158
- }
159
- else {
160
- continue;
161
- }
162
- if (dedupedCount != null) {
163
- newEntry.deduped = true;
164
- newEntry.dedupedDependenciesCount = dedupedCount;
165
- }
166
- if (edge.target.nodeId.type === 'package') {
167
- const peerHash = peersSuffixHashFromDepPath(edge.target.nodeId.depPath);
168
- if (peerHash != null) {
169
- newEntry.peersSuffixHash = peerHash;
170
- }
171
- }
172
- }
173
- if (searchMatch) {
174
- newEntry.searched = true;
175
- resultHasSearchMatch = true;
176
- if (typeof searchMatch === 'string') {
177
- newEntry.searchMessage = searchMatch;
178
- resultSearchMessages?.push(searchMatch);
179
- }
74
+ materializeEdge(ctx, { parent, edge, result });
75
+ }
76
+ return {
77
+ count: result.count,
78
+ hasSearchMatch: result.hasSearchMatch,
79
+ nodes: result.nodes,
80
+ searchMessages: result.searchMessages ?? [],
81
+ };
82
+ }
83
+ function materializeEdge(ctx, { parent, edge, result }) {
84
+ const { pkgInfo: packageInfo, readManifest } = getPkgInfo({
85
+ ...ctx,
86
+ alias: edge.alias,
87
+ ref: edge.ref,
88
+ peers: parent.peers,
89
+ linkedPathBaseDir: parent.linkedPathBaseDir,
90
+ parentDir: parent.dir,
91
+ });
92
+ // A project linked through its publish directory is listed at its own
93
+ // directory.
94
+ if (ctx.onlyProjects && edge.target?.nodeId.type === 'importer') {
95
+ packageInfo.path = path.join(ctx.lockfileDir, edge.target.nodeId.importerId);
96
+ }
97
+ const searchMatch = ctx.search?.({
98
+ alias: edge.alias,
99
+ name: packageInfo.name,
100
+ version: packageInfo.version,
101
+ readManifest,
102
+ });
103
+ const subtree = edge.target == null
104
+ ? createEmptySubtree()
105
+ : materializeTarget(ctx, { target: edge.target, maxDepth: parent.childTreeMaxDepth, dir: packageInfo.path });
106
+ collectSubtreeSearchMatches(result, subtree);
107
+ const newEntry = edge.target == null
108
+ ? selectUnresolvedEntry(ctx, packageInfo, searchMatch)
109
+ : selectTargetEntry(ctx, { packageInfo, searchMatch, subtree, target: edge.target });
110
+ if (newEntry == null)
111
+ return;
112
+ markSearchMatch(newEntry, { searchMatch, subtree, result });
113
+ if (!newEntry.isPeer || !ctx.excludePeerDependencies || newEntry.dependencies?.length) {
114
+ result.nodes.push(newEntry);
115
+ result.count += 1 + (newEntry.dependencies?.length ? subtree.count : 0);
116
+ }
117
+ }
118
+ function collectSubtreeSearchMatches(result, subtree) {
119
+ if (subtree.hasSearchMatch || subtree.dedupedHasSearchMatch) {
120
+ result.hasSearchMatch = true;
121
+ }
122
+ result.searchMessages?.push(...subtree.searchMessages, ...subtree.dedupedSearchMessages);
123
+ }
124
+ function createEmptySubtree() {
125
+ return {
126
+ dependencies: [],
127
+ count: 0,
128
+ hasSearchMatch: false,
129
+ searchMessages: [],
130
+ dedupedHasSearchMatch: false,
131
+ dedupedSearchMessages: [],
132
+ };
133
+ }
134
+ function materializeTarget(ctx, { target, maxDepth, dir }) {
135
+ if (ctx.ancestors.has(target.id))
136
+ return createEmptySubtree();
137
+ const cacheKey = materializeCacheKey(target.id, maxDepth);
138
+ const cached = ctx.materializationCache.get(cacheKey);
139
+ if (cached !== undefined) {
140
+ // This subtree was already returned to a parent elsewhere in
141
+ // the output tree — elide it to avoid repeating the same nodes.
142
+ return {
143
+ ...createEmptySubtree(),
144
+ dedupedCount: cached.count > 0 ? cached.count : undefined,
145
+ dedupedHasSearchMatch: ctx.showDedupedSearchMatches ? cached.hasSearchMatch : false,
146
+ dedupedSearchMessages: ctx.showDedupedSearchMatches ? cached.searchMessages : [],
147
+ };
148
+ }
149
+ ctx.ancestors.add(target.id);
150
+ const childResult = materializeChildren(ctx, target.nodeId, maxDepth, dir);
151
+ ctx.ancestors.delete(target.id);
152
+ ctx.materializationCache.set(cacheKey, {
153
+ count: childResult.count,
154
+ hasSearchMatch: childResult.hasSearchMatch,
155
+ searchMessages: childResult.searchMessages,
156
+ });
157
+ return {
158
+ dependencies: childResult.nodes,
159
+ count: childResult.count,
160
+ hasSearchMatch: childResult.hasSearchMatch,
161
+ searchMessages: childResult.searchMessages,
162
+ dedupedHasSearchMatch: false,
163
+ dedupedSearchMessages: [],
164
+ };
165
+ }
166
+ // External link or unresolvable — no traversal possible. With
167
+ // onlyProjects, this is a link to a project outside the lockfile, which
168
+ // buildDependenciesTree walks and prunes for the search afterwards.
169
+ function selectUnresolvedEntry(ctx, packageInfo, searchMatch) {
170
+ return ctx.search == null || searchMatch || ctx.onlyProjects ? packageInfo : undefined;
171
+ }
172
+ function selectTargetEntry(ctx, { packageInfo, searchMatch, subtree, target }) {
173
+ let newEntry;
174
+ if (subtree.dependencies.length > 0) {
175
+ newEntry = {
176
+ ...packageInfo,
177
+ dependencies: subtree.dependencies,
178
+ };
179
+ }
180
+ else if (ctx.search == null || searchMatch || subtree.dedupedHasSearchMatch) {
181
+ newEntry = packageInfo;
182
+ }
183
+ else {
184
+ return undefined;
185
+ }
186
+ if (subtree.dedupedCount != null) {
187
+ newEntry.deduped = true;
188
+ newEntry.dedupedDependenciesCount = subtree.dedupedCount;
189
+ }
190
+ if (target.nodeId.type === 'package') {
191
+ const peerHash = peersSuffixHashFromDepPath(target.nodeId.depPath);
192
+ if (peerHash != null) {
193
+ newEntry.peersSuffixHash = peerHash;
180
194
  }
181
- else if (dedupedHasSearchMatch) {
182
- newEntry.searched = true;
183
- if (dedupedSearchMessages.length > 0) {
184
- newEntry.searchMessage = dedupedSearchMessages.join('\n');
185
- }
195
+ }
196
+ return newEntry;
197
+ }
198
+ function markSearchMatch(newEntry, { searchMatch, subtree, result }) {
199
+ if (searchMatch) {
200
+ newEntry.searched = true;
201
+ result.hasSearchMatch = true;
202
+ if (typeof searchMatch === 'string') {
203
+ newEntry.searchMessage = searchMatch;
204
+ result.searchMessages?.push(searchMatch);
186
205
  }
187
- if (!newEntry.isPeer || !ctx.excludePeerDependencies || newEntry.dependencies?.length) {
188
- resultDependencies.push(newEntry);
189
- resultCount += 1 + (newEntry.dependencies?.length ? childCount : 0);
206
+ return;
207
+ }
208
+ if (subtree.dedupedHasSearchMatch) {
209
+ newEntry.searched = true;
210
+ if (subtree.dedupedSearchMessages.length > 0) {
211
+ newEntry.searchMessage = subtree.dedupedSearchMessages.join('\n');
190
212
  }
191
213
  }
192
- return {
193
- count: resultCount,
194
- hasSearchMatch: resultHasSearchMatch,
195
- nodes: resultDependencies,
196
- searchMessages: resultSearchMessages ?? [],
197
- };
198
214
  }
199
215
  /**
200
216
  * Walks the materialized DependencyNode[] tree and marks circular back-edges.
@@ -206,7 +222,6 @@ function materializeChildren(ctx, parentId, maxDepth, parentDir) {
206
222
  function fixCircularRefs(nodes, ancestors) {
207
223
  let changed = false;
208
224
  const result = nodes.map(node => {
209
- // A node whose path matches an ancestor is a circular back-edge.
210
225
  if (node.path && ancestors.has(node.path)) {
211
226
  changed = true;
212
227
  const { dependencies: _, deduped: _d, dedupedDependenciesCount: _c, ...rest } = node;
@@ -9,39 +9,41 @@ import { depPathToFilename } from '@pnpm/deps.path';
9
9
  * symlinks are resolved to find the actual store location.
10
10
  */
11
11
  export function resolvePackagePath(opts) {
12
- let fullPackagePath = path.join(opts.virtualStoreDir, depPathToFilename(opts.depPath, opts.virtualStoreDirMaxLength), 'node_modules', opts.name);
12
+ const fullPackagePath = path.join(opts.virtualStoreDir, depPathToFilename(opts.depPath, opts.virtualStoreDirMaxLength), 'node_modules', opts.name);
13
13
  // Resolve symlink for global virtual store.
14
- // Global virtual store is detected when virtualStoreDir is outside the project's node_modules.
15
- const resolvedVirtualStoreDir = path.resolve(opts.virtualStoreDir);
16
- const resolvedModulesDir = opts.modulesDir ? path.resolve(opts.modulesDir) : undefined;
17
- const isGlobalVirtualStore = resolvedModulesDir &&
18
- !resolvedVirtualStoreDir.startsWith(resolvedModulesDir + path.sep) &&
19
- resolvedVirtualStoreDir !== resolvedModulesDir;
20
- if (isGlobalVirtualStore) {
21
- try {
22
- let nodeModulesDir;
23
- if (opts.parentDir) {
24
- // parentDir example: /store/.../node_modules/express
25
- // /store/.../node_modules/@scope/pkg
26
- // We need the node_modules directory to find sibling packages
27
- nodeModulesDir = path.dirname(opts.parentDir);
28
- // For scoped packages (@org/pkg), go up one more level
29
- if (path.basename(nodeModulesDir).startsWith('@')) {
30
- nodeModulesDir = path.dirname(nodeModulesDir);
31
- }
32
- }
33
- else if (opts.modulesDir) {
34
- nodeModulesDir = opts.modulesDir;
35
- }
36
- else {
37
- return fullPackagePath;
38
- }
39
- fullPackagePath = fs.realpathSync(path.join(nodeModulesDir, opts.alias));
40
- }
41
- catch {
42
- // Fallback to constructed path if symlink doesn't exist
43
- }
14
+ if (!isGlobalVirtualStore(opts.virtualStoreDir, opts.modulesDir))
15
+ return fullPackagePath;
16
+ const nodeModulesDir = getSymlinkNodeModulesDir(opts);
17
+ if (nodeModulesDir == null)
18
+ return fullPackagePath;
19
+ try {
20
+ return fs.realpathSync(path.join(nodeModulesDir, opts.alias));
21
+ }
22
+ catch {
23
+ // Fallback to constructed path if symlink doesn't exist
24
+ return fullPackagePath;
44
25
  }
45
- return fullPackagePath;
26
+ }
27
+ /**
28
+ * Global virtual store is detected when virtualStoreDir is outside the project's node_modules.
29
+ */
30
+ function isGlobalVirtualStore(virtualStoreDir, modulesDir) {
31
+ const resolvedVirtualStoreDir = path.resolve(virtualStoreDir);
32
+ const resolvedModulesDir = modulesDir ? path.resolve(modulesDir) : undefined;
33
+ return Boolean(resolvedModulesDir &&
34
+ !resolvedVirtualStoreDir.startsWith(resolvedModulesDir + path.sep) &&
35
+ resolvedVirtualStoreDir !== resolvedModulesDir);
36
+ }
37
+ function getSymlinkNodeModulesDir(opts) {
38
+ if (!opts.parentDir)
39
+ return opts.modulesDir || undefined;
40
+ // parentDir example: /store/.../node_modules/express
41
+ // /store/.../node_modules/@scope/pkg
42
+ // We need the node_modules directory to find sibling packages
43
+ const nodeModulesDir = path.dirname(opts.parentDir);
44
+ // For scoped packages (@org/pkg), go up one more level
45
+ return path.basename(nodeModulesDir).startsWith('@')
46
+ ? path.dirname(nodeModulesDir)
47
+ : nodeModulesDir;
46
48
  }
47
49
  //# sourceMappingURL=resolvePackagePath.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pnpm/deps.inspection.tree-builder",
3
- "version": "1101.0.7",
3
+ "version": "1101.0.9",
4
4
  "description": "Creates a dependencies hierarchy for a symlinked `node_modules`",
5
5
  "keywords": [
6
6
  "pnpm",
@@ -30,19 +30,19 @@
30
30
  "!*.map"
31
31
  ],
32
32
  "dependencies": {
33
- "@pnpm/config.matcher": "1100.0.2",
33
+ "@pnpm/config.matcher": "1100.0.3",
34
34
  "@pnpm/config.normalize-registries": "1101.0.2",
35
35
  "@pnpm/deps.path": "1101.0.5",
36
36
  "@pnpm/fs.read-modules-dir": "1100.0.3",
37
37
  "@pnpm/installing.modules-yaml": "1101.0.5",
38
38
  "@pnpm/lockfile.detect-dep-types": "1100.0.26",
39
- "@pnpm/lockfile.fs": "1100.2.10",
39
+ "@pnpm/lockfile.fs": "1100.2.12",
40
40
  "@pnpm/lockfile.peer-edges": "1100.0.1",
41
41
  "@pnpm/lockfile.utils": "1102.1.5",
42
42
  "@pnpm/npm-package-arg": "^2.0.0",
43
43
  "@pnpm/pkg-manifest.reader": "1100.0.21",
44
- "@pnpm/store.cafs": "1100.3.5",
45
- "@pnpm/store.index": "1100.3.3",
44
+ "@pnpm/store.cafs": "1100.3.6",
45
+ "@pnpm/store.index": "1101.0.0",
46
46
  "@pnpm/text.ordinal-comparator": "1100.0.0",
47
47
  "@pnpm/types": "1102.1.1",
48
48
  "load-json-file": "^7.0.1",
@@ -56,7 +56,7 @@
56
56
  "devDependencies": {
57
57
  "@jest/globals": "30.4.1",
58
58
  "@pnpm/constants": "1102.0.0",
59
- "@pnpm/deps.inspection.tree-builder": "1101.0.7",
59
+ "@pnpm/deps.inspection.tree-builder": "1101.0.9",
60
60
  "@pnpm/test-fixtures": "1100.0.1",
61
61
  "@types/normalize-path": "^3.0.2",
62
62
  "@types/semver": "7.8.0"