@yarnpkg/nm 3.0.2 → 4.0.0-rc.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/lib/buildNodeModulesTree.d.ts +75 -0
- package/lib/buildNodeModulesTree.js +600 -0
- package/lib/hoist.d.ts +96 -0
- package/lib/hoist.js +858 -0
- package/lib/index.d.ts +6 -0
- package/lib/index.js +10 -0
- package/package.json +7 -6
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { Project, MessageName } from '@yarnpkg/core';
|
|
2
|
+
import { PortablePath, Filename } from '@yarnpkg/fslib';
|
|
3
|
+
import { PnpApi } from '@yarnpkg/pnp';
|
|
4
|
+
export declare enum LinkType {
|
|
5
|
+
HARD = "HARD",
|
|
6
|
+
SOFT = "SOFT"
|
|
7
|
+
}
|
|
8
|
+
export declare enum NodeModulesHoistingLimits {
|
|
9
|
+
WORKSPACES = "workspaces",
|
|
10
|
+
DEPENDENCIES = "dependencies",
|
|
11
|
+
NONE = "none"
|
|
12
|
+
}
|
|
13
|
+
export declare type NodeModulesBaseNode = {
|
|
14
|
+
dirList: Set<Filename>;
|
|
15
|
+
};
|
|
16
|
+
export declare type NodeModulesPackageNode = {
|
|
17
|
+
locator: LocatorKey;
|
|
18
|
+
target: PortablePath;
|
|
19
|
+
linkType: LinkType;
|
|
20
|
+
dirList?: undefined;
|
|
21
|
+
nodePath: string;
|
|
22
|
+
aliases: Array<string>;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Node modules tree - a map of every folder within the node_modules, along with their
|
|
26
|
+
* directory listing and whether they are a symlink and their location.
|
|
27
|
+
*
|
|
28
|
+
* Sample contents:
|
|
29
|
+
* /home/user/project/node_modules -> {dirList: ['foo', 'bar']}
|
|
30
|
+
* /home/user/project/node_modules/foo -> {target: '/home/user/project/.yarn/.cache/foo.zip/node_modules/foo', linkType: 'HARD'}
|
|
31
|
+
* /home/user/project/node_modules/bar -> {target: '/home/user/project/packages/bar', linkType: 'SOFT'}
|
|
32
|
+
*/
|
|
33
|
+
export declare type NodeModulesTree = Map<PortablePath, NodeModulesBaseNode | NodeModulesPackageNode>;
|
|
34
|
+
export declare type NodeModulesTreeErrors = Array<{
|
|
35
|
+
messageName: MessageName;
|
|
36
|
+
text: string;
|
|
37
|
+
}>;
|
|
38
|
+
export interface NodeModulesTreeOptions {
|
|
39
|
+
pnpifyFs?: boolean;
|
|
40
|
+
validateExternalSoftLinks?: boolean;
|
|
41
|
+
hoistingLimitsByCwd?: Map<PortablePath, NodeModulesHoistingLimits>;
|
|
42
|
+
selfReferencesByCwd?: Map<PortablePath, Boolean>;
|
|
43
|
+
project?: Project;
|
|
44
|
+
}
|
|
45
|
+
/** Package locator key for usage inside maps */
|
|
46
|
+
declare type LocatorKey = string;
|
|
47
|
+
/**
|
|
48
|
+
* Returns path to archive, if package location is inside the archive.
|
|
49
|
+
*
|
|
50
|
+
* @param packagePath package location
|
|
51
|
+
*
|
|
52
|
+
* @returns path to archive is location is insde the archive or null otherwise
|
|
53
|
+
*/
|
|
54
|
+
export declare const getArchivePath: (packagePath: PortablePath) => PortablePath | null;
|
|
55
|
+
/**
|
|
56
|
+
* Retrieve full package list and build hoisted `node_modules` directories
|
|
57
|
+
* representation in-memory.
|
|
58
|
+
*
|
|
59
|
+
* @param pnp PnP API
|
|
60
|
+
*
|
|
61
|
+
* @returns hoisted `node_modules` directories representation in-memory
|
|
62
|
+
*/
|
|
63
|
+
export declare const buildNodeModulesTree: (pnp: PnpApi, options: NodeModulesTreeOptions) => {
|
|
64
|
+
tree: NodeModulesTree | null;
|
|
65
|
+
errors: NodeModulesTreeErrors;
|
|
66
|
+
preserveSymlinksRequired: boolean;
|
|
67
|
+
};
|
|
68
|
+
export declare type NodeModulesLocatorMap = Map<LocatorKey, {
|
|
69
|
+
target: PortablePath;
|
|
70
|
+
linkType: LinkType;
|
|
71
|
+
locations: Array<PortablePath>;
|
|
72
|
+
aliases: Array<string>;
|
|
73
|
+
}>;
|
|
74
|
+
export declare const buildLocatorMap: (nodeModulesTree: NodeModulesTree) => NodeModulesLocatorMap;
|
|
75
|
+
export {};
|
|
@@ -0,0 +1,600 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildLocatorMap = exports.buildNodeModulesTree = exports.getArchivePath = exports.NodeModulesHoistingLimits = exports.LinkType = void 0;
|
|
4
|
+
const core_1 = require("@yarnpkg/core");
|
|
5
|
+
const fslib_1 = require("@yarnpkg/fslib");
|
|
6
|
+
const fslib_2 = require("@yarnpkg/fslib");
|
|
7
|
+
const hoist_1 = require("./hoist");
|
|
8
|
+
// Babel doesn't support const enums, thats why we use non-const enum for LinkType in @yarnpkg/pnp
|
|
9
|
+
// But because of this TypeScript requires @yarnpkg/pnp during runtime
|
|
10
|
+
// To prevent this we redeclare LinkType enum here, to not depend on @yarnpkg/pnp during runtime
|
|
11
|
+
var LinkType;
|
|
12
|
+
(function (LinkType) {
|
|
13
|
+
LinkType["HARD"] = "HARD";
|
|
14
|
+
LinkType["SOFT"] = "SOFT";
|
|
15
|
+
})(LinkType = exports.LinkType || (exports.LinkType = {}));
|
|
16
|
+
var NodeModulesHoistingLimits;
|
|
17
|
+
(function (NodeModulesHoistingLimits) {
|
|
18
|
+
NodeModulesHoistingLimits["WORKSPACES"] = "workspaces";
|
|
19
|
+
NodeModulesHoistingLimits["DEPENDENCIES"] = "dependencies";
|
|
20
|
+
NodeModulesHoistingLimits["NONE"] = "none";
|
|
21
|
+
})(NodeModulesHoistingLimits = exports.NodeModulesHoistingLimits || (exports.NodeModulesHoistingLimits = {}));
|
|
22
|
+
/** node_modules path segment */
|
|
23
|
+
const NODE_MODULES = `node_modules`;
|
|
24
|
+
/**
|
|
25
|
+
* The workspace name suffix used internally by this implementation and appeneded to the name of workspace package.
|
|
26
|
+
* It is needed to create and distinguuish special nodes for workspaces
|
|
27
|
+
*/
|
|
28
|
+
const WORKSPACE_NAME_SUFFIX = `$wsroot$`;
|
|
29
|
+
/**
|
|
30
|
+
* Returns path to archive, if package location is inside the archive.
|
|
31
|
+
*
|
|
32
|
+
* @param packagePath package location
|
|
33
|
+
*
|
|
34
|
+
* @returns path to archive is location is insde the archive or null otherwise
|
|
35
|
+
*/
|
|
36
|
+
const getArchivePath = (packagePath) => packagePath.indexOf(`.zip/${NODE_MODULES}/`) >= 0 ?
|
|
37
|
+
fslib_1.npath.toPortablePath(packagePath.split(`/${NODE_MODULES}/`)[0]) :
|
|
38
|
+
null;
|
|
39
|
+
exports.getArchivePath = getArchivePath;
|
|
40
|
+
/**
|
|
41
|
+
* Retrieve full package list and build hoisted `node_modules` directories
|
|
42
|
+
* representation in-memory.
|
|
43
|
+
*
|
|
44
|
+
* @param pnp PnP API
|
|
45
|
+
*
|
|
46
|
+
* @returns hoisted `node_modules` directories representation in-memory
|
|
47
|
+
*/
|
|
48
|
+
const buildNodeModulesTree = (pnp, options) => {
|
|
49
|
+
const { packageTree, hoistingLimits, errors, preserveSymlinksRequired } = buildPackageTree(pnp, options);
|
|
50
|
+
let tree = null;
|
|
51
|
+
if (errors.length === 0) {
|
|
52
|
+
const hoistedTree = (0, hoist_1.hoist)(packageTree, { hoistingLimits });
|
|
53
|
+
tree = populateNodeModulesTree(pnp, hoistedTree, options);
|
|
54
|
+
}
|
|
55
|
+
return { tree, errors, preserveSymlinksRequired };
|
|
56
|
+
};
|
|
57
|
+
exports.buildNodeModulesTree = buildNodeModulesTree;
|
|
58
|
+
const stringifyLocator = (locator) => `${locator.name}@${locator.reference}`;
|
|
59
|
+
const buildLocatorMap = (nodeModulesTree) => {
|
|
60
|
+
const map = new Map();
|
|
61
|
+
for (const [location, val] of nodeModulesTree.entries()) {
|
|
62
|
+
if (!val.dirList) {
|
|
63
|
+
let entry = map.get(val.locator);
|
|
64
|
+
if (!entry) {
|
|
65
|
+
entry = { target: val.target, linkType: val.linkType, locations: [], aliases: val.aliases };
|
|
66
|
+
map.set(val.locator, entry);
|
|
67
|
+
}
|
|
68
|
+
entry.locations.push(location);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
for (const val of map.values()) {
|
|
72
|
+
// Sort locations by depth first and then alphabetically for determinism
|
|
73
|
+
val.locations = val.locations.sort((loc1, loc2) => {
|
|
74
|
+
const len1 = loc1.split(fslib_1.ppath.delimiter).length;
|
|
75
|
+
const len2 = loc2.split(fslib_1.ppath.delimiter).length;
|
|
76
|
+
if (loc2 === loc1) {
|
|
77
|
+
return 0;
|
|
78
|
+
}
|
|
79
|
+
else if (len1 !== len2) {
|
|
80
|
+
return len2 - len1;
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
return loc2 > loc1 ? 1 : -1;
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
return map;
|
|
88
|
+
};
|
|
89
|
+
exports.buildLocatorMap = buildLocatorMap;
|
|
90
|
+
const areRealLocatorsEqual = (a, b) => {
|
|
91
|
+
const realA = core_1.structUtils.isVirtualLocator(a) ? core_1.structUtils.devirtualizeLocator(a) : a;
|
|
92
|
+
const realB = core_1.structUtils.isVirtualLocator(b) ? core_1.structUtils.devirtualizeLocator(b) : b;
|
|
93
|
+
return core_1.structUtils.areLocatorsEqual(realA, realB);
|
|
94
|
+
};
|
|
95
|
+
const isExternalSoftLink = (pkg, locator, pnp, topPkgPortableLocation) => {
|
|
96
|
+
if (pkg.linkType !== LinkType.SOFT)
|
|
97
|
+
return false;
|
|
98
|
+
const realSoftLinkPath = fslib_1.npath.toPortablePath(pnp.resolveVirtual && locator.reference && locator.reference.startsWith(`virtual:`) ? pnp.resolveVirtual(pkg.packageLocation) : pkg.packageLocation);
|
|
99
|
+
return fslib_1.ppath.contains(topPkgPortableLocation, realSoftLinkPath) === null;
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* Builds a map representing layout of nested workspaces and internal portals on the file system.
|
|
103
|
+
*/
|
|
104
|
+
const buildWorkspaceMap = (pnp) => {
|
|
105
|
+
const topPkg = pnp.getPackageInformation(pnp.topLevel);
|
|
106
|
+
if (topPkg === null)
|
|
107
|
+
throw new Error(`Assertion failed: Expected the top-level package to have been registered`);
|
|
108
|
+
const topLocator = pnp.findPackageLocator(topPkg.packageLocation);
|
|
109
|
+
if (topLocator === null)
|
|
110
|
+
throw new Error(`Assertion failed: Expected the top-level package to have a physical locator`);
|
|
111
|
+
const topPkgPortableLocation = fslib_1.npath.toPortablePath(topPkg.packageLocation.slice(0, -1));
|
|
112
|
+
const workspaceMap = new Map();
|
|
113
|
+
const workspaceTree = { children: new Map() };
|
|
114
|
+
const pnpRoots = pnp.getDependencyTreeRoots();
|
|
115
|
+
// Workspace and internal portal locations to locators map
|
|
116
|
+
const workspaceLikeLocators = new Map();
|
|
117
|
+
const seen = new Set();
|
|
118
|
+
const visit = (locator, parentLocator) => {
|
|
119
|
+
const locatorKey = stringifyLocator(locator);
|
|
120
|
+
if (seen.has(locatorKey))
|
|
121
|
+
return;
|
|
122
|
+
seen.add(locatorKey);
|
|
123
|
+
const pkg = pnp.getPackageInformation(locator);
|
|
124
|
+
if (pkg) {
|
|
125
|
+
const parentLocatorKey = parentLocator ? stringifyLocator(parentLocator) : ``;
|
|
126
|
+
if (stringifyLocator(locator) !== parentLocatorKey && pkg.linkType === LinkType.SOFT && !isExternalSoftLink(pkg, locator, pnp, topPkgPortableLocation)) {
|
|
127
|
+
const location = getRealPackageLocation(pkg, locator, pnp);
|
|
128
|
+
const prevLocator = workspaceLikeLocators.get(location);
|
|
129
|
+
// Give workspaces a priority over portals and other protocols pointing to the same location
|
|
130
|
+
// The devDependencies are not installed for portals, but installed for workspaces
|
|
131
|
+
if (!prevLocator || locator.reference.startsWith(`workspace:`)) {
|
|
132
|
+
workspaceLikeLocators.set(location, locator);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
for (const [name, referencish] of pkg.packageDependencies) {
|
|
136
|
+
if (referencish !== null) {
|
|
137
|
+
if (!pkg.packagePeers.has(name)) {
|
|
138
|
+
visit(pnp.getLocator(name, referencish), locator);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
for (const locator of pnpRoots)
|
|
145
|
+
visit(locator, null);
|
|
146
|
+
const cwdSegments = topPkgPortableLocation.split(fslib_1.ppath.sep);
|
|
147
|
+
for (const locator of workspaceLikeLocators.values()) {
|
|
148
|
+
const pkg = pnp.getPackageInformation(locator);
|
|
149
|
+
const location = fslib_1.npath.toPortablePath(pkg.packageLocation.slice(0, -1));
|
|
150
|
+
const segments = location.split(fslib_1.ppath.sep).slice(cwdSegments.length);
|
|
151
|
+
let node = workspaceTree;
|
|
152
|
+
for (const segment of segments) {
|
|
153
|
+
let nextNode = node.children.get(segment);
|
|
154
|
+
if (!nextNode) {
|
|
155
|
+
nextNode = { children: new Map() };
|
|
156
|
+
node.children.set(segment, nextNode);
|
|
157
|
+
}
|
|
158
|
+
node = nextNode;
|
|
159
|
+
}
|
|
160
|
+
node.workspaceLocator = locator;
|
|
161
|
+
}
|
|
162
|
+
const addWorkspace = (node, parentWorkspaceLocator) => {
|
|
163
|
+
if (node.workspaceLocator) {
|
|
164
|
+
const parentLocatorKey = stringifyLocator(parentWorkspaceLocator);
|
|
165
|
+
let dependencies = workspaceMap.get(parentLocatorKey);
|
|
166
|
+
if (!dependencies) {
|
|
167
|
+
dependencies = new Set();
|
|
168
|
+
workspaceMap.set(parentLocatorKey, dependencies);
|
|
169
|
+
}
|
|
170
|
+
dependencies.add(node.workspaceLocator);
|
|
171
|
+
}
|
|
172
|
+
for (const child of node.children.values()) {
|
|
173
|
+
addWorkspace(child, node.workspaceLocator || parentWorkspaceLocator);
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
for (const child of workspaceTree.children.values())
|
|
177
|
+
addWorkspace(child, workspaceTree.workspaceLocator);
|
|
178
|
+
return workspaceMap;
|
|
179
|
+
};
|
|
180
|
+
/**
|
|
181
|
+
* Traverses PnP tree and produces input for the `RawHoister`
|
|
182
|
+
*
|
|
183
|
+
* @param pnp PnP API
|
|
184
|
+
*
|
|
185
|
+
* @returns package tree, packages info and locators
|
|
186
|
+
*/
|
|
187
|
+
const buildPackageTree = (pnp, options) => {
|
|
188
|
+
const errors = [];
|
|
189
|
+
let preserveSymlinksRequired = false;
|
|
190
|
+
const hoistingLimits = new Map();
|
|
191
|
+
const workspaceMap = buildWorkspaceMap(pnp);
|
|
192
|
+
const topPkg = pnp.getPackageInformation(pnp.topLevel);
|
|
193
|
+
if (topPkg === null)
|
|
194
|
+
throw new Error(`Assertion failed: Expected the top-level package to have been registered`);
|
|
195
|
+
const topLocator = pnp.findPackageLocator(topPkg.packageLocation);
|
|
196
|
+
if (topLocator === null)
|
|
197
|
+
throw new Error(`Assertion failed: Expected the top-level package to have a physical locator`);
|
|
198
|
+
const topPkgPortableLocation = fslib_1.npath.toPortablePath(topPkg.packageLocation.slice(0, -1));
|
|
199
|
+
const packageTree = {
|
|
200
|
+
name: topLocator.name,
|
|
201
|
+
identName: topLocator.name,
|
|
202
|
+
reference: topLocator.reference,
|
|
203
|
+
peerNames: topPkg.packagePeers,
|
|
204
|
+
dependencies: new Set(),
|
|
205
|
+
dependencyKind: hoist_1.HoisterDependencyKind.WORKSPACE,
|
|
206
|
+
};
|
|
207
|
+
const nodes = new Map();
|
|
208
|
+
const getNodeKey = (name, locator) => `${stringifyLocator(locator)}:${name}`;
|
|
209
|
+
const addPackageToTree = (name, pkg, locator, parent, parentPkg, parentDependencies, parentRelativeCwd, isHoistBorder) => {
|
|
210
|
+
var _a, _b;
|
|
211
|
+
const nodeKey = getNodeKey(name, locator);
|
|
212
|
+
let node = nodes.get(nodeKey);
|
|
213
|
+
const isSeen = !!node;
|
|
214
|
+
if (!isSeen && locator.name === topLocator.name && locator.reference === topLocator.reference) {
|
|
215
|
+
node = packageTree;
|
|
216
|
+
nodes.set(nodeKey, packageTree);
|
|
217
|
+
}
|
|
218
|
+
const isExternalSoftLinkPackage = isExternalSoftLink(pkg, locator, pnp, topPkgPortableLocation);
|
|
219
|
+
if (!node) {
|
|
220
|
+
let dependencyKind = hoist_1.HoisterDependencyKind.REGULAR;
|
|
221
|
+
if (isExternalSoftLinkPackage)
|
|
222
|
+
dependencyKind = hoist_1.HoisterDependencyKind.EXTERNAL_SOFT_LINK;
|
|
223
|
+
else if (pkg.linkType === LinkType.SOFT && locator.name.endsWith(WORKSPACE_NAME_SUFFIX))
|
|
224
|
+
dependencyKind = hoist_1.HoisterDependencyKind.WORKSPACE;
|
|
225
|
+
node = {
|
|
226
|
+
name,
|
|
227
|
+
identName: locator.name,
|
|
228
|
+
reference: locator.reference,
|
|
229
|
+
dependencies: new Set(),
|
|
230
|
+
// View peer dependencies as regular dependencies for workspaces
|
|
231
|
+
// (meeting workspace peer dependency constraints is sometimes hard, sometimes impossible for the nm linker)
|
|
232
|
+
peerNames: dependencyKind === hoist_1.HoisterDependencyKind.WORKSPACE ? new Set() : pkg.packagePeers,
|
|
233
|
+
dependencyKind,
|
|
234
|
+
};
|
|
235
|
+
nodes.set(nodeKey, node);
|
|
236
|
+
}
|
|
237
|
+
let hoistPriority;
|
|
238
|
+
if (isExternalSoftLinkPackage)
|
|
239
|
+
// External soft link dependencies have the highest priority - we don't want to install inside them
|
|
240
|
+
hoistPriority = 2;
|
|
241
|
+
else if (parentPkg.linkType === LinkType.SOFT)
|
|
242
|
+
// Internal soft link dependencies should have priority over transitive dependencies - to maximize chances having only one top-level node_modules
|
|
243
|
+
hoistPriority = 1;
|
|
244
|
+
else
|
|
245
|
+
hoistPriority = 0;
|
|
246
|
+
node.hoistPriority = Math.max(node.hoistPriority || 0, hoistPriority);
|
|
247
|
+
if (isHoistBorder && !isExternalSoftLinkPackage) {
|
|
248
|
+
const parentLocatorKey = stringifyLocator({ name: parent.identName, reference: parent.reference });
|
|
249
|
+
const dependencyBorders = hoistingLimits.get(parentLocatorKey) || new Set();
|
|
250
|
+
hoistingLimits.set(parentLocatorKey, dependencyBorders);
|
|
251
|
+
dependencyBorders.add(node.name);
|
|
252
|
+
}
|
|
253
|
+
const allDependencies = new Map(pkg.packageDependencies);
|
|
254
|
+
if (options.project) {
|
|
255
|
+
const workspace = options.project.workspacesByCwd.get(fslib_1.npath.toPortablePath(pkg.packageLocation.slice(0, -1)));
|
|
256
|
+
if (workspace) {
|
|
257
|
+
const peerCandidates = new Set([
|
|
258
|
+
...Array.from(workspace.manifest.peerDependencies.values(), x => core_1.structUtils.stringifyIdent(x)),
|
|
259
|
+
...Array.from(workspace.manifest.peerDependenciesMeta.keys()),
|
|
260
|
+
]);
|
|
261
|
+
for (const peerName of peerCandidates) {
|
|
262
|
+
if (!allDependencies.has(peerName)) {
|
|
263
|
+
allDependencies.set(peerName, parentDependencies.get(peerName) || null);
|
|
264
|
+
node.peerNames.add(peerName);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
const locatorKey = stringifyLocator({ name: locator.name.replace(WORKSPACE_NAME_SUFFIX, ``), reference: locator.reference });
|
|
270
|
+
const innerWorkspaces = workspaceMap.get(locatorKey);
|
|
271
|
+
if (innerWorkspaces) {
|
|
272
|
+
for (const workspaceLocator of innerWorkspaces) {
|
|
273
|
+
allDependencies.set(`${workspaceLocator.name}${WORKSPACE_NAME_SUFFIX}`, workspaceLocator.reference);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
if (pkg !== parentPkg || pkg.linkType !== LinkType.SOFT || (!isExternalSoftLinkPackage && (!options.selfReferencesByCwd || options.selfReferencesByCwd.get(parentRelativeCwd))))
|
|
277
|
+
parent.dependencies.add(node);
|
|
278
|
+
const isWorkspaceDependency = locator !== topLocator && pkg.linkType === LinkType.SOFT && !locator.name.endsWith(WORKSPACE_NAME_SUFFIX) && !isExternalSoftLinkPackage;
|
|
279
|
+
if (!isSeen && !isWorkspaceDependency) {
|
|
280
|
+
const siblingPortalDependencyMap = new Map();
|
|
281
|
+
for (const [depName, referencish] of allDependencies) {
|
|
282
|
+
if (referencish !== null) {
|
|
283
|
+
const depLocator = pnp.getLocator(depName, referencish);
|
|
284
|
+
const pkgLocator = pnp.getLocator(depName.replace(WORKSPACE_NAME_SUFFIX, ``), referencish);
|
|
285
|
+
const depPkg = pnp.getPackageInformation(pkgLocator);
|
|
286
|
+
if (depPkg === null)
|
|
287
|
+
throw new Error(`Assertion failed: Expected the package to have been registered`);
|
|
288
|
+
const isExternalSoftLinkDep = isExternalSoftLink(depPkg, depLocator, pnp, topPkgPortableLocation);
|
|
289
|
+
if (options.validateExternalSoftLinks && options.project && isExternalSoftLinkDep) {
|
|
290
|
+
if (depPkg.packageDependencies.size > 0)
|
|
291
|
+
preserveSymlinksRequired = true;
|
|
292
|
+
for (const [name, referencish] of depPkg.packageDependencies) {
|
|
293
|
+
if (referencish !== null) {
|
|
294
|
+
const portalDependencyLocator = core_1.structUtils.parseLocator(Array.isArray(referencish) ? `${referencish[0]}@${referencish[1]}` : `${name}@${referencish}`);
|
|
295
|
+
// Ignore self-references during portal hoistability check
|
|
296
|
+
if (stringifyLocator(portalDependencyLocator) !== stringifyLocator(depLocator)) {
|
|
297
|
+
const parentDependencyReferencish = allDependencies.get(name);
|
|
298
|
+
if (parentDependencyReferencish) {
|
|
299
|
+
const parentDependencyLocator = core_1.structUtils.parseLocator(Array.isArray(parentDependencyReferencish) ? `${parentDependencyReferencish[0]}@${parentDependencyReferencish[1]}` : `${name}@${parentDependencyReferencish}`);
|
|
300
|
+
if (!areRealLocatorsEqual(parentDependencyLocator, portalDependencyLocator)) {
|
|
301
|
+
errors.push({
|
|
302
|
+
messageName: core_1.MessageName.NM_CANT_INSTALL_EXTERNAL_SOFT_LINK,
|
|
303
|
+
text: `Cannot link ${core_1.structUtils.prettyIdent(options.project.configuration, core_1.structUtils.parseIdent(depLocator.name))} ` +
|
|
304
|
+
`into ${core_1.structUtils.prettyLocator(options.project.configuration, core_1.structUtils.parseLocator(`${locator.name}@${locator.reference}`))} ` +
|
|
305
|
+
`dependency ${core_1.structUtils.prettyLocator(options.project.configuration, portalDependencyLocator)} ` +
|
|
306
|
+
`conflicts with parent dependency ${core_1.structUtils.prettyLocator(options.project.configuration, parentDependencyLocator)}`,
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
else {
|
|
311
|
+
const siblingPortalDependency = siblingPortalDependencyMap.get(name);
|
|
312
|
+
if (siblingPortalDependency) {
|
|
313
|
+
const siblingReferncish = siblingPortalDependency.target;
|
|
314
|
+
const siblingPortalDependencyLocator = core_1.structUtils.parseLocator(Array.isArray(siblingReferncish) ? `${siblingReferncish[0]}@${siblingReferncish[1]}` : `${name}@${siblingReferncish}`);
|
|
315
|
+
if (!areRealLocatorsEqual(siblingPortalDependencyLocator, portalDependencyLocator)) {
|
|
316
|
+
errors.push({
|
|
317
|
+
messageName: core_1.MessageName.NM_CANT_INSTALL_EXTERNAL_SOFT_LINK,
|
|
318
|
+
text: `Cannot link ${core_1.structUtils.prettyIdent(options.project.configuration, core_1.structUtils.parseIdent(depLocator.name))} ` +
|
|
319
|
+
`into ${core_1.structUtils.prettyLocator(options.project.configuration, core_1.structUtils.parseLocator(`${locator.name}@${locator.reference}`))} ` +
|
|
320
|
+
`dependency ${core_1.structUtils.prettyLocator(options.project.configuration, portalDependencyLocator)} ` +
|
|
321
|
+
`conflicts with dependency ${core_1.structUtils.prettyLocator(options.project.configuration, siblingPortalDependencyLocator)} ` +
|
|
322
|
+
`from sibling portal ${core_1.structUtils.prettyIdent(options.project.configuration, core_1.structUtils.parseIdent(siblingPortalDependency.portal.name))}`,
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
siblingPortalDependencyMap.set(name, { target: portalDependencyLocator.reference, portal: depLocator });
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
const parentHoistingLimits = (_a = options.hoistingLimitsByCwd) === null || _a === void 0 ? void 0 : _a.get(parentRelativeCwd);
|
|
335
|
+
const relativeDepCwd = isExternalSoftLinkDep ? parentRelativeCwd : fslib_1.ppath.relative(topPkgPortableLocation, fslib_1.npath.toPortablePath(depPkg.packageLocation)) || fslib_2.PortablePath.dot;
|
|
336
|
+
const depHoistingLimits = (_b = options.hoistingLimitsByCwd) === null || _b === void 0 ? void 0 : _b.get(relativeDepCwd);
|
|
337
|
+
const isHoistBorder = parentHoistingLimits === NodeModulesHoistingLimits.DEPENDENCIES
|
|
338
|
+
|| depHoistingLimits === NodeModulesHoistingLimits.DEPENDENCIES
|
|
339
|
+
|| depHoistingLimits === NodeModulesHoistingLimits.WORKSPACES;
|
|
340
|
+
addPackageToTree(depName, depPkg, depLocator, node, pkg, allDependencies, relativeDepCwd, isHoistBorder);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
addPackageToTree(topLocator.name, topPkg, topLocator, packageTree, topPkg, topPkg.packageDependencies, fslib_2.PortablePath.dot, false);
|
|
346
|
+
return { packageTree, hoistingLimits, errors, preserveSymlinksRequired };
|
|
347
|
+
};
|
|
348
|
+
function getRealPackageLocation(pkg, locator, pnp) {
|
|
349
|
+
const realPath = pnp.resolveVirtual && locator.reference && locator.reference.startsWith(`virtual:`)
|
|
350
|
+
? pnp.resolveVirtual(pkg.packageLocation)
|
|
351
|
+
: pkg.packageLocation;
|
|
352
|
+
return fslib_1.npath.toPortablePath(realPath || pkg.packageLocation);
|
|
353
|
+
}
|
|
354
|
+
function getTargetLocatorPath(locator, pnp, options) {
|
|
355
|
+
const pkgLocator = pnp.getLocator(locator.name.replace(WORKSPACE_NAME_SUFFIX, ``), locator.reference);
|
|
356
|
+
const info = pnp.getPackageInformation(pkgLocator);
|
|
357
|
+
if (info === null)
|
|
358
|
+
throw new Error(`Assertion failed: Expected the package to be registered`);
|
|
359
|
+
return options.pnpifyFs
|
|
360
|
+
// In case of pnpifyFs we represent modules as symlinks to archives in NodeModulesFS
|
|
361
|
+
// `/home/user/project/foo` is a symlink to `/home/user/project/.yarn/.cache/foo.zip/node_modules/foo`
|
|
362
|
+
// To make this fs layout work with legacy tools we make
|
|
363
|
+
// `/home/user/project/.yarn/.cache/foo.zip/node_modules/foo/node_modules` (which normally does not exist inside archive) a symlink to:
|
|
364
|
+
// `/home/user/project/node_modules/foo/node_modules`, so that the tools were able to access it
|
|
365
|
+
? { linkType: LinkType.SOFT, target: fslib_1.npath.toPortablePath(info.packageLocation) }
|
|
366
|
+
: { linkType: info.linkType, target: getRealPackageLocation(info, locator, pnp) };
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Converts hoisted tree to node modules map
|
|
370
|
+
*
|
|
371
|
+
* @param pnp PnP API
|
|
372
|
+
* @param hoistedTree hoisted package tree from `RawHoister`
|
|
373
|
+
* @param locators locators
|
|
374
|
+
* @param packages package weights
|
|
375
|
+
*
|
|
376
|
+
* @returns node modules map
|
|
377
|
+
*/
|
|
378
|
+
const populateNodeModulesTree = (pnp, hoistedTree, options) => {
|
|
379
|
+
const tree = new Map();
|
|
380
|
+
const makeLeafNode = (locator, nodePath, aliases) => {
|
|
381
|
+
const { linkType, target } = getTargetLocatorPath(locator, pnp, options);
|
|
382
|
+
return {
|
|
383
|
+
locator: stringifyLocator(locator),
|
|
384
|
+
nodePath,
|
|
385
|
+
target,
|
|
386
|
+
linkType,
|
|
387
|
+
aliases,
|
|
388
|
+
};
|
|
389
|
+
};
|
|
390
|
+
const getPackageName = (identName) => {
|
|
391
|
+
const [nameOrScope, name] = identName.split(`/`);
|
|
392
|
+
return name ? {
|
|
393
|
+
scope: (0, fslib_1.toFilename)(nameOrScope),
|
|
394
|
+
name: (0, fslib_1.toFilename)(name),
|
|
395
|
+
} : {
|
|
396
|
+
scope: null,
|
|
397
|
+
name: (0, fslib_1.toFilename)(nameOrScope),
|
|
398
|
+
};
|
|
399
|
+
};
|
|
400
|
+
const seenNodes = new Set();
|
|
401
|
+
const buildTree = (pkg, locationPrefix, parentNodePath) => {
|
|
402
|
+
if (seenNodes.has(pkg))
|
|
403
|
+
return;
|
|
404
|
+
seenNodes.add(pkg);
|
|
405
|
+
for (const dep of pkg.dependencies) {
|
|
406
|
+
// We do not want self-references in node_modules, since they confuse existing tools
|
|
407
|
+
if (dep === pkg)
|
|
408
|
+
continue;
|
|
409
|
+
const references = Array.from(dep.references).sort();
|
|
410
|
+
const locator = { name: dep.identName, reference: references[0] };
|
|
411
|
+
const { name, scope } = getPackageName(dep.name);
|
|
412
|
+
const packageNameParts = scope
|
|
413
|
+
? [scope, name]
|
|
414
|
+
: [name];
|
|
415
|
+
const nodeModulesDirPath = fslib_1.ppath.join(locationPrefix, NODE_MODULES);
|
|
416
|
+
const nodeModulesLocation = fslib_1.ppath.join(nodeModulesDirPath, ...packageNameParts);
|
|
417
|
+
const nodePath = `${parentNodePath}/${locator.name}`;
|
|
418
|
+
const leafNode = makeLeafNode(locator, parentNodePath, references.slice(1));
|
|
419
|
+
// We don't want to create self-referencing symlinks for anonymous workspaces
|
|
420
|
+
let isAnonymousWorkspace = false;
|
|
421
|
+
if (leafNode.linkType === LinkType.SOFT && options.project) {
|
|
422
|
+
const workspace = options.project.workspacesByCwd.get(leafNode.target.slice(0, -1));
|
|
423
|
+
isAnonymousWorkspace = !!(workspace && !workspace.manifest.name);
|
|
424
|
+
}
|
|
425
|
+
if (!dep.name.endsWith(WORKSPACE_NAME_SUFFIX) && !isAnonymousWorkspace) {
|
|
426
|
+
const prevNode = tree.get(nodeModulesLocation);
|
|
427
|
+
if (prevNode) {
|
|
428
|
+
if (prevNode.dirList) {
|
|
429
|
+
throw new Error(`Assertion failed: ${nodeModulesLocation} cannot merge dir node with leaf node`);
|
|
430
|
+
}
|
|
431
|
+
else {
|
|
432
|
+
const locator1 = core_1.structUtils.parseLocator(prevNode.locator);
|
|
433
|
+
const locator2 = core_1.structUtils.parseLocator(leafNode.locator);
|
|
434
|
+
if (prevNode.linkType !== leafNode.linkType)
|
|
435
|
+
throw new Error(`Assertion failed: ${nodeModulesLocation} cannot merge nodes with different link types ${prevNode.nodePath}/${core_1.structUtils.stringifyLocator(locator1)} and ${parentNodePath}/${core_1.structUtils.stringifyLocator(locator2)}`);
|
|
436
|
+
else if (locator1.identHash !== locator2.identHash)
|
|
437
|
+
throw new Error(`Assertion failed: ${nodeModulesLocation} cannot merge nodes with different idents ${prevNode.nodePath}/${core_1.structUtils.stringifyLocator(locator1)} and ${parentNodePath}/s${core_1.structUtils.stringifyLocator(locator2)}`);
|
|
438
|
+
leafNode.aliases = [...leafNode.aliases, ...prevNode.aliases, core_1.structUtils.parseLocator(prevNode.locator).reference];
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
tree.set(nodeModulesLocation, leafNode);
|
|
442
|
+
const segments = nodeModulesLocation.split(`/`);
|
|
443
|
+
const nodeModulesIdx = segments.indexOf(NODE_MODULES);
|
|
444
|
+
for (let segCount = segments.length - 1; nodeModulesIdx >= 0 && segCount > nodeModulesIdx; segCount--) {
|
|
445
|
+
const dirPath = fslib_1.npath.toPortablePath(segments.slice(0, segCount).join(fslib_1.ppath.sep));
|
|
446
|
+
const targetDir = (0, fslib_1.toFilename)(segments[segCount]);
|
|
447
|
+
const subdirs = tree.get(dirPath);
|
|
448
|
+
if (!subdirs) {
|
|
449
|
+
tree.set(dirPath, { dirList: new Set([targetDir]) });
|
|
450
|
+
}
|
|
451
|
+
else if (subdirs.dirList) {
|
|
452
|
+
if (subdirs.dirList.has(targetDir)) {
|
|
453
|
+
break;
|
|
454
|
+
}
|
|
455
|
+
else {
|
|
456
|
+
subdirs.dirList.add(targetDir);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
buildTree(dep, leafNode.linkType === LinkType.SOFT ? leafNode.target : nodeModulesLocation, nodePath);
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
const rootNode = makeLeafNode({ name: hoistedTree.name, reference: Array.from(hoistedTree.references)[0] }, ``, []);
|
|
465
|
+
const rootPath = rootNode.target;
|
|
466
|
+
tree.set(rootPath, rootNode);
|
|
467
|
+
buildTree(hoistedTree, rootPath, ``);
|
|
468
|
+
return tree;
|
|
469
|
+
};
|
|
470
|
+
/**
|
|
471
|
+
* Benchmarks raw hoisting performance.
|
|
472
|
+
*
|
|
473
|
+
* The function is used for troubleshooting purposes only.
|
|
474
|
+
*
|
|
475
|
+
* @param packageTree package tree
|
|
476
|
+
* @param packages package info
|
|
477
|
+
*
|
|
478
|
+
* @returns average raw hoisting time
|
|
479
|
+
*/
|
|
480
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
481
|
+
const benchmarkRawHoisting = (packageTree) => {
|
|
482
|
+
const iterCount = 10;
|
|
483
|
+
const startTime = Date.now();
|
|
484
|
+
for (let iter = 0; iter < iterCount; iter++)
|
|
485
|
+
(0, hoist_1.hoist)(packageTree);
|
|
486
|
+
const endTime = Date.now();
|
|
487
|
+
return (endTime - startTime) / iterCount;
|
|
488
|
+
};
|
|
489
|
+
/**
|
|
490
|
+
* Benchmarks node_modules tree building.
|
|
491
|
+
*
|
|
492
|
+
* The function is used for troubleshooting purposes only.
|
|
493
|
+
*
|
|
494
|
+
* @param packageTree package tree
|
|
495
|
+
* @param packages package info
|
|
496
|
+
*
|
|
497
|
+
* @returns average raw hoisting time
|
|
498
|
+
*/
|
|
499
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
500
|
+
const benchmarkBuildTree = (pnp, options) => {
|
|
501
|
+
const iterCount = 100;
|
|
502
|
+
const startTime = Date.now();
|
|
503
|
+
for (let iter = 0; iter < iterCount; iter++) {
|
|
504
|
+
const { packageTree, hoistingLimits } = buildPackageTree(pnp, options);
|
|
505
|
+
const hoistedTree = (0, hoist_1.hoist)(packageTree, { hoistingLimits });
|
|
506
|
+
populateNodeModulesTree(pnp, hoistedTree, options);
|
|
507
|
+
}
|
|
508
|
+
const endTime = Date.now();
|
|
509
|
+
return (endTime - startTime) / iterCount;
|
|
510
|
+
};
|
|
511
|
+
/**
|
|
512
|
+
* Pretty-prints node_modules tree.
|
|
513
|
+
*
|
|
514
|
+
* The function is used for troubleshooting purposes only.
|
|
515
|
+
*
|
|
516
|
+
* @param tree node_modules tree
|
|
517
|
+
* @param rootPath top-level project root folder
|
|
518
|
+
*
|
|
519
|
+
* @returns sorted node_modules tree
|
|
520
|
+
*/
|
|
521
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
522
|
+
const dumpNodeModulesTree = (tree, rootPath) => {
|
|
523
|
+
const sortedTree = new Map();
|
|
524
|
+
const keys = Array.from(tree.keys()).sort();
|
|
525
|
+
for (const key of keys) {
|
|
526
|
+
const val = tree.get(key);
|
|
527
|
+
sortedTree.set(key, val.dirList ? { dirList: new Set(Array.from(val.dirList).sort()) } : val);
|
|
528
|
+
}
|
|
529
|
+
const seenPaths = new Set();
|
|
530
|
+
const dumpTree = (nodePath, prefix = ``, dirPrefix = ``) => {
|
|
531
|
+
const node = sortedTree.get(nodePath);
|
|
532
|
+
if (!node)
|
|
533
|
+
return ``;
|
|
534
|
+
seenPaths.add(nodePath);
|
|
535
|
+
let str = ``;
|
|
536
|
+
if (node.dirList) {
|
|
537
|
+
const dirs = Array.from(node.dirList);
|
|
538
|
+
for (let idx = 0; idx < dirs.length; idx++) {
|
|
539
|
+
const dir = dirs[idx];
|
|
540
|
+
str += `${prefix}${idx < dirs.length - 1 ? `├─` : `└─`}${dirPrefix}${dir}\n`;
|
|
541
|
+
str += dumpTree(fslib_1.ppath.join(nodePath, dir), `${prefix}${idx < dirs.length - 1 ? `│ ` : ` `}`);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
else {
|
|
545
|
+
const { target, linkType } = node;
|
|
546
|
+
str += dumpTree(fslib_1.ppath.join(nodePath, NODE_MODULES), `${prefix}│ `, `${NODE_MODULES}/`);
|
|
547
|
+
str += `${prefix}└─${linkType === LinkType.SOFT ? `s>` : `>`}${target}\n`;
|
|
548
|
+
}
|
|
549
|
+
return str;
|
|
550
|
+
};
|
|
551
|
+
let str = dumpTree(fslib_1.ppath.join(rootPath, NODE_MODULES));
|
|
552
|
+
for (const key of sortedTree.keys()) {
|
|
553
|
+
if (!seenPaths.has(key)) {
|
|
554
|
+
str += `${key.replace(rootPath, ``)}\n${dumpTree(key)}`;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
return str;
|
|
558
|
+
};
|
|
559
|
+
/**
|
|
560
|
+
* Pretty-prints dependency tree in the `yarn why`-like format
|
|
561
|
+
*
|
|
562
|
+
* The function is used for troubleshooting purposes only.
|
|
563
|
+
*
|
|
564
|
+
* @param pkg node_modules tree
|
|
565
|
+
*
|
|
566
|
+
* @returns sorted node_modules tree
|
|
567
|
+
*/
|
|
568
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
569
|
+
const dumpDepTree = (tree) => {
|
|
570
|
+
const dumpLocator = (locator) => {
|
|
571
|
+
if (locator.reference === `workspace:.`) {
|
|
572
|
+
return `.`;
|
|
573
|
+
}
|
|
574
|
+
else if (!locator.reference) {
|
|
575
|
+
return `${locator.name}@${locator.reference}`;
|
|
576
|
+
}
|
|
577
|
+
else {
|
|
578
|
+
const version = (locator.reference.indexOf(`#`) > 0 ? locator.reference.split(`#`)[1] : locator.reference).replace(`npm:`, ``);
|
|
579
|
+
if (locator.reference.startsWith(`virtual`)) {
|
|
580
|
+
return `v:${locator.name}@${version}`;
|
|
581
|
+
}
|
|
582
|
+
else {
|
|
583
|
+
return `${locator.name}@${version}`;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
};
|
|
587
|
+
const dumpPackage = (pkg, parents, prefix = ``) => {
|
|
588
|
+
if (parents.includes(pkg))
|
|
589
|
+
return ``;
|
|
590
|
+
const dependencies = Array.from(pkg.dependencies);
|
|
591
|
+
let str = ``;
|
|
592
|
+
for (let idx = 0; idx < dependencies.length; idx++) {
|
|
593
|
+
const dep = dependencies[idx];
|
|
594
|
+
str += `${prefix}${idx < dependencies.length - 1 ? `├─` : `└─`}${(parents.includes(dep) ? `>` : ``) + dumpLocator({ name: dep.name, reference: Array.from(dep.references)[0] })}\n`;
|
|
595
|
+
str += dumpPackage(dep, [...parents, dep], `${prefix}${idx < dependencies.length - 1 ? `│ ` : ` `}`);
|
|
596
|
+
}
|
|
597
|
+
return str;
|
|
598
|
+
};
|
|
599
|
+
return dumpPackage(tree, []);
|
|
600
|
+
};
|