@pnpm/deps.inspection.tree-builder 1101.0.4 → 1101.0.6
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 +38 -0
- package/lib/buildDependenciesTree.d.ts +14 -2
- package/lib/buildDependenciesTree.js +113 -3
- package/lib/buildDependencyGraph.d.ts +12 -1
- package/lib/buildDependencyGraph.js +20 -5
- package/lib/buildDependentsTree.d.ts +1 -0
- package/lib/buildDependentsTree.js +5 -0
- package/lib/getTree.d.ts +1 -1
- package/lib/getTree.js +11 -3
- package/lib/getTreeNodeChildId.js +26 -3
- package/package.json +14 -13
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
# @pnpm/reviewing.dependencies-hierarchy
|
|
2
2
|
|
|
3
|
+
## 1101.0.6
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- `pnpm list --only-projects` now lists the workspace projects when `sharedWorkspaceLockfile` is `false` [#7151](https://github.com/pnpm/pnpm/issues/7151).
|
|
8
|
+
|
|
9
|
+
- `pnpm list --only-projects` now lists a workspace project that sets `publishConfig.directory`. Dependents link such a project through its publish directory, which `--only-projects` did not recognize as a project [#10635](https://github.com/pnpm/pnpm/issues/10635).
|
|
10
|
+
|
|
11
|
+
- `pnpm list --only-projects` now prints every project selected with `--filter` or `--recursive`, including a project that has no workspace dependencies [#9770](https://github.com/pnpm/pnpm/issues/9770).
|
|
12
|
+
|
|
13
|
+
`pnpm list --only-projects` no longer reports packages in `node_modules` that are missing from the lockfile [#9528](https://github.com/pnpm/pnpm/issues/9528).
|
|
14
|
+
|
|
15
|
+
- `pnpm install --prod`, `pnpm fetch --prod` and `pnpm deploy --prod` no longer install a devDependency that is only there to satisfy an optional peer dependency of a production dependency. `pnpm list`, `pnpm why`, `pnpm licenses`, `pnpm sbom` and `pnpm audit` leave it out of `--prod` results too. The same applies to `--dev`. A peer that is not optional is still installed and audited [#15344](https://github.com/pnpm/pnpm/issues/15344).
|
|
16
|
+
|
|
17
|
+
- Updated dependencies:
|
|
18
|
+
- @pnpm/config.normalize-registries@1101.0.2
|
|
19
|
+
- @pnpm/deps.path@1101.0.4
|
|
20
|
+
- @pnpm/installing.modules-yaml@1101.0.4
|
|
21
|
+
- @pnpm/lockfile.detect-dep-types@1100.0.25
|
|
22
|
+
- @pnpm/lockfile.fs@1100.2.9
|
|
23
|
+
- @pnpm/lockfile.utils@1102.1.4
|
|
24
|
+
- @pnpm/pkg-manifest.reader@1100.0.20
|
|
25
|
+
- @pnpm/store.cafs@1100.3.4
|
|
26
|
+
- @pnpm/store.index@1100.3.2
|
|
27
|
+
- @pnpm/types@1102.1.1
|
|
28
|
+
|
|
29
|
+
## 1101.0.5
|
|
30
|
+
|
|
31
|
+
### Patch Changes
|
|
32
|
+
|
|
33
|
+
- Updated dependencies:
|
|
34
|
+
- @pnpm/deps.path@1101.0.3
|
|
35
|
+
- @pnpm/installing.modules-yaml@1101.0.3
|
|
36
|
+
- @pnpm/lockfile.detect-dep-types@1100.0.24
|
|
37
|
+
- @pnpm/lockfile.fs@1100.2.8
|
|
38
|
+
- @pnpm/lockfile.utils@1102.1.3
|
|
39
|
+
- @pnpm/store.cafs@1100.3.3
|
|
40
|
+
|
|
3
41
|
## 1101.0.4
|
|
4
42
|
|
|
5
43
|
### Patch Changes
|
|
@@ -6,7 +6,7 @@ export interface DependenciesTree {
|
|
|
6
6
|
optionalDependencies?: DependencyNode[];
|
|
7
7
|
unsavedDependencies?: DependencyNode[];
|
|
8
8
|
}
|
|
9
|
-
export
|
|
9
|
+
export interface BuildDependenciesTreeOptions {
|
|
10
10
|
depth: number;
|
|
11
11
|
excludePeerDependencies?: boolean;
|
|
12
12
|
include?: {
|
|
@@ -15,12 +15,24 @@ export declare function buildDependenciesTree(projectPaths: string[] | undefined
|
|
|
15
15
|
registriesByScope?: RegistriesByScope;
|
|
16
16
|
registriesByPrefix?: Record<string, string>;
|
|
17
17
|
onlyProjects?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* The workspace projects that `onlyProjects` follows through their own
|
|
20
|
+
* lockfiles when the lockfile being read has no importer for them.
|
|
21
|
+
*/
|
|
22
|
+
workspaceProjectDirs?: string[];
|
|
23
|
+
/**
|
|
24
|
+
* The workspace projects that dependents link through their publish
|
|
25
|
+
* directory (`publishConfig.directory`), keyed by that directory.
|
|
26
|
+
*/
|
|
27
|
+
workspaceProjectPublishDirs?: Record<string, string>;
|
|
18
28
|
search?: Finder;
|
|
19
29
|
showDedupedSearchMatches?: boolean;
|
|
20
30
|
lockfileDir: string;
|
|
21
31
|
checkWantedLockfileOnly?: boolean;
|
|
22
32
|
modulesDir?: string;
|
|
33
|
+
resolvePeersFromWorkspaceRoot?: boolean;
|
|
23
34
|
virtualStoreDirMaxLength: number;
|
|
24
|
-
}
|
|
35
|
+
}
|
|
36
|
+
export declare function buildDependenciesTree(projectPaths: string[] | undefined, maybeOpts: BuildDependenciesTreeOptions): Promise<{
|
|
25
37
|
[projectDir: string]: DependenciesTree;
|
|
26
38
|
}>;
|
|
@@ -4,6 +4,7 @@ import { readModulesDir } from '@pnpm/fs.read-modules-dir';
|
|
|
4
4
|
import { readModulesManifest } from '@pnpm/installing.modules-yaml';
|
|
5
5
|
import { detectDepTypes } from '@pnpm/lockfile.detect-dep-types';
|
|
6
6
|
import { getLockfileImporterId, readCurrentLockfile, readWantedLockfile, } from '@pnpm/lockfile.fs';
|
|
7
|
+
import { getPeerSatisfactionEdgesToSkip } from '@pnpm/lockfile.peer-edges';
|
|
7
8
|
import { safeReadPackageJsonFromDir } from '@pnpm/pkg-manifest.reader';
|
|
8
9
|
import { StoreIndex } from '@pnpm/store.index';
|
|
9
10
|
import { DEPENDENCIES_FIELDS } from '@pnpm/types';
|
|
@@ -16,6 +17,16 @@ import { buildDependencyGraph } from './buildDependencyGraph.js';
|
|
|
16
17
|
import { getTree } from './getTree.js';
|
|
17
18
|
const limitUnsavedReads = pLimit(4);
|
|
18
19
|
export async function buildDependenciesTree(projectPaths, maybeOpts) {
|
|
20
|
+
return buildProjectsTrees(projectPaths, maybeOpts, {
|
|
21
|
+
ancestors: new Set(),
|
|
22
|
+
expanded: new Map(),
|
|
23
|
+
linkedProjectDirs: new Map([
|
|
24
|
+
...Object.entries(maybeOpts.workspaceProjectPublishDirs ?? {}),
|
|
25
|
+
...(maybeOpts.workspaceProjectDirs ?? []).map((dir) => [dir, dir]),
|
|
26
|
+
]),
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
async function buildProjectsTrees(projectPaths, maybeOpts, linkedWalk) {
|
|
19
30
|
if (!maybeOpts?.lockfileDir) {
|
|
20
31
|
throw new TypeError('opts.lockfileDir is required');
|
|
21
32
|
}
|
|
@@ -76,9 +87,13 @@ export async function buildDependenciesTree(projectPaths, maybeOpts) {
|
|
|
76
87
|
include: opts.include,
|
|
77
88
|
lockfileDir: opts.lockfileDir,
|
|
78
89
|
onlyProjects: opts.onlyProjects,
|
|
90
|
+
peerSatisfactionEdges: getPeerSatisfactionEdgesToSkip(lockfileToUse, {
|
|
91
|
+
include: opts.include,
|
|
92
|
+
resolvePeersFromWorkspaceRoot: maybeOpts.resolvePeersFromWorkspaceRoot,
|
|
93
|
+
}),
|
|
79
94
|
});
|
|
80
95
|
const sharedMaterializationCache = new Map();
|
|
81
|
-
const sharedDepTypes = detectDepTypes(lockfileToUse);
|
|
96
|
+
const sharedDepTypes = detectDepTypes(lockfileToUse, maybeOpts);
|
|
82
97
|
const ctx = {
|
|
83
98
|
currentLockfile: lockfileToUse,
|
|
84
99
|
wantedLockfile,
|
|
@@ -94,12 +109,106 @@ export async function buildDependenciesTree(projectPaths, maybeOpts) {
|
|
|
94
109
|
await getHierarchy(projectPath),
|
|
95
110
|
];
|
|
96
111
|
}));
|
|
112
|
+
storeIndex?.close();
|
|
97
113
|
for (const [projectPath, dependenciesHierarchy] of pairs) {
|
|
98
114
|
result[projectPath] = dependenciesHierarchy;
|
|
99
115
|
}
|
|
100
|
-
|
|
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
|
+
}
|
|
101
131
|
return result;
|
|
102
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* Attaches the project dependencies of every linked workspace project that
|
|
135
|
+
* the lockfile has no importer for. With `sharedWorkspaceLockfile: false`,
|
|
136
|
+
* the lockfile that the tree was built from knows nothing about the
|
|
137
|
+
* dependencies of the other workspace projects.
|
|
138
|
+
*/
|
|
139
|
+
async function expandLinkedProjects(tree, ctx) {
|
|
140
|
+
for (const field of DEPENDENCIES_FIELDS) {
|
|
141
|
+
if (tree[field] != null) {
|
|
142
|
+
// eslint-disable-next-line no-await-in-loop
|
|
143
|
+
tree[field] = await expandLinkedProjectNodes(tree[field], 0, ctx);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
async function expandLinkedProjectNodes(nodes, level, ctx) {
|
|
148
|
+
const expanded = [];
|
|
149
|
+
for (const node of nodes) {
|
|
150
|
+
let expandedNode = node;
|
|
151
|
+
if (node.dependencies != null) {
|
|
152
|
+
// eslint-disable-next-line no-await-in-loop
|
|
153
|
+
expandedNode = keepSearched({ ...node, dependencies: await expandLinkedProjectNodes(node.dependencies, level + 1, ctx) }, ctx);
|
|
154
|
+
}
|
|
155
|
+
else if (!node.circular && ctx.importers[getLockfileImporterId(ctx.lockfileDir, node.path)] == null) {
|
|
156
|
+
// eslint-disable-next-line no-await-in-loop
|
|
157
|
+
expandedNode = await expandLinkedProject(node, level, ctx);
|
|
158
|
+
}
|
|
159
|
+
if (expandedNode != null)
|
|
160
|
+
expanded.push(expandedNode);
|
|
161
|
+
}
|
|
162
|
+
return expanded;
|
|
163
|
+
}
|
|
164
|
+
async function expandLinkedProject(linkedNode, level, ctx) {
|
|
165
|
+
const projectDir = ctx.walk.linkedProjectDirs.get(linkedNode.path);
|
|
166
|
+
if (projectDir == null)
|
|
167
|
+
return undefined;
|
|
168
|
+
const node = { ...linkedNode, path: projectDir };
|
|
169
|
+
if (ctx.walk.ancestors.has(node.path))
|
|
170
|
+
return keepSearched({ ...node, circular: true }, ctx);
|
|
171
|
+
if (level >= ctx.depth)
|
|
172
|
+
return keepSearched(node, ctx);
|
|
173
|
+
const depth = ctx.depth - level - 1;
|
|
174
|
+
const key = `${node.path}@${depth}`;
|
|
175
|
+
const previousCount = ctx.walk.expanded.get(key);
|
|
176
|
+
if (previousCount != null) {
|
|
177
|
+
return previousCount > 0
|
|
178
|
+
? { ...node, deduped: true, dedupedDependenciesCount: previousCount }
|
|
179
|
+
: keepSearched(node, ctx);
|
|
180
|
+
}
|
|
181
|
+
const linkedTrees = await buildProjectsTrees([node.path], {
|
|
182
|
+
...ctx.treeOpts,
|
|
183
|
+
lockfileDir: node.path,
|
|
184
|
+
depth,
|
|
185
|
+
}, ctx.walk);
|
|
186
|
+
const linkedTree = linkedTrees[node.path];
|
|
187
|
+
const dependencies = DEPENDENCIES_FIELDS.flatMap((field) => linkedTree[field] ?? []);
|
|
188
|
+
ctx.walk.expanded.set(key, countNodes(dependencies));
|
|
189
|
+
return keepSearched(dependencies.length > 0
|
|
190
|
+
? { ...node, dependencies: rewriteLinkVersions(dependencies, node.path, ctx.rewriteLinkVersionDir) }
|
|
191
|
+
: node, ctx);
|
|
192
|
+
}
|
|
193
|
+
function countNodes(nodes) {
|
|
194
|
+
return nodes.reduce((count, node) => count + 1 + countNodes(node.dependencies ?? []), 0);
|
|
195
|
+
}
|
|
196
|
+
function keepSearched(node, ctx) {
|
|
197
|
+
return ctx.treeOpts.search == null || node.searched || node.dependencies?.length ? node : undefined;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Rebases the `link:` versions of a linked project's dependencies, which are
|
|
201
|
+
* relative to `linkedProjectDir`, onto `rewriteLinkVersionDir`.
|
|
202
|
+
*/
|
|
203
|
+
function rewriteLinkVersions(nodes, linkedProjectDir, rewriteLinkVersionDir) {
|
|
204
|
+
return nodes.map((node) => ({
|
|
205
|
+
...node,
|
|
206
|
+
version: node.version.startsWith('link:')
|
|
207
|
+
? `link:${normalizePath(path.relative(rewriteLinkVersionDir, path.resolve(linkedProjectDir, node.version.slice('link:'.length))))}`
|
|
208
|
+
: node.version,
|
|
209
|
+
...(node.dependencies && { dependencies: rewriteLinkVersions(node.dependencies, linkedProjectDir, rewriteLinkVersionDir) }),
|
|
210
|
+
}));
|
|
211
|
+
}
|
|
103
212
|
async function dependenciesHierarchyForPackage(opts, projectPath) {
|
|
104
213
|
const { currentLockfile, wantedLockfile } = opts;
|
|
105
214
|
const importerId = getLockfileImporterId(opts.lockfileDir, projectPath);
|
|
@@ -144,7 +253,8 @@ async function dependenciesHierarchyForPackage(opts, projectPath) {
|
|
|
144
253
|
// Handle unsaved dependencies (packages in node_modules but not in lockfile).
|
|
145
254
|
// When searching, unsaved deps are irrelevant — they aren't in the lockfile
|
|
146
255
|
// graph and can't have dependency subtrees showing paths to the search target.
|
|
147
|
-
|
|
256
|
+
// They aren't workspace projects either, which is all onlyProjects lists.
|
|
257
|
+
if (!opts.search && !opts.onlyProjects) {
|
|
148
258
|
const savedDeps = getAllDirectDependencies(currentLockfile.importers[importerId]);
|
|
149
259
|
const unsavedDeps = ((await readModulesDir(modulesDir)) ?? []).filter((directDep) => !savedDeps[directDep]);
|
|
150
260
|
if (unsavedDeps.length > 0)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { PackageSnapshots, ProjectSnapshot } from '@pnpm/lockfile.fs';
|
|
2
|
+
import { type PeerSatisfactionEdges } from '@pnpm/lockfile.peer-edges';
|
|
2
3
|
import { type TreeNodeId } from './TreeNodeId.js';
|
|
3
|
-
interface DependencyEdge {
|
|
4
|
+
export interface DependencyEdge {
|
|
4
5
|
alias: string;
|
|
5
6
|
ref: string;
|
|
6
7
|
target?: {
|
|
@@ -26,5 +27,15 @@ export declare function buildDependencyGraph(rootIds: TreeNodeId[], opts: {
|
|
|
26
27
|
};
|
|
27
28
|
lockfileDir: string;
|
|
28
29
|
onlyProjects?: boolean;
|
|
30
|
+
/** Edges left out of the graph, from `getPeerSatisfactionEdgesToSkip`. */
|
|
31
|
+
peerSatisfactionEdges?: PeerSatisfactionEdges;
|
|
29
32
|
}): DependencyGraph;
|
|
33
|
+
/**
|
|
34
|
+
* Whether an edge may lead to a project. Besides the importers of the lockfile,
|
|
35
|
+
* this includes a project's `link:` dependency on a directory outside the
|
|
36
|
+
* lockfile: with a dedicated lockfile per project, every other workspace
|
|
37
|
+
* project is such a directory. `buildDependenciesTree` keeps those only when
|
|
38
|
+
* the directory is a workspace project.
|
|
39
|
+
*/
|
|
40
|
+
export declare function isProjectEdge(parentId: TreeNodeId, edge: DependencyEdge): boolean;
|
|
30
41
|
export {};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isPeerSatisfactionEdge } from '@pnpm/lockfile.peer-edges';
|
|
1
2
|
import { getTreeNodeChildId } from './getTreeNodeChildId.js';
|
|
2
3
|
import { serializeTreeNodeId } from './TreeNodeId.js';
|
|
3
4
|
export function buildDependencyGraph(rootIds, opts) {
|
|
@@ -43,20 +44,22 @@ export function buildDependencyGraph(rootIds, opts) {
|
|
|
43
44
|
: rawRef?.version;
|
|
44
45
|
if (ref == null)
|
|
45
46
|
continue;
|
|
47
|
+
if (nodeId.type === 'package' && isPeerSatisfactionEdge(opts.peerSatisfactionEdges, nodeId.depPath, alias))
|
|
48
|
+
continue;
|
|
46
49
|
const targetNodeId = getTreeNodeChildId({
|
|
47
50
|
parentId: nodeId,
|
|
48
51
|
dep: { alias, ref },
|
|
49
52
|
lockfileDir: opts.lockfileDir,
|
|
50
53
|
importers: opts.importers,
|
|
51
54
|
});
|
|
52
|
-
// When onlyProjects is true, only follow edges to workspace importers
|
|
53
|
-
if (opts.onlyProjects && targetNodeId?.type !== 'importer') {
|
|
54
|
-
continue;
|
|
55
|
-
}
|
|
56
55
|
const target = targetNodeId != null
|
|
57
56
|
? { id: serializeTreeNodeId(targetNodeId), nodeId: targetNodeId }
|
|
58
57
|
: undefined;
|
|
59
|
-
|
|
58
|
+
const edge = { alias, ref, target };
|
|
59
|
+
if (opts.onlyProjects && !isProjectEdge(nodeId, edge)) {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
edges.push(edge);
|
|
60
63
|
if (target && !visited.has(target.id)) {
|
|
61
64
|
queue.push(target.nodeId);
|
|
62
65
|
}
|
|
@@ -66,6 +69,18 @@ export function buildDependencyGraph(rootIds, opts) {
|
|
|
66
69
|
}
|
|
67
70
|
return graph;
|
|
68
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Whether an edge may lead to a project. Besides the importers of the lockfile,
|
|
74
|
+
* this includes a project's `link:` dependency on a directory outside the
|
|
75
|
+
* lockfile: with a dedicated lockfile per project, every other workspace
|
|
76
|
+
* project is such a directory. `buildDependenciesTree` keeps those only when
|
|
77
|
+
* the directory is a workspace project.
|
|
78
|
+
*/
|
|
79
|
+
export function isProjectEdge(parentId, edge) {
|
|
80
|
+
if (edge.target != null)
|
|
81
|
+
return edge.target.nodeId.type === 'importer';
|
|
82
|
+
return parentId.type === 'importer' && edge.ref.startsWith('link:');
|
|
83
|
+
}
|
|
69
84
|
function getSnapshot(treeNodeId, opts) {
|
|
70
85
|
switch (treeNodeId.type) {
|
|
71
86
|
case 'importer':
|
|
@@ -2,6 +2,7 @@ import path from 'node:path';
|
|
|
2
2
|
import { normalizeRegistriesByScope } from '@pnpm/config.normalize-registries';
|
|
3
3
|
import { readModulesManifest } from '@pnpm/installing.modules-yaml';
|
|
4
4
|
import { getLockfileImporterId, } from '@pnpm/lockfile.fs';
|
|
5
|
+
import { getPeerSatisfactionEdgesToSkip } from '@pnpm/lockfile.peer-edges';
|
|
5
6
|
import { nameVerFromPkgSnapshot } from '@pnpm/lockfile.utils';
|
|
6
7
|
import { StoreIndex } from '@pnpm/store.index';
|
|
7
8
|
import { lexCompare } from '@pnpm/text.ordinal-comparator';
|
|
@@ -37,6 +38,10 @@ export async function buildDependentsTree(packages, projectPaths, opts) {
|
|
|
37
38
|
importers: opts.lockfile.importers,
|
|
38
39
|
include,
|
|
39
40
|
lockfileDir: opts.lockfileDir,
|
|
41
|
+
peerSatisfactionEdges: getPeerSatisfactionEdgesToSkip(opts.lockfile, {
|
|
42
|
+
include,
|
|
43
|
+
resolvePeersFromWorkspaceRoot: opts.resolvePeersFromWorkspaceRoot,
|
|
44
|
+
}),
|
|
40
45
|
});
|
|
41
46
|
const reverseMap = invertGraph(graph);
|
|
42
47
|
const search = createPackagesSearcher(packages, opts.finders);
|
package/lib/getTree.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { DepTypes } from '@pnpm/lockfile.detect-dep-types';
|
|
|
2
2
|
import type { PackageSnapshots, ProjectSnapshot } from '@pnpm/lockfile.fs';
|
|
3
3
|
import type { StoreIndex } from '@pnpm/store.index';
|
|
4
4
|
import type { Finder, RegistriesByScope } from '@pnpm/types';
|
|
5
|
-
import type
|
|
5
|
+
import { type DependencyGraph } from './buildDependencyGraph.js';
|
|
6
6
|
import type { DependencyNode } from './DependencyNode.js';
|
|
7
7
|
import { type TreeNodeId } from './TreeNodeId.js';
|
|
8
8
|
export interface BaseTreeOpts {
|
package/lib/getTree.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { lexCompare } from '@pnpm/text.ordinal-comparator';
|
|
3
|
+
import { isProjectEdge } from './buildDependencyGraph.js';
|
|
3
4
|
import { getPkgInfo } from './getPkgInfo.js';
|
|
4
5
|
import { peersSuffixHashFromDepPath } from './peersSuffixHash.js';
|
|
5
6
|
import { serializeTreeNodeId } from './TreeNodeId.js';
|
|
@@ -65,7 +66,7 @@ function materializeChildren(ctx, parentId, maxDepth, parentDir) {
|
|
|
65
66
|
// the alphabetically-first dependency always gets fully expanded.
|
|
66
67
|
const sortedEdges = [...graphNode.edges].sort((a, b) => lexCompare(a.alias, b.alias));
|
|
67
68
|
for (const edge of sortedEdges) {
|
|
68
|
-
if (ctx.onlyProjects && edge
|
|
69
|
+
if (ctx.onlyProjects && !isProjectEdge(parentId, edge)) {
|
|
69
70
|
continue;
|
|
70
71
|
}
|
|
71
72
|
const { pkgInfo: packageInfo, readManifest } = getPkgInfo({
|
|
@@ -76,6 +77,11 @@ function materializeChildren(ctx, parentId, maxDepth, parentDir) {
|
|
|
76
77
|
linkedPathBaseDir,
|
|
77
78
|
parentDir,
|
|
78
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
|
+
}
|
|
79
85
|
const searchMatch = ctx.search?.({
|
|
80
86
|
alias: edge.alias,
|
|
81
87
|
name: packageInfo.name,
|
|
@@ -87,8 +93,10 @@ function materializeChildren(ctx, parentId, maxDepth, parentDir) {
|
|
|
87
93
|
let dedupedHasSearchMatch = false;
|
|
88
94
|
let dedupedSearchMessages = [];
|
|
89
95
|
if (edge.target == null) {
|
|
90
|
-
// External link or unresolvable — no traversal possible
|
|
91
|
-
|
|
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) {
|
|
92
100
|
newEntry = packageInfo;
|
|
93
101
|
}
|
|
94
102
|
else {
|
|
@@ -18,13 +18,16 @@ export function getTreeNodeChildId(opts) {
|
|
|
18
18
|
// importerId is created by reusing the getLockfileImporterId function.
|
|
19
19
|
const absoluteLinkedPath = path.join(opts.lockfileDir, opts.parentId.importerId, linkValue);
|
|
20
20
|
const childImporterId = getLockfileImporterId(opts.lockfileDir, absoluteLinkedPath);
|
|
21
|
+
if (opts.importers[childImporterId] != null) {
|
|
22
|
+
return { type: 'importer', importerId: childImporterId };
|
|
23
|
+
}
|
|
21
24
|
// A 'link:' reference may refer to a package outside of the pnpm workspace.
|
|
22
25
|
// Return undefined in that case since it would be difficult to list/traverse
|
|
23
26
|
// that package outside of the pnpm workspace.
|
|
24
|
-
const
|
|
25
|
-
return
|
|
27
|
+
const publishingImporterId = getPublishDirectoryImporters(opts.lockfileDir, opts.importers).get(childImporterId);
|
|
28
|
+
return publishingImporterId == null
|
|
26
29
|
? undefined
|
|
27
|
-
: { type: 'importer', importerId:
|
|
30
|
+
: { type: 'importer', importerId: publishingImporterId };
|
|
28
31
|
}
|
|
29
32
|
case 'package':
|
|
30
33
|
// In theory an external package could be overridden to link to a
|
|
@@ -33,4 +36,24 @@ export function getTreeNodeChildId(opts) {
|
|
|
33
36
|
return undefined;
|
|
34
37
|
}
|
|
35
38
|
}
|
|
39
|
+
const publishDirectoryImportersCache = new WeakMap();
|
|
40
|
+
/**
|
|
41
|
+
* The importer each publish directory belongs to, keyed like an importer id.
|
|
42
|
+
* Dependents link a project with `publishConfig.directory` there unless
|
|
43
|
+
* `publishConfig.linkDirectory` is false.
|
|
44
|
+
*/
|
|
45
|
+
function getPublishDirectoryImporters(lockfileDir, importers) {
|
|
46
|
+
let publishDirectoryImporters = publishDirectoryImportersCache.get(importers);
|
|
47
|
+
if (publishDirectoryImporters == null) {
|
|
48
|
+
publishDirectoryImporters = new Map();
|
|
49
|
+
for (const [importerId, { publishDirectory, linkDirectory }] of Object.entries(importers)) {
|
|
50
|
+
if (publishDirectory != null && linkDirectory !== false) {
|
|
51
|
+
const linkedImporterId = getLockfileImporterId(lockfileDir, path.join(lockfileDir, importerId, publishDirectory));
|
|
52
|
+
publishDirectoryImporters.set(linkedImporterId, importerId);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
publishDirectoryImportersCache.set(importers, publishDirectoryImporters);
|
|
56
|
+
}
|
|
57
|
+
return publishDirectoryImporters;
|
|
58
|
+
}
|
|
36
59
|
//# sourceMappingURL=getTreeNodeChildId.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.6",
|
|
4
4
|
"description": "Creates a dependencies hierarchy for a symlinked `node_modules`",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pnpm",
|
|
@@ -31,22 +31,23 @@
|
|
|
31
31
|
],
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@pnpm/config.matcher": "1100.0.2",
|
|
34
|
-
"@pnpm/config.normalize-registries": "1101.0.
|
|
35
|
-
"@pnpm/deps.path": "1101.0.
|
|
34
|
+
"@pnpm/config.normalize-registries": "1101.0.2",
|
|
35
|
+
"@pnpm/deps.path": "1101.0.4",
|
|
36
36
|
"@pnpm/fs.read-modules-dir": "1100.0.2",
|
|
37
|
-
"@pnpm/installing.modules-yaml": "1101.0.
|
|
38
|
-
"@pnpm/lockfile.detect-dep-types": "1100.0.
|
|
39
|
-
"@pnpm/lockfile.fs": "1100.2.
|
|
40
|
-
"@pnpm/lockfile.
|
|
37
|
+
"@pnpm/installing.modules-yaml": "1101.0.4",
|
|
38
|
+
"@pnpm/lockfile.detect-dep-types": "1100.0.25",
|
|
39
|
+
"@pnpm/lockfile.fs": "1100.2.9",
|
|
40
|
+
"@pnpm/lockfile.peer-edges": "1100.0.0",
|
|
41
|
+
"@pnpm/lockfile.utils": "1102.1.4",
|
|
41
42
|
"@pnpm/npm-package-arg": "^2.0.0",
|
|
42
|
-
"@pnpm/pkg-manifest.reader": "1100.0.
|
|
43
|
-
"@pnpm/store.cafs": "1100.3.
|
|
44
|
-
"@pnpm/store.index": "1100.3.
|
|
43
|
+
"@pnpm/pkg-manifest.reader": "1100.0.20",
|
|
44
|
+
"@pnpm/store.cafs": "1100.3.4",
|
|
45
|
+
"@pnpm/store.index": "1100.3.2",
|
|
45
46
|
"@pnpm/text.ordinal-comparator": "1100.0.0",
|
|
46
|
-
"@pnpm/types": "1102.1.
|
|
47
|
+
"@pnpm/types": "1102.1.1",
|
|
47
48
|
"load-json-file": "^7.0.1",
|
|
48
49
|
"normalize-path": "^3.0.0",
|
|
49
|
-
"p-limit": "^7.3.
|
|
50
|
+
"p-limit": "^7.3.3",
|
|
50
51
|
"path-absolute": "^2.0.0",
|
|
51
52
|
"realpath-missing": "^2.0.0",
|
|
52
53
|
"resolve-link-target": "^3.0.0",
|
|
@@ -55,7 +56,7 @@
|
|
|
55
56
|
"devDependencies": {
|
|
56
57
|
"@jest/globals": "30.4.1",
|
|
57
58
|
"@pnpm/constants": "1102.0.0",
|
|
58
|
-
"@pnpm/deps.inspection.tree-builder": "1101.0.
|
|
59
|
+
"@pnpm/deps.inspection.tree-builder": "1101.0.6",
|
|
59
60
|
"@pnpm/test-fixtures": "1100.0.1",
|
|
60
61
|
"@types/normalize-path": "^3.0.2",
|
|
61
62
|
"@types/semver": "7.8.0"
|