UNPKG

@dependabot/yarn-lib

Version:

📦🐈 Fast, reliable, and secure dependency management.

1,159 lines (928 loc) 36.4 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.NohoistResolver = exports.HoistManifest = undefined; var _extends2; function _load_extends() { return _extends2 = _interopRequireDefault(require('babel-runtime/helpers/extends')); } var _config; function _load_config() { return _config = _interopRequireDefault(require('./config.js')); } var _misc; function _load_misc() { return _misc = require('./util/misc.js'); } var _micromatch; function _load_micromatch() { return _micromatch = _interopRequireDefault(require('micromatch')); } var _workspaceLayout2; function _load_workspaceLayout() { return _workspaceLayout2 = _interopRequireDefault(require('./workspace-layout.js')); } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } const invariant = require('invariant'); const path = require('path'); let historyCounter = 0; const LINK_TYPES = new Set(['workspace', 'link']); class HoistManifest { constructor(key, parts, pkg, loc, isDirectRequire, isRequired, isIncompatible) { this.isDirectRequire = isDirectRequire; this.isRequired = isRequired; this.isIncompatible = isIncompatible; this.loc = loc; this.pkg = pkg; this.key = key; this.parts = parts; this.originalKey = key; this.previousPaths = []; this.history = []; this.addHistory(`Start position = ${key}`); this.isNohoist = false; this.originalParentPath = ''; this.shallowPaths = []; this.isShallow = false; } //focus // nohoist info addHistory(msg) { this.history.push(`${++historyCounter}: ${msg}`); } } exports.HoistManifest = HoistManifest; class PackageHoister { constructor(config, resolver, { ignoreOptional, workspaceLayout } = {}) { this.resolver = resolver; this.config = config; this.ignoreOptional = ignoreOptional; this.taintedKeys = new Map(); this.levelQueue = []; this.tree = new Map(); this.workspaceLayout = workspaceLayout; this.nohoistResolver = new NohoistResolver(config, resolver); } /** * Taint this key and prevent any modules from being hoisted to it. */ taintKey(key, info) { const existingTaint = this.taintedKeys.get(key); if (existingTaint && existingTaint.loc !== info.loc) { return false; } else { this.taintedKeys.set(key, info); return true; } } /** * Implode an array of ancestry parts into a key. */ implodeKey(parts) { return parts.join('#'); } /** * Seed the hoister with patterns taken from the included resolver. */ seed(patterns) { this.prepass(patterns); for (var _iterator = this.resolver.dedupePatterns(patterns), _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) { var _ref; if (_isArray) { if (_i >= _iterator.length) break; _ref = _iterator[_i++]; } else { _i = _iterator.next(); if (_i.done) break; _ref = _i.value; } const pattern = _ref; this._seed(pattern, { isDirectRequire: true }); } while (true) { let queue = this.levelQueue; if (!queue.length) { this._propagateRequired(); return; } this.levelQueue = []; // sort queue to get determinism between runs queue = queue.sort(([aPattern], [bPattern]) => { return (0, (_misc || _load_misc()).sortAlpha)(aPattern, bPattern); }); // sort the queue again to hoist packages without peer dependencies first let sortedQueue = []; const availableSet = new Set(); let hasChanged = true; while (queue.length > 0 && hasChanged) { hasChanged = false; const queueCopy = queue; queue = []; for (let t = 0; t < queueCopy.length; ++t) { const queueItem = queueCopy[t]; const pattern = queueItem[0]; const pkg = this.resolver.getStrictResolvedPattern(pattern); const peerDependencies = Object.keys(pkg.peerDependencies || {}); const areDependenciesFulfilled = peerDependencies.every(peerDependency => availableSet.has(peerDependency)); if (areDependenciesFulfilled) { // Move the package inside our sorted queue sortedQueue.push(queueItem); // Add it to our set, so that we know it is available availableSet.add(pattern); // Schedule a next pass, in case other packages had peer dependencies on this one hasChanged = true; } else { queue.push(queueItem); } } } // We might end up with some packages left in the queue, that have not been sorted. We reach this codepath if two // packages have a cyclic dependency, or if the peer dependency is provided by a parent package. In these case, // nothing we can do, so we just add all of these packages to the end of the sorted queue. sortedQueue = sortedQueue.concat(queue); for (var _iterator2 = sortedQueue, _isArray2 = Array.isArray(_iterator2), _i2 = 0, _iterator2 = _isArray2 ? _iterator2 : _iterator2[Symbol.iterator]();;) { var _ref3; if (_isArray2) { if (_i2 >= _iterator2.length) break; _ref3 = _iterator2[_i2++]; } else { _i2 = _iterator2.next(); if (_i2.done) break; _ref3 = _i2.value; } const _ref2 = _ref3; const pattern = _ref2[0]; const parent = _ref2[1]; const info = this._seed(pattern, { isDirectRequire: false, parent }); if (info) { this.hoist(info); } } } } /** * Seed the hoister with a specific pattern. */ _seed(pattern, { isDirectRequire, parent }) { // const pkg = this.resolver.getStrictResolvedPattern(pattern); const ref = pkg._reference; invariant(ref, 'expected reference'); // let parentParts = []; const isIncompatible = ref.incompatible; const isMarkedAsOptional = ref.optional && this.ignoreOptional; let isRequired = isDirectRequire && !ref.ignore && !isIncompatible && !isMarkedAsOptional; if (parent) { if (!this.tree.get(parent.key)) { return null; } // non ignored dependencies inherit parent's ignored status // parent may transition from ignored to non ignored when hoisted if it is used in another non ignored branch if (!isDirectRequire && !isIncompatible && parent.isRequired && !isMarkedAsOptional) { isRequired = true; } parentParts = parent.parts; } // const loc = this.config.generateModuleCachePath(ref); const parts = parentParts.concat(pkg.name); const key = this.implodeKey(parts); const info = new HoistManifest(key, parts, pkg, loc, isDirectRequire, isRequired, isIncompatible); this.nohoistResolver.initNohoist(info, parent); this.tree.set(key, info); this.taintKey(key, info); // const pushed = new Set(); for (var _iterator3 = ref.dependencies, _isArray3 = Array.isArray(_iterator3), _i3 = 0, _iterator3 = _isArray3 ? _iterator3 : _iterator3[Symbol.iterator]();;) { var _ref4; if (_isArray3) { if (_i3 >= _iterator3.length) break; _ref4 = _iterator3[_i3++]; } else { _i3 = _iterator3.next(); if (_i3.done) break; _ref4 = _i3.value; } const depPattern = _ref4; if (!pushed.has(depPattern)) { this.levelQueue.push([depPattern, info]); pushed.add(depPattern); } } return info; } /** * Propagate inherited ignore statuses from non-ignored to ignored packages */ _propagateRequired() { // const toVisit = []; // enumerate all non-ignored packages for (var _iterator4 = this.tree.entries(), _isArray4 = Array.isArray(_iterator4), _i4 = 0, _iterator4 = _isArray4 ? _iterator4 : _iterator4[Symbol.iterator]();;) { var _ref5; if (_isArray4) { if (_i4 >= _iterator4.length) break; _ref5 = _iterator4[_i4++]; } else { _i4 = _iterator4.next(); if (_i4.done) break; _ref5 = _i4.value; } const entry = _ref5; if (entry[1].isRequired) { toVisit.push(entry[1]); } } // visit them while (toVisit.length) { const info = toVisit.shift(); const ref = info.pkg._reference; invariant(ref, 'expected reference'); for (var _iterator5 = ref.dependencies, _isArray5 = Array.isArray(_iterator5), _i5 = 0, _iterator5 = _isArray5 ? _iterator5 : _iterator5[Symbol.iterator]();;) { var _ref6; if (_isArray5) { if (_i5 >= _iterator5.length) break; _ref6 = _iterator5[_i5++]; } else { _i5 = _iterator5.next(); if (_i5.done) break; _ref6 = _i5.value; } const depPattern = _ref6; const depinfo = this._lookupDependency(info, depPattern); if (!depinfo) { continue; } const depRef = depinfo.pkg._reference; // If it's marked as optional, but the parent is required and the // dependency was not listed in `optionalDependencies`, then we mark the // dependency as required. const isMarkedAsOptional = depRef && depRef.optional && this.ignoreOptional && !(info.isRequired && depRef.hint !== 'optional'); if (!depinfo.isRequired && !depinfo.isIncompatible && !isMarkedAsOptional) { depinfo.isRequired = true; depinfo.addHistory(`Mark as non-ignored because of usage by ${info.key}`); toVisit.push(depinfo); } } } } /** * Looks up the package a dependency resolves to */ _lookupDependency(info, depPattern) { // const pkg = this.resolver.getStrictResolvedPattern(depPattern); const ref = pkg._reference; invariant(ref, 'expected reference'); // for (let i = info.parts.length; i >= 0; i--) { const checkParts = info.parts.slice(0, i).concat(pkg.name); const checkKey = this.implodeKey(checkParts); const existing = this.tree.get(checkKey); if (existing) { return existing; } } return null; } /** * Find the highest position we can hoist this module to. */ getNewParts(key, info, parts) { let stepUp = false; const highestHoistingPoint = this.nohoistResolver.highestHoistingPoint(info) || 0; const fullKey = this.implodeKey(parts); const stack = []; // stack of removed parts const name = parts.pop(); if (info.isNohoist) { info.addHistory(`Marked as nohoist, will not be hoisted above '${parts[highestHoistingPoint]}'`); } for (let i = parts.length - 1; i >= highestHoistingPoint; i--) { const checkParts = parts.slice(0, i).concat(name); const checkKey = this.implodeKey(checkParts); info.addHistory(`Looked at ${checkKey} for a match`); const existing = this.tree.get(checkKey); if (existing) { if (existing.loc === info.loc) { // switch to non ignored if earlier deduped version was ignored (must be compatible) if (!existing.isRequired && info.isRequired) { existing.addHistory(`Deduped ${fullKey} to this item, marking as required`); existing.isRequired = true; } else { existing.addHistory(`Deduped ${fullKey} to this item`); } return { parts: checkParts, duplicate: true }; } else { // everything above will be shadowed and this is a conflict info.addHistory(`Found a collision at ${checkKey}`); break; } } const existingTaint = this.taintedKeys.get(checkKey); if (existingTaint && existingTaint.loc !== info.loc) { info.addHistory(`Broken by ${checkKey}`); break; } } const peerDependencies = Object.keys(info.pkg.peerDependencies || {}); // remove redundant parts that wont collide hoistLoop: while (parts.length > highestHoistingPoint) { // we must not hoist a package higher than its peer dependencies for (var _iterator6 = peerDependencies, _isArray6 = Array.isArray(_iterator6), _i6 = 0, _iterator6 = _isArray6 ? _iterator6 : _iterator6[Symbol.iterator]();;) { var _ref7; if (_isArray6) { if (_i6 >= _iterator6.length) break; _ref7 = _iterator6[_i6++]; } else { _i6 = _iterator6.next(); if (_i6.done) break; _ref7 = _i6.value; } const peerDependency = _ref7; const checkParts = parts.concat(peerDependency); const checkKey = this.implodeKey(checkParts); info.addHistory(`Looked at ${checkKey} for a peer dependency match`); const existing = this.tree.get(checkKey); if (existing) { info.addHistory(`Found a peer dependency requirement at ${checkKey}`); break hoistLoop; } } const checkParts = parts.concat(name); const checkKey = this.implodeKey(checkParts); // const existing = this.tree.get(checkKey); if (existing) { stepUp = true; break; } // check if we're trying to hoist ourselves to a previously unflattened module key, // this will result in a conflict and we'll need to move ourselves up if (key !== checkKey && this.taintedKeys.has(checkKey)) { stepUp = true; break; } // stack.push(parts.pop()); } // parts.push(name); // const isValidPosition = parts => { // nohoist package can't be hoisted to the "root" if (parts.length <= highestHoistingPoint) { return false; } const key = this.implodeKey(parts); const existing = this.tree.get(key); if (existing && existing.loc === info.loc) { return true; } // ensure there's no taint or the taint is us const existingTaint = this.taintedKeys.get(key); if (existingTaint && existingTaint.loc !== info.loc) { return false; } return true; }; // we need to special case when we attempt to hoist to the top level as the `existing` logic // wont be hit in the above `while` loop and we could conflict if (!isValidPosition(parts)) { stepUp = true; } // sometimes we need to step up to a parent module to install ourselves while (stepUp && stack.length) { info.addHistory(`Stepping up from ${this.implodeKey(parts)}`); parts.pop(); // remove `name` parts.push(stack.pop(), name); if (isValidPosition(parts)) { info.addHistory(`Found valid position ${this.implodeKey(parts)}`); stepUp = false; } } return { parts, duplicate: false }; } /** * Hoist all seeded patterns to their highest positions. */ hoist(info) { const oldKey = info.key, rawParts = info.parts; // remove this item from the `tree` map so we can ignore it this.tree.delete(oldKey); var _getNewParts = this.getNewParts(oldKey, info, rawParts.slice()); const parts = _getNewParts.parts, duplicate = _getNewParts.duplicate; const newKey = this.implodeKey(parts); if (duplicate) { info.addHistory(`Satisfied from above by ${newKey}`); this.declareRename(info, rawParts, parts); this.updateHoistHistory(this.nohoistResolver._originalPath(info), this.implodeKey(parts)); return; } // update to the new key if (oldKey === newKey) { info.addHistory(`Didn't hoist - see reason above`); this.setKey(info, oldKey, rawParts); return; } // this.declareRename(info, rawParts, parts); this.setKey(info, newKey, parts); } /** * Declare that a module has been hoisted and update our internal references. */ declareRename(info, oldParts, newParts) { // go down the tree from our new position reserving our name this.taintParents(info, oldParts.slice(0, -1), newParts.length - 1); } /** * Crawl upwards through a list of ancestry parts and taint a package name. */ taintParents(info, processParts, start) { for (let i = start; i < processParts.length; i++) { const parts = processParts.slice(0, i).concat(info.pkg.name); const key = this.implodeKey(parts); if (this.taintKey(key, info)) { info.addHistory(`Tainted ${key} to prevent collisions`); } } } updateHoistHistory(fromPath, toKey) { const info = this.tree.get(toKey); invariant(info, `expect to find hoist-to ${toKey}`); info.previousPaths.push(fromPath); } /** * Update the key of a module and update our references. */ setKey(info, newKey, parts) { const oldKey = info.key; info.key = newKey; info.parts = parts; this.tree.set(newKey, info); if (oldKey === newKey) { return; } const fromInfo = this.tree.get(newKey); invariant(fromInfo, `expect to find hoist-from ${newKey}`); info.previousPaths.push(this.nohoistResolver._originalPath(fromInfo)); info.addHistory(`New position = ${newKey}`); } /** * Perform a prepass and if there's multiple versions of the same package, hoist the one with * the most dependents to the top. */ prepass(patterns) { patterns = this.resolver.dedupePatterns(patterns).sort(); const visited = new Map(); const occurences = {}; // visitor to be used inside add() to mark occurences of packages const visitAdd = (pkg, ancestry, pattern) => { const versions = occurences[pkg.name] = occurences[pkg.name] || {}; const version = versions[pkg.version] = versions[pkg.version] || { occurences: new Set(), pattern }; if (ancestry.length) { version.occurences.add(ancestry[ancestry.length - 1]); } }; // add an occurring package to the above data structure const add = (pattern, ancestry, ancestryPatterns) => { const pkg = this.resolver.getStrictResolvedPattern(pattern); if (ancestry.indexOf(pkg) >= 0) { // prevent recursive dependencies return; } let visitedPattern = visited.get(pattern); if (visitedPattern) { // if a package has been visited before, simply increment occurrences of packages // like last time this package was visited visitedPattern.forEach(visitPkg => { visitAdd(visitPkg.pkg, visitPkg.ancestry, visitPkg.pattern); }); visitAdd(pkg, ancestry, pattern); return; } const ref = pkg._reference; invariant(ref, 'expected reference'); visitAdd(pkg, ancestry, pattern); for (var _iterator7 = ref.dependencies, _isArray7 = Array.isArray(_iterator7), _i7 = 0, _iterator7 = _isArray7 ? _iterator7 : _iterator7[Symbol.iterator]();;) { var _ref8; if (_isArray7) { if (_i7 >= _iterator7.length) break; _ref8 = _iterator7[_i7++]; } else { _i7 = _iterator7.next(); if (_i7.done) break; _ref8 = _i7.value; } const depPattern = _ref8; const depAncestry = ancestry.concat(pkg); const depAncestryPatterns = ancestryPatterns.concat(depPattern); add(depPattern, depAncestry, depAncestryPatterns); } visitedPattern = visited.get(pattern) || []; visited.set(pattern, visitedPattern); visitedPattern.push({ pkg, ancestry, pattern }); ancestryPatterns.forEach(ancestryPattern => { const visitedAncestryPattern = visited.get(ancestryPattern); if (visitedAncestryPattern) { visitedAncestryPattern.push({ pkg, ancestry, pattern }); } }); }; // get a list of root package names since we can't hoist other dependencies to these spots! const rootPackageNames = new Set(); for (var _iterator8 = patterns, _isArray8 = Array.isArray(_iterator8), _i8 = 0, _iterator8 = _isArray8 ? _iterator8 : _iterator8[Symbol.iterator]();;) { var _ref9; if (_isArray8) { if (_i8 >= _iterator8.length) break; _ref9 = _iterator8[_i8++]; } else { _i8 = _iterator8.next(); if (_i8.done) break; _ref9 = _i8.value; } const pattern = _ref9; const pkg = this.resolver.getStrictResolvedPattern(pattern); rootPackageNames.add(pkg.name); add(pattern, [], []); } for (var _iterator9 = Object.keys(occurences).sort(), _isArray9 = Array.isArray(_iterator9), _i9 = 0, _iterator9 = _isArray9 ? _iterator9 : _iterator9[Symbol.iterator]();;) { var _ref10; if (_isArray9) { if (_i9 >= _iterator9.length) break; _ref10 = _iterator9[_i9++]; } else { _i9 = _iterator9.next(); if (_i9.done) break; _ref10 = _i9.value; } const packageName = _ref10; const versionOccurences = occurences[packageName]; const versions = Object.keys(versionOccurences); if (versions.length === 1) { // only one package type so we'll hoist this to the top anyway continue; } if (this.tree.get(packageName)) { // a transitive dependency of a previously hoisted dependency exists continue; } if (rootPackageNames.has(packageName)) { // can't replace top level packages continue; } let mostOccurenceCount; let mostOccurencePattern; for (var _iterator10 = Object.keys(versionOccurences).sort(), _isArray10 = Array.isArray(_iterator10), _i10 = 0, _iterator10 = _isArray10 ? _iterator10 : _iterator10[Symbol.iterator]();;) { var _ref11; if (_isArray10) { if (_i10 >= _iterator10.length) break; _ref11 = _iterator10[_i10++]; } else { _i10 = _iterator10.next(); if (_i10.done) break; _ref11 = _i10.value; } const version = _ref11; var _versionOccurences$ve = versionOccurences[version]; const occurences = _versionOccurences$ve.occurences, pattern = _versionOccurences$ve.pattern; const occurenceCount = occurences.size; if (!mostOccurenceCount || occurenceCount > mostOccurenceCount) { mostOccurenceCount = occurenceCount; mostOccurencePattern = pattern; } } invariant(mostOccurencePattern, 'expected most occurring pattern'); invariant(mostOccurenceCount, 'expected most occurring count'); // only hoist this module if it occured more than once if (mostOccurenceCount > 1) { this._seed(mostOccurencePattern, { isDirectRequire: false }); } } } markShallowWorkspaceEntries() { const targetWorkspace = this.config.focusedWorkspaceName; const targetHoistManifest = this.tree.get(targetWorkspace); invariant(targetHoistManifest, `targetHoistManifest from ${targetWorkspace} missing`); //dedupe with a set const dependentWorkspaces = Array.from(new Set(this._getDependentWorkspaces(targetHoistManifest))); const entries = Array.from(this.tree); entries.forEach(([key, info]) => { const splitPath = key.split('#'); //mark the workspace and any un-hoisted dependencies it has for shallow installation const isShallowDependency = dependentWorkspaces.some(w => { if (splitPath[0] !== w) { //entry is not related to the workspace return false; } if (!splitPath[1]) { //entry is the workspace return true; } //don't bother marking dev dependencies or nohoist packages for shallow installation const treeEntry = this.tree.get(w); invariant(treeEntry, 'treeEntry is not defined for ' + w); const pkg = treeEntry.pkg; return !info.isNohoist && (!pkg.devDependencies || !(splitPath[1] in pkg.devDependencies)); }); if (isShallowDependency) { info.shallowPaths = [null]; return; } //if package foo is at TARGET_WORKSPACE/node_modules/foo, the hoisted version of foo //should be installed under each shallow workspace that uses it //(unless that workspace has its own version of foo, in which case that should be installed) if (splitPath.length !== 2 || splitPath[0] !== targetWorkspace) { return; } const unhoistedDependency = splitPath[1]; const unhoistedInfo = this.tree.get(unhoistedDependency); if (!unhoistedInfo) { return; } dependentWorkspaces.forEach(w => { if (this._packageDependsOnHoistedPackage(w, unhoistedDependency, false)) { unhoistedInfo.shallowPaths.push(w); } }); }); } _getDependentWorkspaces(parent, allowDevDeps = true, alreadySeen = new Set()) { const parentName = parent.pkg.name; if (alreadySeen.has(parentName)) { return []; } alreadySeen.add(parentName); invariant(this.workspaceLayout, 'missing workspaceLayout'); var _workspaceLayout = this.workspaceLayout; const virtualManifestName = _workspaceLayout.virtualManifestName, workspaces = _workspaceLayout.workspaces; const directDependencies = []; const ignored = []; Object.keys(workspaces).forEach(workspace => { if (alreadySeen.has(workspace) || workspace === virtualManifestName) { return; } //skip a workspace if a different version of it is already being installed under the parent workspace let info = this.tree.get(`${parentName}#${workspace}`); if (info) { const workspaceVersion = workspaces[workspace].manifest.version; if (info.isNohoist && info.originalParentPath.startsWith(`/${WS_ROOT_ALIAS}/${parentName}`) && info.pkg.version === workspaceVersion) { //nohoist installations are exceptions directDependencies.push(info.key); } else { ignored.push(workspace); } return; } const searchPath = `/${WS_ROOT_ALIAS}/${parentName}`; info = this.tree.get(workspace); invariant(info, 'missing workspace tree entry ' + workspace); if (!info.previousPaths.some(p => p.startsWith(searchPath))) { return; } if (allowDevDeps || !parent.pkg.devDependencies || !(workspace in parent.pkg.devDependencies)) { directDependencies.push(workspace); } }); let nested = directDependencies.map(d => { const dependencyEntry = this.tree.get(d); invariant(dependencyEntry, 'missing dependencyEntry ' + d); return this._getDependentWorkspaces(dependencyEntry, false, alreadySeen); }); nested = [].concat.apply([], nested); //flatten const directDependencyNames = directDependencies.map(d => d.split('#').slice(-1)[0]); return directDependencyNames.concat(nested).filter(w => ignored.indexOf(w) === -1); } _packageDependsOnHoistedPackage(p, hoisted, checkDevDeps = true, checked = new Set()) { //don't check the same package more than once, and ignore any package that has its own version of hoisted if (checked.has(p) || this.tree.has(`${p}#${hoisted}`)) { return false; } checked.add(p); const info = this.tree.get(p); if (!info) { return false; } const pkg = info.pkg; if (!pkg) { return false; } let deps = []; if (pkg.dependencies) { deps = deps.concat(Object.keys(pkg.dependencies)); } if (checkDevDeps && pkg.devDependencies) { deps = deps.concat(Object.keys(pkg.devDependencies)); } if (deps.indexOf(hoisted) !== -1) { return true; } return deps.some(dep => this._packageDependsOnHoistedPackage(dep, hoisted, false, checked)); } /** * Produce a flattened list of module locations and manifests. */ init() { const flatTree = []; // for (var _iterator11 = this.tree.entries(), _isArray11 = Array.isArray(_iterator11), _i11 = 0, _iterator11 = _isArray11 ? _iterator11 : _iterator11[Symbol.iterator]();;) { var _ref13; if (_isArray11) { if (_i11 >= _iterator11.length) break; _ref13 = _iterator11[_i11++]; } else { _i11 = _iterator11.next(); if (_i11.done) break; _ref13 = _i11.value; } const _ref12 = _ref13; const key = _ref12[0]; const info = _ref12[1]; // decompress the location and push it to the flat tree. this path could be made const parts = []; const keyParts = key.split('#'); const isWorkspaceEntry = this.workspaceLayout && keyParts[0] === this.workspaceLayout.virtualManifestName; // Don't add the virtual manifest (keyParts.length === 1) // or ws childs which were not hoisted to the root (keyParts.length === 2). // If a ws child was hoisted its key would not contain the virtual manifest name if (isWorkspaceEntry && keyParts.length <= 2) { continue; } for (let i = 0; i < keyParts.length; i++) { const key = keyParts.slice(0, i + 1).join('#'); const hoisted = this.tree.get(key); invariant(hoisted, `expected hoisted manifest for "${key}"`); parts.push(this.config.getFolder(hoisted.pkg)); parts.push(keyParts[i]); } // Check if the destination is pointing to a sub folder of the virtualManifestName // e.g. _project_/node_modules/workspace-aggregator-123456/node_modules/workspaceChild/node_modules/dependency // This probably happened because the hoister was not able to hoist the workspace child to the root // So we have to change the folder to the workspace package location if (this.workspaceLayout && isWorkspaceEntry) { const wspPkg = this.workspaceLayout.workspaces[keyParts[1]]; invariant(wspPkg, `expected workspace package to exist for "${keyParts[1]}"`); parts.splice(0, 4, wspPkg.loc); } else { if (this.config.modulesFolder) { // remove the first part which will be the folder name and replace it with a // hardcoded modules folder parts.splice(0, 1, this.config.modulesFolder); } else { // first part will be the registry-specific module folder parts.splice(0, 0, this.config.lockfileFolder); } } const shallowLocs = []; info.shallowPaths.forEach(shallowPath => { const shallowCopyParts = parts.slice(); shallowCopyParts[0] = this.config.cwd; if (this.config.modulesFolder) { //add back the module folder name for the shallow installation const treeEntry = this.tree.get(keyParts[0]); invariant(treeEntry, 'expected treeEntry for ' + keyParts[0]); const moduleFolderName = this.config.getFolder(treeEntry.pkg); shallowCopyParts.splice(1, 0, moduleFolderName); } if (shallowPath) { const targetWorkspace = this.config.focusedWorkspaceName; const treeEntry = this.tree.get(`${targetWorkspace}#${shallowPath}`) || this.tree.get(shallowPath); invariant(treeEntry, 'expected treeEntry for ' + shallowPath); const moduleFolderName = this.config.getFolder(treeEntry.pkg); shallowCopyParts.splice(1, 0, moduleFolderName, shallowPath); } shallowLocs.push(path.join(...shallowCopyParts)); }); const loc = path.join(...parts); flatTree.push([loc, info]); shallowLocs.forEach(shallowLoc => { const newManifest = (0, (_extends2 || _load_extends()).default)({}, info, { isShallow: true }); flatTree.push([shallowLoc, newManifest]); }); } // remove ignored modules from the tree const visibleFlatTree = []; for (var _iterator12 = flatTree, _isArray12 = Array.isArray(_iterator12), _i12 = 0, _iterator12 = _isArray12 ? _iterator12 : _iterator12[Symbol.iterator]();;) { var _ref15; if (_isArray12) { if (_i12 >= _iterator12.length) break; _ref15 = _iterator12[_i12++]; } else { _i12 = _iterator12.next(); if (_i12.done) break; _ref15 = _i12.value; } const _ref14 = _ref15; const loc = _ref14[0]; const info = _ref14[1]; const ref = info.pkg._reference; invariant(ref, 'expected reference'); if (!info.isRequired) { info.addHistory('Deleted as this module was ignored'); } else { visibleFlatTree.push([loc, info]); } } return visibleFlatTree; } } exports.default = PackageHoister; const WS_ROOT_ALIAS = '_project_'; class NohoistResolver { constructor(config, resolver) { this.initNohoist = (info, parent) => { let parentNohoistList; let originalParentPath = info.originalParentPath; if (parent) { parentNohoistList = parent.nohoistList; originalParentPath = this._originalPath(parent); } else { invariant(this._isTopPackage(info), `${info.key} doesn't have parent nor a top package`); if (info.pkg.name !== this._wsRootPackageName) { parentNohoistList = this._wsRootNohoistList; originalParentPath = this._wsRootPackageName || ''; } } info.originalParentPath = originalParentPath; let nohoistList = this._extractNohoistList(info.pkg, this._originalPath(info)) || []; if (parentNohoistList) { nohoistList = nohoistList.concat(parentNohoistList); } info.nohoistList = nohoistList.length > 0 ? nohoistList : null; info.isNohoist = this._isNohoist(info); }; this.highestHoistingPoint = info => { return info.isNohoist && info.parts.length > 1 ? 1 : null; }; this._isNohoist = info => { if (this._isTopPackage(info)) { return false; } if (info.nohoistList && info.nohoistList.length > 0 && (_micromatch || _load_micromatch()).default.any(this._originalPath(info), info.nohoistList)) { return true; } if (this._config.plugnplayEnabled) { return true; } return false; }; this._isRootPackage = pkg => { return pkg.name === this._wsRootPackageName; }; this._originalPath = info => { return this._makePath(info.originalParentPath, info.pkg.name); }; this._isTopPackage = info => { const parentParts = info.parts.slice(0, -1); const result = !parentParts || parentParts.length <= 0 || parentParts.length === 1 && parentParts[0] === this._wsRootPackageName; return result; }; this._isLink = info => { return info.pkg._remote != null && LINK_TYPES.has(info.pkg._remote.type); }; this._extractNohoistList = (pkg, pathPrefix) => { let nohoistList; const ws = this._config.getWorkspaces(pkg); if (ws && ws.nohoist) { nohoistList = ws.nohoist.map(p => this._makePath(pathPrefix, p)); } return nohoistList; }; this._resolver = resolver; this._config = config; if (resolver.workspaceLayout) { this._wsRootPackageName = resolver.workspaceLayout.virtualManifestName; var _resolver$workspaceLa = resolver.workspaceLayout.getWorkspaceManifest(this._wsRootPackageName); const manifest = _resolver$workspaceLa.manifest; this._wsRootNohoistList = this._extractNohoistList(manifest, manifest.name); } } /** * examine the top level packages to find the root package */ /** * find the highest hoisting point for the given HoistManifest. * algorithm: a nohoist package should never be hoisted beyond the top of its branch, i.e. * the first element of its parts. Therefore the highest possible hoisting index is 1, * unless the package has only 1 part (itself), in such case returns null just like any hoisted package * */ // private functions _makePath(...args) { const parts = args.map(s => s === this._wsRootPackageName ? WS_ROOT_ALIAS : s); const result = parts.join('/'); return result[0] === '/' ? result : '/' + result; } // extract nohoist from package.json then prefix them with branch path // so we can matched against the branch tree ("originalPath") later } exports.NohoistResolver = NohoistResolver;