@pnpm/deps.inspection.tree-builder 1101.0.7 → 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/lib/getTree.js CHANGED
@@ -54,14 +54,21 @@ function materializeChildren(ctx, parentId, maxDepth, parentDir) {
54
54
  if (!graphNode) {
55
55
  throw new Error(`Node ${parentSerialized} not found in the dependency graph`);
56
56
  }
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;
57
+ const parent = {
58
+ id: parentId,
59
+ dir: parentDir,
60
+ peers: graphNode.peers,
61
+ childTreeMaxDepth: maxDepth - 1,
62
+ linkedPathBaseDir: parentId.type === 'importer'
63
+ ? path.join(ctx.lockfileDir, parentId.importerId)
64
+ : ctx.lockfileDir,
65
+ };
66
+ const result = {
67
+ nodes: [],
68
+ count: 0,
69
+ hasSearchMatch: false,
70
+ searchMessages: ctx.showDedupedSearchMatches ? [] : undefined,
71
+ };
65
72
  // Sort edges by alias so that deduplication is deterministic:
66
73
  // the alphabetically-first dependency always gets fully expanded.
67
74
  const sortedEdges = [...graphNode.edges].sort((a, b) => lexCompare(a.alias, b.alias));
@@ -69,132 +76,147 @@ function materializeChildren(ctx, parentId, maxDepth, parentDir) {
69
76
  if (ctx.onlyProjects && !isProjectEdge(parentId, edge)) {
70
77
  continue;
71
78
  }
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
- }
79
+ materializeEdge(ctx, { parent, edge, result });
80
+ }
81
+ return {
82
+ count: result.count,
83
+ hasSearchMatch: result.hasSearchMatch,
84
+ nodes: result.nodes,
85
+ searchMessages: result.searchMessages ?? [],
86
+ };
87
+ }
88
+ function materializeEdge(ctx, { parent, edge, result }) {
89
+ const { pkgInfo: packageInfo, readManifest } = getPkgInfo({
90
+ ...ctx,
91
+ alias: edge.alias,
92
+ ref: edge.ref,
93
+ peers: parent.peers,
94
+ linkedPathBaseDir: parent.linkedPathBaseDir,
95
+ parentDir: parent.dir,
96
+ });
97
+ // A project linked through its publish directory is listed at its own
98
+ // directory.
99
+ if (ctx.onlyProjects && edge.target?.nodeId.type === 'importer') {
100
+ packageInfo.path = path.join(ctx.lockfileDir, edge.target.nodeId.importerId);
101
+ }
102
+ const searchMatch = ctx.search?.({
103
+ alias: edge.alias,
104
+ name: packageInfo.name,
105
+ version: packageInfo.version,
106
+ readManifest,
107
+ });
108
+ const subtree = edge.target == null
109
+ ? createEmptySubtree()
110
+ : materializeTarget(ctx, { target: edge.target, maxDepth: parent.childTreeMaxDepth, dir: packageInfo.path });
111
+ collectSubtreeSearchMatches(result, subtree);
112
+ const newEntry = edge.target == null
113
+ ? selectUnresolvedEntry(ctx, packageInfo, searchMatch)
114
+ : selectTargetEntry(ctx, { packageInfo, searchMatch, subtree, target: edge.target });
115
+ if (newEntry == null)
116
+ return;
117
+ markSearchMatch(newEntry, { searchMatch, subtree, result });
118
+ if (!newEntry.isPeer || !ctx.excludePeerDependencies || newEntry.dependencies?.length) {
119
+ result.nodes.push(newEntry);
120
+ result.count += 1 + (newEntry.dependencies?.length ? subtree.count : 0);
121
+ }
122
+ }
123
+ function collectSubtreeSearchMatches(result, subtree) {
124
+ if (subtree.hasSearchMatch || subtree.dedupedHasSearchMatch) {
125
+ result.hasSearchMatch = true;
126
+ }
127
+ result.searchMessages?.push(...subtree.searchMessages, ...subtree.dedupedSearchMessages);
128
+ }
129
+ function createEmptySubtree() {
130
+ return {
131
+ dependencies: [],
132
+ count: 0,
133
+ hasSearchMatch: false,
134
+ searchMessages: [],
135
+ dedupedHasSearchMatch: false,
136
+ dedupedSearchMessages: [],
137
+ };
138
+ }
139
+ function materializeTarget(ctx, { target, maxDepth, dir }) {
140
+ if (ctx.ancestors.has(target.id))
141
+ return createEmptySubtree();
142
+ const cacheKey = materializeCacheKey(target.id, maxDepth);
143
+ const cached = ctx.materializationCache.get(cacheKey);
144
+ if (cached !== undefined) {
145
+ // This subtree was already returned to a parent elsewhere in
146
+ // the output tree — elide it to avoid repeating the same nodes.
147
+ return {
148
+ ...createEmptySubtree(),
149
+ dedupedCount: cached.count > 0 ? cached.count : undefined,
150
+ dedupedHasSearchMatch: ctx.showDedupedSearchMatches ? cached.hasSearchMatch : false,
151
+ dedupedSearchMessages: ctx.showDedupedSearchMatches ? cached.searchMessages : [],
152
+ };
153
+ }
154
+ ctx.ancestors.add(target.id);
155
+ const childResult = materializeChildren(ctx, target.nodeId, maxDepth, dir);
156
+ ctx.ancestors.delete(target.id);
157
+ // Always cache — even results with circular truncations.
158
+ ctx.materializationCache.set(cacheKey, {
159
+ count: childResult.count,
160
+ hasSearchMatch: childResult.hasSearchMatch,
161
+ searchMessages: childResult.searchMessages,
162
+ });
163
+ return {
164
+ dependencies: childResult.nodes,
165
+ count: childResult.count,
166
+ hasSearchMatch: childResult.hasSearchMatch,
167
+ searchMessages: childResult.searchMessages,
168
+ dedupedHasSearchMatch: false,
169
+ dedupedSearchMessages: [],
170
+ };
171
+ }
172
+ // External link or unresolvable — no traversal possible. With
173
+ // onlyProjects, this is a link to a project outside the lockfile, which
174
+ // buildDependenciesTree walks and prunes for the search afterwards.
175
+ function selectUnresolvedEntry(ctx, packageInfo, searchMatch) {
176
+ return ctx.search == null || searchMatch || ctx.onlyProjects ? packageInfo : undefined;
177
+ }
178
+ function selectTargetEntry(ctx, { packageInfo, searchMatch, subtree, target }) {
179
+ let newEntry;
180
+ if (subtree.dependencies.length > 0) {
181
+ newEntry = {
182
+ ...packageInfo,
183
+ dependencies: subtree.dependencies,
184
+ };
185
+ }
186
+ else if (ctx.search == null || searchMatch || subtree.dedupedHasSearchMatch) {
187
+ newEntry = packageInfo;
188
+ }
189
+ else {
190
+ return undefined;
191
+ }
192
+ if (subtree.dedupedCount != null) {
193
+ newEntry.deduped = true;
194
+ newEntry.dedupedDependenciesCount = subtree.dedupedCount;
195
+ }
196
+ if (target.nodeId.type === 'package') {
197
+ const peerHash = peersSuffixHashFromDepPath(target.nodeId.depPath);
198
+ if (peerHash != null) {
199
+ newEntry.peersSuffixHash = peerHash;
180
200
  }
181
- else if (dedupedHasSearchMatch) {
182
- newEntry.searched = true;
183
- if (dedupedSearchMessages.length > 0) {
184
- newEntry.searchMessage = dedupedSearchMessages.join('\n');
185
- }
201
+ }
202
+ return newEntry;
203
+ }
204
+ function markSearchMatch(newEntry, { searchMatch, subtree, result }) {
205
+ if (searchMatch) {
206
+ newEntry.searched = true;
207
+ result.hasSearchMatch = true;
208
+ if (typeof searchMatch === 'string') {
209
+ newEntry.searchMessage = searchMatch;
210
+ result.searchMessages?.push(searchMatch);
186
211
  }
187
- if (!newEntry.isPeer || !ctx.excludePeerDependencies || newEntry.dependencies?.length) {
188
- resultDependencies.push(newEntry);
189
- resultCount += 1 + (newEntry.dependencies?.length ? childCount : 0);
212
+ return;
213
+ }
214
+ if (subtree.dedupedHasSearchMatch) {
215
+ newEntry.searched = true;
216
+ if (subtree.dedupedSearchMessages.length > 0) {
217
+ newEntry.searchMessage = subtree.dedupedSearchMessages.join('\n');
190
218
  }
191
219
  }
192
- return {
193
- count: resultCount,
194
- hasSearchMatch: resultHasSearchMatch,
195
- nodes: resultDependencies,
196
- searchMessages: resultSearchMessages ?? [],
197
- };
198
220
  }
199
221
  /**
200
222
  * Walks the materialized DependencyNode[] tree and marks circular back-edges.
@@ -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.8",
4
4
  "description": "Creates a dependencies hierarchy for a symlinked `node_modules`",
5
5
  "keywords": [
6
6
  "pnpm",
@@ -36,13 +36,13 @@
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.11",
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
44
  "@pnpm/store.cafs": "1100.3.5",
45
- "@pnpm/store.index": "1100.3.3",
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.8",
60
60
  "@pnpm/test-fixtures": "1100.0.1",
61
61
  "@types/normalize-path": "^3.0.2",
62
62
  "@types/semver": "7.8.0"