@pnpm/deps.inspection.tree-builder 1101.0.9 → 1101.0.10
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.d.ts +1 -0
- package/lib/buildDependenciesTree.js +3 -0
- package/lib/buildDependentsTree.d.ts +1 -0
- package/lib/buildDependentsTree.js +8 -4
- package/lib/getPkgInfo.d.ts +3 -0
- package/lib/getPkgInfo.js +5 -0
- package/lib/getTree.d.ts +2 -0
- package/lib/resolvePackagePath.d.ts +10 -2
- package/lib/resolvePackagePath.js +115 -7
- package/package.json +14 -14
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# @pnpm/reviewing.dependencies-hierarchy
|
|
2
2
|
|
|
3
|
+
## 1101.0.10
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- `pnpm list` now reports the correct package paths when `nodeLinker` is `hoisted` [#9593](https://github.com/pnpm/pnpm/issues/9593).
|
|
8
|
+
|
|
9
|
+
- Updated dependencies:
|
|
10
|
+
- @pnpm/config.normalize-registries@1101.0.3
|
|
11
|
+
- @pnpm/deps.path@1101.0.6
|
|
12
|
+
- @pnpm/fs.read-modules-dir@1100.0.4
|
|
13
|
+
- @pnpm/installing.modules-yaml@1101.0.6
|
|
14
|
+
- @pnpm/lockfile.detect-dep-types@1100.0.27
|
|
15
|
+
- @pnpm/lockfile.fs@1100.2.13
|
|
16
|
+
- @pnpm/lockfile.peer-edges@1100.0.2
|
|
17
|
+
- @pnpm/lockfile.utils@1102.1.6
|
|
18
|
+
- @pnpm/pkg-manifest.reader@1100.0.22
|
|
19
|
+
- @pnpm/store.cafs@1100.3.7
|
|
20
|
+
- @pnpm/store.index@1101.0.1
|
|
21
|
+
|
|
3
22
|
## 1101.0.9
|
|
4
23
|
|
|
5
24
|
### Patch Changes
|
|
@@ -32,6 +32,7 @@ export interface BuildDependenciesTreeOptions {
|
|
|
32
32
|
modulesDir?: string;
|
|
33
33
|
resolvePeersFromWorkspaceRoot?: boolean;
|
|
34
34
|
virtualStoreDirMaxLength: number;
|
|
35
|
+
nodeLinker?: 'hoisted' | 'isolated' | 'pnp';
|
|
35
36
|
}
|
|
36
37
|
export declare function buildDependenciesTree(projectPaths: string[] | undefined, maybeOpts: BuildDependenciesTreeOptions): Promise<{
|
|
37
38
|
[projectDir: string]: DependenciesTree;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { normalizeRegistriesByScope } from '@pnpm/config.normalize-registries';
|
|
3
|
+
import { withCollapsedVariants } from '@pnpm/deps.path';
|
|
3
4
|
import { readModulesDir } from '@pnpm/fs.read-modules-dir';
|
|
4
5
|
import { readModulesManifest } from '@pnpm/installing.modules-yaml';
|
|
5
6
|
import { detectDepTypes } from '@pnpm/lockfile.detect-dep-types';
|
|
@@ -134,6 +135,8 @@ function createTreeOptions({ treeOpts, modules, modulesDir, registriesByScope, s
|
|
|
134
135
|
modulesDir,
|
|
135
136
|
virtualStoreDir: modules?.virtualStoreDir,
|
|
136
137
|
virtualStoreDirMaxLength: modules?.virtualStoreDirMaxLength ?? treeOpts.virtualStoreDirMaxLength,
|
|
138
|
+
nodeLinker: modules?.nodeLinker ?? treeOpts.nodeLinker,
|
|
139
|
+
hoistedLocations: modules?.hoistedLocations && withCollapsedVariants(modules.hoistedLocations),
|
|
137
140
|
};
|
|
138
141
|
}
|
|
139
142
|
async function expandLinkedProjectsOfEach(pairs, opts) {
|
|
@@ -46,6 +46,7 @@ interface BuildDependentsTreeOptions {
|
|
|
46
46
|
lockfile: LockfileObject;
|
|
47
47
|
nameFormatter?: NameFormatter;
|
|
48
48
|
resolvePeersFromWorkspaceRoot?: boolean;
|
|
49
|
+
nodeLinker?: 'hoisted' | 'isolated' | 'pnp';
|
|
49
50
|
}
|
|
50
51
|
export declare function buildDependentsTree(packages: string[], projectPaths: string[], opts: BuildDependentsTreeOptions): Promise<DependentsTree[]>;
|
|
51
52
|
export {};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { normalizeRegistriesByScope } from '@pnpm/config.normalize-registries';
|
|
3
|
+
import { withCollapsedVariants } from '@pnpm/deps.path';
|
|
3
4
|
import { readModulesManifest } from '@pnpm/installing.modules-yaml';
|
|
4
5
|
import { getLockfileImporterId, } from '@pnpm/lockfile.fs';
|
|
5
6
|
import { getPeerSatisfactionEdgesToSkip } from '@pnpm/lockfile.peer-edges';
|
|
@@ -34,6 +35,9 @@ export async function buildDependentsTree(packages, projectPaths, opts) {
|
|
|
34
35
|
wantedPackages: currentPackages,
|
|
35
36
|
storeDir,
|
|
36
37
|
storeIndex,
|
|
38
|
+
nodeLinker: modules?.nodeLinker ?? opts.nodeLinker,
|
|
39
|
+
hoistedLocations: modules?.hoistedLocations && withCollapsedVariants(modules.hoistedLocations),
|
|
40
|
+
lockfileDir: opts.lockfileDir,
|
|
37
41
|
});
|
|
38
42
|
const trees = collectMatchedTrees(search, {
|
|
39
43
|
reverseMap,
|
|
@@ -177,7 +181,7 @@ function invertGraph(graph) {
|
|
|
177
181
|
*/
|
|
178
182
|
function resolvePackageNodes(graph, currentPackages, opts) {
|
|
179
183
|
const resolved = new Map();
|
|
180
|
-
function walk(serialized, parentDir) {
|
|
184
|
+
function walk(serialized, parentDir, importerDir) {
|
|
181
185
|
const node = graph.nodes.get(serialized);
|
|
182
186
|
if (!node)
|
|
183
187
|
return;
|
|
@@ -194,18 +198,18 @@ function resolvePackageNodes(graph, currentPackages, opts) {
|
|
|
194
198
|
alias: edge.alias,
|
|
195
199
|
currentPackages,
|
|
196
200
|
depTypes: {},
|
|
197
|
-
linkedPathBaseDir:
|
|
201
|
+
linkedPathBaseDir: importerDir,
|
|
198
202
|
parentDir,
|
|
199
203
|
ref: edge.target.nodeId.depPath,
|
|
200
204
|
skipped: new Set(),
|
|
201
205
|
});
|
|
202
206
|
resolved.set(childSerialized, { path: pkgInfo.path, readManifest });
|
|
203
|
-
walk(childSerialized, pkgInfo.path);
|
|
207
|
+
walk(childSerialized, pkgInfo.path, importerDir);
|
|
204
208
|
}
|
|
205
209
|
}
|
|
206
210
|
for (const [serialized, node] of graph.nodes) {
|
|
207
211
|
if (node.nodeId.type === 'importer') {
|
|
208
|
-
walk(serialized, undefined);
|
|
212
|
+
walk(serialized, undefined, path.join(opts.lockfileDir, node.nodeId.importerId));
|
|
209
213
|
}
|
|
210
214
|
}
|
|
211
215
|
return resolved;
|
package/lib/getPkgInfo.d.ts
CHANGED
|
@@ -41,6 +41,9 @@ export interface GetPkgInfoOpts {
|
|
|
41
41
|
* This is needed for subdependencies when using global virtual store.
|
|
42
42
|
*/
|
|
43
43
|
readonly parentDir?: string;
|
|
44
|
+
readonly nodeLinker?: 'hoisted' | 'isolated' | 'pnp';
|
|
45
|
+
readonly hoistedLocations?: Record<string, string[]>;
|
|
46
|
+
readonly lockfileDir?: string;
|
|
44
47
|
}
|
|
45
48
|
export declare function getPkgInfo(opts: GetPkgInfoOpts): {
|
|
46
49
|
pkgInfo: PackageInfo;
|
package/lib/getPkgInfo.js
CHANGED
|
@@ -21,6 +21,11 @@ export function getPkgInfo(opts) {
|
|
|
21
21
|
virtualStoreDirMaxLength: opts.virtualStoreDirMaxLength,
|
|
22
22
|
modulesDir: opts.modulesDir,
|
|
23
23
|
parentDir: opts.parentDir,
|
|
24
|
+
nodeLinker: opts.nodeLinker,
|
|
25
|
+
hoistedLocations: opts.hoistedLocations,
|
|
26
|
+
lockfileDir: opts.lockfileDir,
|
|
27
|
+
projectDir: opts.linkedPathBaseDir,
|
|
28
|
+
version: lockedInfo.version,
|
|
24
29
|
})
|
|
25
30
|
: resolveLinkedPath(opts);
|
|
26
31
|
const version = rewriteLinkVersion(lockedInfo.version || opts.ref, fullPackagePath, opts.rewriteLinkVersionDir);
|
package/lib/getTree.d.ts
CHANGED
|
@@ -27,6 +27,8 @@ export interface BaseTreeOpts {
|
|
|
27
27
|
showDedupedSearchMatches?: boolean;
|
|
28
28
|
graph: DependencyGraph;
|
|
29
29
|
materializationCache: MaterializationCache;
|
|
30
|
+
nodeLinker?: 'hoisted' | 'isolated' | 'pnp';
|
|
31
|
+
hoistedLocations?: Record<string, string[]>;
|
|
30
32
|
}
|
|
31
33
|
interface GetTreeOpts extends BaseTreeOpts {
|
|
32
34
|
maxDepth: number;
|
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
* For local virtual stores, the path is constructed directly.
|
|
5
5
|
* For global virtual stores (where virtualStoreDir is outside modulesDir),
|
|
6
6
|
* symlinks are resolved to find the actual store location.
|
|
7
|
+
* For hoisted linker (where virtual store is empty), packages live where the
|
|
8
|
+
* hoisted linker placed them, recorded in hoistedLocations.
|
|
7
9
|
*/
|
|
8
|
-
export
|
|
10
|
+
export interface ResolvePackagePathOptions {
|
|
9
11
|
depPath: string;
|
|
10
12
|
name: string;
|
|
11
13
|
alias: string;
|
|
@@ -13,4 +15,10 @@ export declare function resolvePackagePath(opts: {
|
|
|
13
15
|
virtualStoreDirMaxLength: number;
|
|
14
16
|
modulesDir?: string;
|
|
15
17
|
parentDir?: string;
|
|
16
|
-
|
|
18
|
+
nodeLinker?: 'hoisted' | 'isolated' | 'pnp';
|
|
19
|
+
hoistedLocations?: Record<string, string[]>;
|
|
20
|
+
lockfileDir?: string;
|
|
21
|
+
projectDir?: string;
|
|
22
|
+
version?: string;
|
|
23
|
+
}
|
|
24
|
+
export declare function resolvePackagePath(opts: ResolvePackagePathOptions): string;
|
|
@@ -1,14 +1,105 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { depPathToFilename } from '@pnpm/deps.path';
|
|
3
|
+
import { depPathToFilename, findHoistedPackageDirs } from '@pnpm/deps.path';
|
|
4
|
+
import { readPackageJsonFromDirSync } from '@pnpm/pkg-manifest.reader';
|
|
5
|
+
export function resolvePackagePath(opts) {
|
|
6
|
+
if (isUnsafePathComponent(opts.name)) {
|
|
7
|
+
return opts.virtualStoreDir;
|
|
8
|
+
}
|
|
9
|
+
if (opts.nodeLinker === 'hoisted') {
|
|
10
|
+
const hoistedPath = resolveHoistedPackagePath(opts);
|
|
11
|
+
if (hoistedPath != null)
|
|
12
|
+
return hoistedPath;
|
|
13
|
+
}
|
|
14
|
+
return resolveVirtualStorePackagePath(opts);
|
|
15
|
+
}
|
|
16
|
+
function resolveHoistedPackagePath(opts) {
|
|
17
|
+
const lockfileDir = opts.lockfileDir ?? opts.projectDir;
|
|
18
|
+
if (!lockfileDir)
|
|
19
|
+
return undefined;
|
|
20
|
+
const hoistedPaths = findHoistedPackageDirs(opts.hoistedLocations, opts.depPath, lockfileDir);
|
|
21
|
+
if (hoistedPaths.length)
|
|
22
|
+
return pickHoistedDir(hoistedPaths, { ...opts, lockfileDir });
|
|
23
|
+
return findHoistedByAlias(opts, lockfileDir);
|
|
24
|
+
}
|
|
4
25
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
26
|
+
* Picks the copy that Node.js resolves from the parent package, or from the
|
|
27
|
+
* project for a direct dependency: the one in the closest modules directory
|
|
28
|
+
* above it. Copies on disk under the edge's alias are preferred, since one
|
|
29
|
+
* depPath gets a directory per alias it is installed under. Falls back to any
|
|
30
|
+
* copy on disk, then to the recorded one under the alias, then to the first
|
|
31
|
+
* recorded one.
|
|
10
32
|
*/
|
|
11
|
-
|
|
33
|
+
function pickHoistedDir(recordedDirs, opts) {
|
|
34
|
+
const existing = recordedDirs.filter((dir) => fs.existsSync(dir));
|
|
35
|
+
const existingUnderAlias = existing.filter((dir) => isInstalledUnder(dir, opts.alias));
|
|
36
|
+
const candidates = existingUnderAlias.length ? existingUnderAlias : existing;
|
|
37
|
+
const resolveFrom = opts.parentDir ?? opts.projectDir;
|
|
38
|
+
if (resolveFrom) {
|
|
39
|
+
const relativeModulesDir = getRelativeModulesDir(opts.lockfileDir, opts.modulesDir);
|
|
40
|
+
const closest = candidates
|
|
41
|
+
.map((dir) => ({ dir, ownerDir: getOwnerDir(dir, relativeModulesDir) }))
|
|
42
|
+
.filter(({ ownerDir }) => isSameOrSubdir(ownerDir, resolveFrom))
|
|
43
|
+
.sort((a, b) => b.ownerDir.length - a.ownerDir.length)[0];
|
|
44
|
+
if (closest)
|
|
45
|
+
return closest.dir;
|
|
46
|
+
}
|
|
47
|
+
return candidates[0] ??
|
|
48
|
+
recordedDirs.find((dir) => isInstalledUnder(dir, opts.alias)) ??
|
|
49
|
+
recordedDirs[0];
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Whether `pkgDir` is `<modules dir>/<alias>`: its trailing segments spell the
|
|
53
|
+
* whole alias, not just the name of a scoped package.
|
|
54
|
+
*/
|
|
55
|
+
function isInstalledUnder(pkgDir, alias) {
|
|
56
|
+
const aliasSuffix = path.join(path.sep, alias);
|
|
57
|
+
return pkgDir.endsWith(aliasSuffix) &&
|
|
58
|
+
!path.basename(pkgDir.slice(0, -aliasSuffix.length)).startsWith('@');
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The directory whose modules directory holds `pkgDir`.
|
|
62
|
+
*/
|
|
63
|
+
function getOwnerDir(pkgDir, relativeModulesDir) {
|
|
64
|
+
let modulesDir = path.dirname(pkgDir);
|
|
65
|
+
if (path.basename(modulesDir).startsWith('@'))
|
|
66
|
+
modulesDir = path.dirname(modulesDir);
|
|
67
|
+
const rootModulesDirSuffix = path.sep + relativeModulesDir;
|
|
68
|
+
return modulesDir.endsWith(rootModulesDirSuffix)
|
|
69
|
+
? modulesDir.slice(0, -rootModulesDirSuffix.length)
|
|
70
|
+
: path.dirname(modulesDir);
|
|
71
|
+
}
|
|
72
|
+
function isSameOrSubdir(parent, dir) {
|
|
73
|
+
return dir === parent || dir.startsWith(parent.endsWith(path.sep) ? parent : parent + path.sep);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Probes `<alias>` in the modules directory of the project and of the root,
|
|
77
|
+
* because the hoisted linker places a dependency under its alias.
|
|
78
|
+
*/
|
|
79
|
+
function findHoistedByAlias(opts, lockfileDir) {
|
|
80
|
+
if (isUnsafePathComponent(opts.alias))
|
|
81
|
+
return undefined;
|
|
82
|
+
const relativeModulesDir = getRelativeModulesDir(lockfileDir, opts.modulesDir);
|
|
83
|
+
const rootModulesDir = opts.modulesDir ?? path.join(lockfileDir, 'node_modules');
|
|
84
|
+
const candidates = opts.projectDir ? [path.resolve(opts.projectDir, relativeModulesDir, opts.alias)] : [];
|
|
85
|
+
candidates.push(path.join(rootModulesDir, opts.alias));
|
|
86
|
+
return candidates.find((candidate) => candidateMatchesVersion(candidate, opts.version));
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* The modules directory relative to the lockfile directory, such as
|
|
90
|
+
* `node_modules` or `www/modules`. Falls back to its last segment when it
|
|
91
|
+
* lies outside the lockfile directory.
|
|
92
|
+
*/
|
|
93
|
+
function getRelativeModulesDir(lockfileDir, modulesDir) {
|
|
94
|
+
if (!modulesDir)
|
|
95
|
+
return 'node_modules';
|
|
96
|
+
const relative = path.relative(lockfileDir, modulesDir);
|
|
97
|
+
if (!relative || relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
|
|
98
|
+
return path.basename(modulesDir);
|
|
99
|
+
}
|
|
100
|
+
return relative;
|
|
101
|
+
}
|
|
102
|
+
function resolveVirtualStorePackagePath(opts) {
|
|
12
103
|
const fullPackagePath = path.join(opts.virtualStoreDir, depPathToFilename(opts.depPath, opts.virtualStoreDirMaxLength), 'node_modules', opts.name);
|
|
13
104
|
// Resolve symlink for global virtual store.
|
|
14
105
|
if (!isGlobalVirtualStore(opts.virtualStoreDir, opts.modulesDir))
|
|
@@ -46,4 +137,21 @@ function getSymlinkNodeModulesDir(opts) {
|
|
|
46
137
|
? path.dirname(nodeModulesDir)
|
|
47
138
|
: nodeModulesDir;
|
|
48
139
|
}
|
|
140
|
+
function candidateMatchesVersion(candidate, expectedVersion) {
|
|
141
|
+
if (!expectedVersion)
|
|
142
|
+
return fs.existsSync(candidate);
|
|
143
|
+
try {
|
|
144
|
+
const manifest = readPackageJsonFromDirSync(candidate);
|
|
145
|
+
return manifest.version === expectedVersion;
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function isUnsafePathComponent(component) {
|
|
152
|
+
return (path.isAbsolute(component) ||
|
|
153
|
+
component.startsWith('\\') ||
|
|
154
|
+
component.includes(':') ||
|
|
155
|
+
component.split(/[/\\]/).some((part) => part === '..' || part === '.'));
|
|
156
|
+
}
|
|
49
157
|
//# 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.10",
|
|
4
4
|
"description": "Creates a dependencies hierarchy for a symlinked `node_modules`",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pnpm",
|
|
@@ -31,18 +31,18 @@
|
|
|
31
31
|
],
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@pnpm/config.matcher": "1100.0.3",
|
|
34
|
-
"@pnpm/config.normalize-registries": "1101.0.
|
|
35
|
-
"@pnpm/deps.path": "1101.0.
|
|
36
|
-
"@pnpm/fs.read-modules-dir": "1100.0.
|
|
37
|
-
"@pnpm/installing.modules-yaml": "1101.0.
|
|
38
|
-
"@pnpm/lockfile.detect-dep-types": "1100.0.
|
|
39
|
-
"@pnpm/lockfile.fs": "1100.2.
|
|
40
|
-
"@pnpm/lockfile.peer-edges": "1100.0.
|
|
41
|
-
"@pnpm/lockfile.utils": "1102.1.
|
|
34
|
+
"@pnpm/config.normalize-registries": "1101.0.3",
|
|
35
|
+
"@pnpm/deps.path": "1101.0.6",
|
|
36
|
+
"@pnpm/fs.read-modules-dir": "1100.0.4",
|
|
37
|
+
"@pnpm/installing.modules-yaml": "1101.0.6",
|
|
38
|
+
"@pnpm/lockfile.detect-dep-types": "1100.0.27",
|
|
39
|
+
"@pnpm/lockfile.fs": "1100.2.13",
|
|
40
|
+
"@pnpm/lockfile.peer-edges": "1100.0.2",
|
|
41
|
+
"@pnpm/lockfile.utils": "1102.1.6",
|
|
42
42
|
"@pnpm/npm-package-arg": "^2.0.0",
|
|
43
|
-
"@pnpm/pkg-manifest.reader": "1100.0.
|
|
44
|
-
"@pnpm/store.cafs": "1100.3.
|
|
45
|
-
"@pnpm/store.index": "1101.0.
|
|
43
|
+
"@pnpm/pkg-manifest.reader": "1100.0.22",
|
|
44
|
+
"@pnpm/store.cafs": "1100.3.7",
|
|
45
|
+
"@pnpm/store.index": "1101.0.1",
|
|
46
46
|
"@pnpm/text.ordinal-comparator": "1100.0.0",
|
|
47
47
|
"@pnpm/types": "1102.1.1",
|
|
48
48
|
"load-json-file": "^7.0.1",
|
|
@@ -55,8 +55,8 @@
|
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
57
|
"@jest/globals": "30.4.1",
|
|
58
|
-
"@pnpm/constants": "1102.0.
|
|
59
|
-
"@pnpm/deps.inspection.tree-builder": "1101.0.
|
|
58
|
+
"@pnpm/constants": "1102.0.1",
|
|
59
|
+
"@pnpm/deps.inspection.tree-builder": "1101.0.10",
|
|
60
60
|
"@pnpm/test-fixtures": "1100.0.1",
|
|
61
61
|
"@types/normalize-path": "^3.0.2",
|
|
62
62
|
"@types/semver": "7.8.0"
|