@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/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
|
|
37
|
-
const
|
|
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
|
-
|
|
47
|
-
result[projectPath] = {};
|
|
48
|
-
}
|
|
49
|
-
return result;
|
|
40
|
+
return Object.fromEntries(projectDirs.map((projectPath) => [projectPath, {}]));
|
|
50
41
|
}
|
|
51
|
-
const
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
|
87
|
+
for (const projectPath of sources.projectDirs) {
|
|
79
88
|
const importerId = getLockfileImporterId(opts.lockfileDir, projectPath);
|
|
80
|
-
if (
|
|
89
|
+
if (lockfile.importers[importerId]) {
|
|
81
90
|
allRootIds.push({ type: 'importer', importerId });
|
|
82
91
|
}
|
|
83
92
|
}
|
|
84
93
|
const sharedGraph = buildDependencyGraph(allRootIds, {
|
|
85
|
-
currentPackages:
|
|
86
|
-
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(
|
|
99
|
+
peerSatisfactionEdges: getPeerSatisfactionEdgesToSkip(lockfile, {
|
|
91
100
|
include: opts.include,
|
|
92
|
-
resolvePeersFromWorkspaceRoot:
|
|
101
|
+
resolvePeersFromWorkspaceRoot: treeOpts.resolvePeersFromWorkspaceRoot,
|
|
93
102
|
}),
|
|
94
103
|
});
|
|
95
104
|
const sharedMaterializationCache = new Map();
|
|
96
|
-
const sharedDepTypes = detectDepTypes(
|
|
97
|
-
|
|
98
|
-
currentLockfile:
|
|
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
},
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
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
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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,
|
|
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
|
-
|
|
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?:
|
|
43
|
-
name: string;
|
|
44
|
-
version: string;
|
|
45
|
-
manifest: DependencyManifest;
|
|
46
|
-
}) => string | undefined;
|
|
47
|
+
nameFormatter?: NameFormatter;
|
|
47
48
|
resolvePeersFromWorkspaceRoot?: boolean;
|
|
48
|
-
}
|
|
49
|
+
}
|
|
50
|
+
export declare function buildDependentsTree(packages: string[], projectPaths: string[], opts: BuildDependentsTreeOptions): Promise<DependentsTree[]>;
|
|
51
|
+
export {};
|