@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 +19 -0
- package/lib/buildDependenciesTree.js +142 -119
- package/lib/buildDependencyGraph.d.ts +3 -2
- package/lib/buildDependencyGraph.js +70 -52
- package/lib/buildDependentsTree.d.ts +10 -7
- package/lib/buildDependentsTree.js +180 -158
- package/lib/createPackagesSearcher.js +17 -14
- package/lib/getPkgInfo.js +75 -78
- package/lib/getTree.js +151 -136
- package/lib/resolvePackagePath.js +34 -32
- package/package.json +6 -6
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
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
:
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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
|
-
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
45
|
-
"@pnpm/store.index": "
|
|
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.
|
|
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"
|