UNPKG

36.4 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, "__esModule", {
4 value: true
5});
6exports.NohoistResolver = exports.HoistManifest = undefined;
7
8var _extends2;
9
10function _load_extends() {
11 return _extends2 = _interopRequireDefault(require('babel-runtime/helpers/extends'));
12}
13
14var _config;
15
16function _load_config() {
17 return _config = _interopRequireDefault(require('./config.js'));
18}
19
20var _misc;
21
22function _load_misc() {
23 return _misc = require('./util/misc.js');
24}
25
26var _micromatch;
27
28function _load_micromatch() {
29 return _micromatch = _interopRequireDefault(require('micromatch'));
30}
31
32var _workspaceLayout2;
33
34function _load_workspaceLayout() {
35 return _workspaceLayout2 = _interopRequireDefault(require('./workspace-layout.js'));
36}
37
38function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
39
40const invariant = require('invariant');
41
42const path = require('path');
43
44let historyCounter = 0;
45
46const LINK_TYPES = new Set(['workspace', 'link']);
47class HoistManifest {
48 constructor(key, parts, pkg, loc, isDirectRequire, isRequired, isIncompatible) {
49 this.isDirectRequire = isDirectRequire;
50 this.isRequired = isRequired;
51 this.isIncompatible = isIncompatible;
52
53 this.loc = loc;
54 this.pkg = pkg;
55 this.key = key;
56 this.parts = parts;
57 this.originalKey = key;
58 this.previousPaths = [];
59
60 this.history = [];
61 this.addHistory(`Start position = ${key}`);
62
63 this.isNohoist = false;
64 this.originalParentPath = '';
65
66 this.shallowPaths = [];
67 this.isShallow = false;
68 }
69
70 //focus
71
72
73 // nohoist info
74
75
76 addHistory(msg) {
77 this.history.push(`${++historyCounter}: ${msg}`);
78 }
79}
80
81exports.HoistManifest = HoistManifest;
82class PackageHoister {
83 constructor(config, resolver, { ignoreOptional, workspaceLayout } = {}) {
84 this.resolver = resolver;
85 this.config = config;
86
87 this.ignoreOptional = ignoreOptional;
88
89 this.taintedKeys = new Map();
90 this.levelQueue = [];
91 this.tree = new Map();
92
93 this.workspaceLayout = workspaceLayout;
94
95 this.nohoistResolver = new NohoistResolver(config, resolver);
96 }
97
98 /**
99 * Taint this key and prevent any modules from being hoisted to it.
100 */
101
102 taintKey(key, info) {
103 const existingTaint = this.taintedKeys.get(key);
104 if (existingTaint && existingTaint.loc !== info.loc) {
105 return false;
106 } else {
107 this.taintedKeys.set(key, info);
108 return true;
109 }
110 }
111
112 /**
113 * Implode an array of ancestry parts into a key.
114 */
115
116 implodeKey(parts) {
117 return parts.join('#');
118 }
119
120 /**
121 * Seed the hoister with patterns taken from the included resolver.
122 */
123
124 seed(patterns) {
125 this.prepass(patterns);
126
127 for (var _iterator = this.resolver.dedupePatterns(patterns), _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) {
128 var _ref;
129
130 if (_isArray) {
131 if (_i >= _iterator.length) break;
132 _ref = _iterator[_i++];
133 } else {
134 _i = _iterator.next();
135 if (_i.done) break;
136 _ref = _i.value;
137 }
138
139 const pattern = _ref;
140
141 this._seed(pattern, { isDirectRequire: true });
142 }
143
144 while (true) {
145 let queue = this.levelQueue;
146 if (!queue.length) {
147 this._propagateRequired();
148 return;
149 }
150
151 this.levelQueue = [];
152
153 // sort queue to get determinism between runs
154 queue = queue.sort(([aPattern], [bPattern]) => {
155 return (0, (_misc || _load_misc()).sortAlpha)(aPattern, bPattern);
156 });
157
158 // sort the queue again to hoist packages without peer dependencies first
159 let sortedQueue = [];
160 const availableSet = new Set();
161
162 let hasChanged = true;
163 while (queue.length > 0 && hasChanged) {
164 hasChanged = false;
165
166 const queueCopy = queue;
167 queue = [];
168 for (let t = 0; t < queueCopy.length; ++t) {
169 const queueItem = queueCopy[t];
170 const pattern = queueItem[0];
171 const pkg = this.resolver.getStrictResolvedPattern(pattern);
172
173 const peerDependencies = Object.keys(pkg.peerDependencies || {});
174 const areDependenciesFulfilled = peerDependencies.every(peerDependency => availableSet.has(peerDependency));
175
176 if (areDependenciesFulfilled) {
177 // Move the package inside our sorted queue
178 sortedQueue.push(queueItem);
179
180 // Add it to our set, so that we know it is available
181 availableSet.add(pattern);
182
183 // Schedule a next pass, in case other packages had peer dependencies on this one
184 hasChanged = true;
185 } else {
186 queue.push(queueItem);
187 }
188 }
189 }
190
191 // We might end up with some packages left in the queue, that have not been sorted. We reach this codepath if two
192 // packages have a cyclic dependency, or if the peer dependency is provided by a parent package. In these case,
193 // nothing we can do, so we just add all of these packages to the end of the sorted queue.
194 sortedQueue = sortedQueue.concat(queue);
195
196 for (var _iterator2 = sortedQueue, _isArray2 = Array.isArray(_iterator2), _i2 = 0, _iterator2 = _isArray2 ? _iterator2 : _iterator2[Symbol.iterator]();;) {
197 var _ref3;
198
199 if (_isArray2) {
200 if (_i2 >= _iterator2.length) break;
201 _ref3 = _iterator2[_i2++];
202 } else {
203 _i2 = _iterator2.next();
204 if (_i2.done) break;
205 _ref3 = _i2.value;
206 }
207
208 const _ref2 = _ref3;
209 const pattern = _ref2[0];
210 const parent = _ref2[1];
211
212 const info = this._seed(pattern, { isDirectRequire: false, parent });
213 if (info) {
214 this.hoist(info);
215 }
216 }
217 }
218 }
219
220 /**
221 * Seed the hoister with a specific pattern.
222 */
223
224 _seed(pattern, { isDirectRequire, parent }) {
225 //
226 const pkg = this.resolver.getStrictResolvedPattern(pattern);
227 const ref = pkg._reference;
228 invariant(ref, 'expected reference');
229
230 //
231 let parentParts = [];
232
233 const isIncompatible = ref.incompatible;
234 const isMarkedAsOptional = ref.optional && this.ignoreOptional;
235
236 let isRequired = isDirectRequire && !ref.ignore && !isIncompatible && !isMarkedAsOptional;
237
238 if (parent) {
239 if (!this.tree.get(parent.key)) {
240 return null;
241 }
242 // non ignored dependencies inherit parent's ignored status
243 // parent may transition from ignored to non ignored when hoisted if it is used in another non ignored branch
244 if (!isDirectRequire && !isIncompatible && parent.isRequired && !isMarkedAsOptional) {
245 isRequired = true;
246 }
247 parentParts = parent.parts;
248 }
249
250 //
251 const loc = this.config.generateModuleCachePath(ref);
252 const parts = parentParts.concat(pkg.name);
253 const key = this.implodeKey(parts);
254 const info = new HoistManifest(key, parts, pkg, loc, isDirectRequire, isRequired, isIncompatible);
255
256 this.nohoistResolver.initNohoist(info, parent);
257
258 this.tree.set(key, info);
259 this.taintKey(key, info);
260
261 //
262 const pushed = new Set();
263 for (var _iterator3 = ref.dependencies, _isArray3 = Array.isArray(_iterator3), _i3 = 0, _iterator3 = _isArray3 ? _iterator3 : _iterator3[Symbol.iterator]();;) {
264 var _ref4;
265
266 if (_isArray3) {
267 if (_i3 >= _iterator3.length) break;
268 _ref4 = _iterator3[_i3++];
269 } else {
270 _i3 = _iterator3.next();
271 if (_i3.done) break;
272 _ref4 = _i3.value;
273 }
274
275 const depPattern = _ref4;
276
277 if (!pushed.has(depPattern)) {
278 this.levelQueue.push([depPattern, info]);
279 pushed.add(depPattern);
280 }
281 }
282
283 return info;
284 }
285
286 /**
287 * Propagate inherited ignore statuses from non-ignored to ignored packages
288 */
289
290 _propagateRequired() {
291 //
292 const toVisit = [];
293
294 // enumerate all non-ignored packages
295 for (var _iterator4 = this.tree.entries(), _isArray4 = Array.isArray(_iterator4), _i4 = 0, _iterator4 = _isArray4 ? _iterator4 : _iterator4[Symbol.iterator]();;) {
296 var _ref5;
297
298 if (_isArray4) {
299 if (_i4 >= _iterator4.length) break;
300 _ref5 = _iterator4[_i4++];
301 } else {
302 _i4 = _iterator4.next();
303 if (_i4.done) break;
304 _ref5 = _i4.value;
305 }
306
307 const entry = _ref5;
308
309 if (entry[1].isRequired) {
310 toVisit.push(entry[1]);
311 }
312 }
313
314 // visit them
315 while (toVisit.length) {
316 const info = toVisit.shift();
317 const ref = info.pkg._reference;
318 invariant(ref, 'expected reference');
319
320 for (var _iterator5 = ref.dependencies, _isArray5 = Array.isArray(_iterator5), _i5 = 0, _iterator5 = _isArray5 ? _iterator5 : _iterator5[Symbol.iterator]();;) {
321 var _ref6;
322
323 if (_isArray5) {
324 if (_i5 >= _iterator5.length) break;
325 _ref6 = _iterator5[_i5++];
326 } else {
327 _i5 = _iterator5.next();
328 if (_i5.done) break;
329 _ref6 = _i5.value;
330 }
331
332 const depPattern = _ref6;
333
334 const depinfo = this._lookupDependency(info, depPattern);
335
336 if (!depinfo) {
337 continue;
338 }
339
340 const depRef = depinfo.pkg._reference;
341
342 // If it's marked as optional, but the parent is required and the
343 // dependency was not listed in `optionalDependencies`, then we mark the
344 // dependency as required.
345 const isMarkedAsOptional = depRef && depRef.optional && this.ignoreOptional && !(info.isRequired && depRef.hint !== 'optional');
346
347 if (!depinfo.isRequired && !depinfo.isIncompatible && !isMarkedAsOptional) {
348 depinfo.isRequired = true;
349 depinfo.addHistory(`Mark as non-ignored because of usage by ${info.key}`);
350 toVisit.push(depinfo);
351 }
352 }
353 }
354 }
355
356 /**
357 * Looks up the package a dependency resolves to
358 */
359
360 _lookupDependency(info, depPattern) {
361 //
362 const pkg = this.resolver.getStrictResolvedPattern(depPattern);
363 const ref = pkg._reference;
364 invariant(ref, 'expected reference');
365
366 //
367 for (let i = info.parts.length; i >= 0; i--) {
368 const checkParts = info.parts.slice(0, i).concat(pkg.name);
369 const checkKey = this.implodeKey(checkParts);
370 const existing = this.tree.get(checkKey);
371 if (existing) {
372 return existing;
373 }
374 }
375
376 return null;
377 }
378
379 /**
380 * Find the highest position we can hoist this module to.
381 */
382
383 getNewParts(key, info, parts) {
384 let stepUp = false;
385
386 const highestHoistingPoint = this.nohoistResolver.highestHoistingPoint(info) || 0;
387 const fullKey = this.implodeKey(parts);
388 const stack = []; // stack of removed parts
389 const name = parts.pop();
390
391 if (info.isNohoist) {
392 info.addHistory(`Marked as nohoist, will not be hoisted above '${parts[highestHoistingPoint]}'`);
393 }
394
395 for (let i = parts.length - 1; i >= highestHoistingPoint; i--) {
396 const checkParts = parts.slice(0, i).concat(name);
397 const checkKey = this.implodeKey(checkParts);
398 info.addHistory(`Looked at ${checkKey} for a match`);
399
400 const existing = this.tree.get(checkKey);
401
402 if (existing) {
403 if (existing.loc === info.loc) {
404 // switch to non ignored if earlier deduped version was ignored (must be compatible)
405 if (!existing.isRequired && info.isRequired) {
406 existing.addHistory(`Deduped ${fullKey} to this item, marking as required`);
407 existing.isRequired = true;
408 } else {
409 existing.addHistory(`Deduped ${fullKey} to this item`);
410 }
411
412 return { parts: checkParts, duplicate: true };
413 } else {
414 // everything above will be shadowed and this is a conflict
415 info.addHistory(`Found a collision at ${checkKey}`);
416 break;
417 }
418 }
419
420 const existingTaint = this.taintedKeys.get(checkKey);
421 if (existingTaint && existingTaint.loc !== info.loc) {
422 info.addHistory(`Broken by ${checkKey}`);
423 break;
424 }
425 }
426
427 const peerDependencies = Object.keys(info.pkg.peerDependencies || {});
428
429 // remove redundant parts that wont collide
430 hoistLoop: while (parts.length > highestHoistingPoint) {
431 // we must not hoist a package higher than its peer dependencies
432 for (var _iterator6 = peerDependencies, _isArray6 = Array.isArray(_iterator6), _i6 = 0, _iterator6 = _isArray6 ? _iterator6 : _iterator6[Symbol.iterator]();;) {
433 var _ref7;
434
435 if (_isArray6) {
436 if (_i6 >= _iterator6.length) break;
437 _ref7 = _iterator6[_i6++];
438 } else {
439 _i6 = _iterator6.next();
440 if (_i6.done) break;
441 _ref7 = _i6.value;
442 }
443
444 const peerDependency = _ref7;
445
446 const checkParts = parts.concat(peerDependency);
447 const checkKey = this.implodeKey(checkParts);
448 info.addHistory(`Looked at ${checkKey} for a peer dependency match`);
449
450 const existing = this.tree.get(checkKey);
451
452 if (existing) {
453 info.addHistory(`Found a peer dependency requirement at ${checkKey}`);
454 break hoistLoop;
455 }
456 }
457
458 const checkParts = parts.concat(name);
459 const checkKey = this.implodeKey(checkParts);
460
461 //
462 const existing = this.tree.get(checkKey);
463 if (existing) {
464 stepUp = true;
465 break;
466 }
467
468 // check if we're trying to hoist ourselves to a previously unflattened module key,
469 // this will result in a conflict and we'll need to move ourselves up
470 if (key !== checkKey && this.taintedKeys.has(checkKey)) {
471 stepUp = true;
472 break;
473 }
474
475 //
476 stack.push(parts.pop());
477 }
478
479 //
480 parts.push(name);
481
482 //
483 const isValidPosition = parts => {
484 // nohoist package can't be hoisted to the "root"
485 if (parts.length <= highestHoistingPoint) {
486 return false;
487 }
488 const key = this.implodeKey(parts);
489 const existing = this.tree.get(key);
490 if (existing && existing.loc === info.loc) {
491 return true;
492 }
493
494 // ensure there's no taint or the taint is us
495 const existingTaint = this.taintedKeys.get(key);
496 if (existingTaint && existingTaint.loc !== info.loc) {
497 return false;
498 }
499
500 return true;
501 };
502
503 // we need to special case when we attempt to hoist to the top level as the `existing` logic
504 // wont be hit in the above `while` loop and we could conflict
505 if (!isValidPosition(parts)) {
506 stepUp = true;
507 }
508
509 // sometimes we need to step up to a parent module to install ourselves
510 while (stepUp && stack.length) {
511 info.addHistory(`Stepping up from ${this.implodeKey(parts)}`);
512
513 parts.pop(); // remove `name`
514 parts.push(stack.pop(), name);
515
516 if (isValidPosition(parts)) {
517 info.addHistory(`Found valid position ${this.implodeKey(parts)}`);
518 stepUp = false;
519 }
520 }
521
522 return { parts, duplicate: false };
523 }
524
525 /**
526 * Hoist all seeded patterns to their highest positions.
527 */
528
529 hoist(info) {
530 const oldKey = info.key,
531 rawParts = info.parts;
532
533 // remove this item from the `tree` map so we can ignore it
534
535 this.tree.delete(oldKey);
536
537 var _getNewParts = this.getNewParts(oldKey, info, rawParts.slice());
538
539 const parts = _getNewParts.parts,
540 duplicate = _getNewParts.duplicate;
541
542
543 const newKey = this.implodeKey(parts);
544 if (duplicate) {
545 info.addHistory(`Satisfied from above by ${newKey}`);
546 this.declareRename(info, rawParts, parts);
547 this.updateHoistHistory(this.nohoistResolver._originalPath(info), this.implodeKey(parts));
548 return;
549 }
550
551 // update to the new key
552 if (oldKey === newKey) {
553 info.addHistory(`Didn't hoist - see reason above`);
554 this.setKey(info, oldKey, rawParts);
555 return;
556 }
557
558 //
559 this.declareRename(info, rawParts, parts);
560 this.setKey(info, newKey, parts);
561 }
562
563 /**
564 * Declare that a module has been hoisted and update our internal references.
565 */
566
567 declareRename(info, oldParts, newParts) {
568 // go down the tree from our new position reserving our name
569 this.taintParents(info, oldParts.slice(0, -1), newParts.length - 1);
570 }
571
572 /**
573 * Crawl upwards through a list of ancestry parts and taint a package name.
574 */
575
576 taintParents(info, processParts, start) {
577 for (let i = start; i < processParts.length; i++) {
578 const parts = processParts.slice(0, i).concat(info.pkg.name);
579 const key = this.implodeKey(parts);
580
581 if (this.taintKey(key, info)) {
582 info.addHistory(`Tainted ${key} to prevent collisions`);
583 }
584 }
585 }
586
587 updateHoistHistory(fromPath, toKey) {
588 const info = this.tree.get(toKey);
589 invariant(info, `expect to find hoist-to ${toKey}`);
590 info.previousPaths.push(fromPath);
591 }
592
593 /**
594 * Update the key of a module and update our references.
595 */
596
597 setKey(info, newKey, parts) {
598 const oldKey = info.key;
599
600 info.key = newKey;
601 info.parts = parts;
602 this.tree.set(newKey, info);
603
604 if (oldKey === newKey) {
605 return;
606 }
607
608 const fromInfo = this.tree.get(newKey);
609 invariant(fromInfo, `expect to find hoist-from ${newKey}`);
610 info.previousPaths.push(this.nohoistResolver._originalPath(fromInfo));
611 info.addHistory(`New position = ${newKey}`);
612 }
613
614 /**
615 * Perform a prepass and if there's multiple versions of the same package, hoist the one with
616 * the most dependents to the top.
617 */
618
619 prepass(patterns) {
620 patterns = this.resolver.dedupePatterns(patterns).sort();
621
622 const visited = new Map();
623
624 const occurences = {};
625
626 // visitor to be used inside add() to mark occurences of packages
627 const visitAdd = (pkg, ancestry, pattern) => {
628 const versions = occurences[pkg.name] = occurences[pkg.name] || {};
629 const version = versions[pkg.version] = versions[pkg.version] || {
630 occurences: new Set(),
631 pattern
632 };
633
634 if (ancestry.length) {
635 version.occurences.add(ancestry[ancestry.length - 1]);
636 }
637 };
638
639 // add an occurring package to the above data structure
640 const add = (pattern, ancestry, ancestryPatterns) => {
641 const pkg = this.resolver.getStrictResolvedPattern(pattern);
642 if (ancestry.indexOf(pkg) >= 0) {
643 // prevent recursive dependencies
644 return;
645 }
646
647 let visitedPattern = visited.get(pattern);
648
649 if (visitedPattern) {
650 // if a package has been visited before, simply increment occurrences of packages
651 // like last time this package was visited
652 visitedPattern.forEach(visitPkg => {
653 visitAdd(visitPkg.pkg, visitPkg.ancestry, visitPkg.pattern);
654 });
655
656 visitAdd(pkg, ancestry, pattern);
657
658 return;
659 }
660
661 const ref = pkg._reference;
662 invariant(ref, 'expected reference');
663
664 visitAdd(pkg, ancestry, pattern);
665
666 for (var _iterator7 = ref.dependencies, _isArray7 = Array.isArray(_iterator7), _i7 = 0, _iterator7 = _isArray7 ? _iterator7 : _iterator7[Symbol.iterator]();;) {
667 var _ref8;
668
669 if (_isArray7) {
670 if (_i7 >= _iterator7.length) break;
671 _ref8 = _iterator7[_i7++];
672 } else {
673 _i7 = _iterator7.next();
674 if (_i7.done) break;
675 _ref8 = _i7.value;
676 }
677
678 const depPattern = _ref8;
679
680 const depAncestry = ancestry.concat(pkg);
681 const depAncestryPatterns = ancestryPatterns.concat(depPattern);
682 add(depPattern, depAncestry, depAncestryPatterns);
683 }
684
685 visitedPattern = visited.get(pattern) || [];
686 visited.set(pattern, visitedPattern);
687 visitedPattern.push({ pkg, ancestry, pattern });
688
689 ancestryPatterns.forEach(ancestryPattern => {
690 const visitedAncestryPattern = visited.get(ancestryPattern);
691 if (visitedAncestryPattern) {
692 visitedAncestryPattern.push({ pkg, ancestry, pattern });
693 }
694 });
695 };
696
697 // get a list of root package names since we can't hoist other dependencies to these spots!
698 const rootPackageNames = new Set();
699 for (var _iterator8 = patterns, _isArray8 = Array.isArray(_iterator8), _i8 = 0, _iterator8 = _isArray8 ? _iterator8 : _iterator8[Symbol.iterator]();;) {
700 var _ref9;
701
702 if (_isArray8) {
703 if (_i8 >= _iterator8.length) break;
704 _ref9 = _iterator8[_i8++];
705 } else {
706 _i8 = _iterator8.next();
707 if (_i8.done) break;
708 _ref9 = _i8.value;
709 }
710
711 const pattern = _ref9;
712
713 const pkg = this.resolver.getStrictResolvedPattern(pattern);
714 rootPackageNames.add(pkg.name);
715 add(pattern, [], []);
716 }
717
718 for (var _iterator9 = Object.keys(occurences).sort(), _isArray9 = Array.isArray(_iterator9), _i9 = 0, _iterator9 = _isArray9 ? _iterator9 : _iterator9[Symbol.iterator]();;) {
719 var _ref10;
720
721 if (_isArray9) {
722 if (_i9 >= _iterator9.length) break;
723 _ref10 = _iterator9[_i9++];
724 } else {
725 _i9 = _iterator9.next();
726 if (_i9.done) break;
727 _ref10 = _i9.value;
728 }
729
730 const packageName = _ref10;
731
732 const versionOccurences = occurences[packageName];
733 const versions = Object.keys(versionOccurences);
734
735 if (versions.length === 1) {
736 // only one package type so we'll hoist this to the top anyway
737 continue;
738 }
739
740 if (this.tree.get(packageName)) {
741 // a transitive dependency of a previously hoisted dependency exists
742 continue;
743 }
744
745 if (rootPackageNames.has(packageName)) {
746 // can't replace top level packages
747 continue;
748 }
749
750 let mostOccurenceCount;
751 let mostOccurencePattern;
752 for (var _iterator10 = Object.keys(versionOccurences).sort(), _isArray10 = Array.isArray(_iterator10), _i10 = 0, _iterator10 = _isArray10 ? _iterator10 : _iterator10[Symbol.iterator]();;) {
753 var _ref11;
754
755 if (_isArray10) {
756 if (_i10 >= _iterator10.length) break;
757 _ref11 = _iterator10[_i10++];
758 } else {
759 _i10 = _iterator10.next();
760 if (_i10.done) break;
761 _ref11 = _i10.value;
762 }
763
764 const version = _ref11;
765 var _versionOccurences$ve = versionOccurences[version];
766 const occurences = _versionOccurences$ve.occurences,
767 pattern = _versionOccurences$ve.pattern;
768
769 const occurenceCount = occurences.size;
770
771 if (!mostOccurenceCount || occurenceCount > mostOccurenceCount) {
772 mostOccurenceCount = occurenceCount;
773 mostOccurencePattern = pattern;
774 }
775 }
776 invariant(mostOccurencePattern, 'expected most occurring pattern');
777 invariant(mostOccurenceCount, 'expected most occurring count');
778
779 // only hoist this module if it occured more than once
780 if (mostOccurenceCount > 1) {
781 this._seed(mostOccurencePattern, { isDirectRequire: false });
782 }
783 }
784 }
785
786 markShallowWorkspaceEntries() {
787 const targetWorkspace = this.config.focusedWorkspaceName;
788 const targetHoistManifest = this.tree.get(targetWorkspace);
789 invariant(targetHoistManifest, `targetHoistManifest from ${targetWorkspace} missing`);
790
791 //dedupe with a set
792 const dependentWorkspaces = Array.from(new Set(this._getDependentWorkspaces(targetHoistManifest)));
793
794 const entries = Array.from(this.tree);
795 entries.forEach(([key, info]) => {
796 const splitPath = key.split('#');
797
798 //mark the workspace and any un-hoisted dependencies it has for shallow installation
799 const isShallowDependency = dependentWorkspaces.some(w => {
800 if (splitPath[0] !== w) {
801 //entry is not related to the workspace
802 return false;
803 }
804 if (!splitPath[1]) {
805 //entry is the workspace
806 return true;
807 }
808 //don't bother marking dev dependencies or nohoist packages for shallow installation
809 const treeEntry = this.tree.get(w);
810 invariant(treeEntry, 'treeEntry is not defined for ' + w);
811 const pkg = treeEntry.pkg;
812 return !info.isNohoist && (!pkg.devDependencies || !(splitPath[1] in pkg.devDependencies));
813 });
814
815 if (isShallowDependency) {
816 info.shallowPaths = [null];
817 return;
818 }
819
820 //if package foo is at TARGET_WORKSPACE/node_modules/foo, the hoisted version of foo
821 //should be installed under each shallow workspace that uses it
822 //(unless that workspace has its own version of foo, in which case that should be installed)
823 if (splitPath.length !== 2 || splitPath[0] !== targetWorkspace) {
824 return;
825 }
826 const unhoistedDependency = splitPath[1];
827 const unhoistedInfo = this.tree.get(unhoistedDependency);
828 if (!unhoistedInfo) {
829 return;
830 }
831 dependentWorkspaces.forEach(w => {
832 if (this._packageDependsOnHoistedPackage(w, unhoistedDependency, false)) {
833 unhoistedInfo.shallowPaths.push(w);
834 }
835 });
836 });
837 }
838
839 _getDependentWorkspaces(parent, allowDevDeps = true, alreadySeen = new Set()) {
840 const parentName = parent.pkg.name;
841 if (alreadySeen.has(parentName)) {
842 return [];
843 }
844
845 alreadySeen.add(parentName);
846 invariant(this.workspaceLayout, 'missing workspaceLayout');
847 var _workspaceLayout = this.workspaceLayout;
848 const virtualManifestName = _workspaceLayout.virtualManifestName,
849 workspaces = _workspaceLayout.workspaces;
850
851
852 const directDependencies = [];
853 const ignored = [];
854 Object.keys(workspaces).forEach(workspace => {
855 if (alreadySeen.has(workspace) || workspace === virtualManifestName) {
856 return;
857 }
858
859 //skip a workspace if a different version of it is already being installed under the parent workspace
860 let info = this.tree.get(`${parentName}#${workspace}`);
861 if (info) {
862 const workspaceVersion = workspaces[workspace].manifest.version;
863 if (info.isNohoist && info.originalParentPath.startsWith(`/${WS_ROOT_ALIAS}/${parentName}`) && info.pkg.version === workspaceVersion) {
864 //nohoist installations are exceptions
865 directDependencies.push(info.key);
866 } else {
867 ignored.push(workspace);
868 }
869 return;
870 }
871
872 const searchPath = `/${WS_ROOT_ALIAS}/${parentName}`;
873 info = this.tree.get(workspace);
874 invariant(info, 'missing workspace tree entry ' + workspace);
875 if (!info.previousPaths.some(p => p.startsWith(searchPath))) {
876 return;
877 }
878 if (allowDevDeps || !parent.pkg.devDependencies || !(workspace in parent.pkg.devDependencies)) {
879 directDependencies.push(workspace);
880 }
881 });
882
883 let nested = directDependencies.map(d => {
884 const dependencyEntry = this.tree.get(d);
885 invariant(dependencyEntry, 'missing dependencyEntry ' + d);
886 return this._getDependentWorkspaces(dependencyEntry, false, alreadySeen);
887 });
888 nested = [].concat.apply([], nested); //flatten
889
890 const directDependencyNames = directDependencies.map(d => d.split('#').slice(-1)[0]);
891
892 return directDependencyNames.concat(nested).filter(w => ignored.indexOf(w) === -1);
893 }
894
895 _packageDependsOnHoistedPackage(p, hoisted, checkDevDeps = true, checked = new Set()) {
896 //don't check the same package more than once, and ignore any package that has its own version of hoisted
897 if (checked.has(p) || this.tree.has(`${p}#${hoisted}`)) {
898 return false;
899 }
900 checked.add(p);
901 const info = this.tree.get(p);
902 if (!info) {
903 return false;
904 }
905
906 const pkg = info.pkg;
907 if (!pkg) {
908 return false;
909 }
910
911 let deps = [];
912 if (pkg.dependencies) {
913 deps = deps.concat(Object.keys(pkg.dependencies));
914 }
915 if (checkDevDeps && pkg.devDependencies) {
916 deps = deps.concat(Object.keys(pkg.devDependencies));
917 }
918
919 if (deps.indexOf(hoisted) !== -1) {
920 return true;
921 }
922 return deps.some(dep => this._packageDependsOnHoistedPackage(dep, hoisted, false, checked));
923 }
924
925 /**
926 * Produce a flattened list of module locations and manifests.
927 */
928
929 init() {
930 const flatTree = [];
931
932 //
933 for (var _iterator11 = this.tree.entries(), _isArray11 = Array.isArray(_iterator11), _i11 = 0, _iterator11 = _isArray11 ? _iterator11 : _iterator11[Symbol.iterator]();;) {
934 var _ref13;
935
936 if (_isArray11) {
937 if (_i11 >= _iterator11.length) break;
938 _ref13 = _iterator11[_i11++];
939 } else {
940 _i11 = _iterator11.next();
941 if (_i11.done) break;
942 _ref13 = _i11.value;
943 }
944
945 const _ref12 = _ref13;
946 const key = _ref12[0];
947 const info = _ref12[1];
948
949 // decompress the location and push it to the flat tree. this path could be made
950 const parts = [];
951 const keyParts = key.split('#');
952 const isWorkspaceEntry = this.workspaceLayout && keyParts[0] === this.workspaceLayout.virtualManifestName;
953
954 // Don't add the virtual manifest (keyParts.length === 1)
955 // or ws childs which were not hoisted to the root (keyParts.length === 2).
956 // If a ws child was hoisted its key would not contain the virtual manifest name
957 if (isWorkspaceEntry && keyParts.length <= 2) {
958 continue;
959 }
960
961 for (let i = 0; i < keyParts.length; i++) {
962 const key = keyParts.slice(0, i + 1).join('#');
963 const hoisted = this.tree.get(key);
964 invariant(hoisted, `expected hoisted manifest for "${key}"`);
965 parts.push(this.config.getFolder(hoisted.pkg));
966 parts.push(keyParts[i]);
967 }
968
969 // Check if the destination is pointing to a sub folder of the virtualManifestName
970 // e.g. _project_/node_modules/workspace-aggregator-123456/node_modules/workspaceChild/node_modules/dependency
971 // This probably happened because the hoister was not able to hoist the workspace child to the root
972 // So we have to change the folder to the workspace package location
973 if (this.workspaceLayout && isWorkspaceEntry) {
974 const wspPkg = this.workspaceLayout.workspaces[keyParts[1]];
975 invariant(wspPkg, `expected workspace package to exist for "${keyParts[1]}"`);
976 parts.splice(0, 4, wspPkg.loc);
977 } else {
978 if (this.config.modulesFolder) {
979 // remove the first part which will be the folder name and replace it with a
980 // hardcoded modules folder
981 parts.splice(0, 1, this.config.modulesFolder);
982 } else {
983 // first part will be the registry-specific module folder
984 parts.splice(0, 0, this.config.lockfileFolder);
985 }
986 }
987
988 const shallowLocs = [];
989 info.shallowPaths.forEach(shallowPath => {
990 const shallowCopyParts = parts.slice();
991 shallowCopyParts[0] = this.config.cwd;
992 if (this.config.modulesFolder) {
993 //add back the module folder name for the shallow installation
994 const treeEntry = this.tree.get(keyParts[0]);
995 invariant(treeEntry, 'expected treeEntry for ' + keyParts[0]);
996 const moduleFolderName = this.config.getFolder(treeEntry.pkg);
997 shallowCopyParts.splice(1, 0, moduleFolderName);
998 }
999
1000 if (shallowPath) {
1001 const targetWorkspace = this.config.focusedWorkspaceName;
1002 const treeEntry = this.tree.get(`${targetWorkspace}#${shallowPath}`) || this.tree.get(shallowPath);
1003 invariant(treeEntry, 'expected treeEntry for ' + shallowPath);
1004 const moduleFolderName = this.config.getFolder(treeEntry.pkg);
1005 shallowCopyParts.splice(1, 0, moduleFolderName, shallowPath);
1006 }
1007 shallowLocs.push(path.join(...shallowCopyParts));
1008 });
1009
1010 const loc = path.join(...parts);
1011 flatTree.push([loc, info]);
1012 shallowLocs.forEach(shallowLoc => {
1013 const newManifest = (0, (_extends2 || _load_extends()).default)({}, info, { isShallow: true });
1014 flatTree.push([shallowLoc, newManifest]);
1015 });
1016 }
1017
1018 // remove ignored modules from the tree
1019 const visibleFlatTree = [];
1020 for (var _iterator12 = flatTree, _isArray12 = Array.isArray(_iterator12), _i12 = 0, _iterator12 = _isArray12 ? _iterator12 : _iterator12[Symbol.iterator]();;) {
1021 var _ref15;
1022
1023 if (_isArray12) {
1024 if (_i12 >= _iterator12.length) break;
1025 _ref15 = _iterator12[_i12++];
1026 } else {
1027 _i12 = _iterator12.next();
1028 if (_i12.done) break;
1029 _ref15 = _i12.value;
1030 }
1031
1032 const _ref14 = _ref15;
1033 const loc = _ref14[0];
1034 const info = _ref14[1];
1035
1036 const ref = info.pkg._reference;
1037 invariant(ref, 'expected reference');
1038 if (!info.isRequired) {
1039 info.addHistory('Deleted as this module was ignored');
1040 } else {
1041 visibleFlatTree.push([loc, info]);
1042 }
1043 }
1044 return visibleFlatTree;
1045 }
1046}
1047
1048exports.default = PackageHoister;
1049const WS_ROOT_ALIAS = '_project_';
1050class NohoistResolver {
1051 constructor(config, resolver) {
1052 this.initNohoist = (info, parent) => {
1053 let parentNohoistList;
1054 let originalParentPath = info.originalParentPath;
1055
1056 if (parent) {
1057 parentNohoistList = parent.nohoistList;
1058 originalParentPath = this._originalPath(parent);
1059 } else {
1060 invariant(this._isTopPackage(info), `${info.key} doesn't have parent nor a top package`);
1061 if (info.pkg.name !== this._wsRootPackageName) {
1062 parentNohoistList = this._wsRootNohoistList;
1063 originalParentPath = this._wsRootPackageName || '';
1064 }
1065 }
1066
1067 info.originalParentPath = originalParentPath;
1068 let nohoistList = this._extractNohoistList(info.pkg, this._originalPath(info)) || [];
1069 if (parentNohoistList) {
1070 nohoistList = nohoistList.concat(parentNohoistList);
1071 }
1072 info.nohoistList = nohoistList.length > 0 ? nohoistList : null;
1073 info.isNohoist = this._isNohoist(info);
1074 };
1075
1076 this.highestHoistingPoint = info => {
1077 return info.isNohoist && info.parts.length > 1 ? 1 : null;
1078 };
1079
1080 this._isNohoist = info => {
1081 if (this._isTopPackage(info)) {
1082 return false;
1083 }
1084 if (info.nohoistList && info.nohoistList.length > 0 && (_micromatch || _load_micromatch()).default.any(this._originalPath(info), info.nohoistList)) {
1085 return true;
1086 }
1087 if (this._config.plugnplayEnabled) {
1088 return true;
1089 }
1090 return false;
1091 };
1092
1093 this._isRootPackage = pkg => {
1094 return pkg.name === this._wsRootPackageName;
1095 };
1096
1097 this._originalPath = info => {
1098 return this._makePath(info.originalParentPath, info.pkg.name);
1099 };
1100
1101 this._isTopPackage = info => {
1102 const parentParts = info.parts.slice(0, -1);
1103 const result = !parentParts || parentParts.length <= 0 || parentParts.length === 1 && parentParts[0] === this._wsRootPackageName;
1104 return result;
1105 };
1106
1107 this._isLink = info => {
1108 return info.pkg._remote != null && LINK_TYPES.has(info.pkg._remote.type);
1109 };
1110
1111 this._extractNohoistList = (pkg, pathPrefix) => {
1112 let nohoistList;
1113 const ws = this._config.getWorkspaces(pkg);
1114
1115 if (ws && ws.nohoist) {
1116 nohoistList = ws.nohoist.map(p => this._makePath(pathPrefix, p));
1117 }
1118 return nohoistList;
1119 };
1120
1121 this._resolver = resolver;
1122 this._config = config;
1123 if (resolver.workspaceLayout) {
1124 this._wsRootPackageName = resolver.workspaceLayout.virtualManifestName;
1125
1126 var _resolver$workspaceLa = resolver.workspaceLayout.getWorkspaceManifest(this._wsRootPackageName);
1127
1128 const manifest = _resolver$workspaceLa.manifest;
1129
1130 this._wsRootNohoistList = this._extractNohoistList(manifest, manifest.name);
1131 }
1132 }
1133
1134 /**
1135 * examine the top level packages to find the root package
1136 */
1137
1138
1139 /**
1140 * find the highest hoisting point for the given HoistManifest.
1141 * algorithm: a nohoist package should never be hoisted beyond the top of its branch, i.e.
1142 * the first element of its parts. Therefore the highest possible hoisting index is 1,
1143 * unless the package has only 1 part (itself), in such case returns null just like any hoisted package
1144 *
1145 */
1146
1147 // private functions
1148
1149 _makePath(...args) {
1150 const parts = args.map(s => s === this._wsRootPackageName ? WS_ROOT_ALIAS : s);
1151 const result = parts.join('/');
1152 return result[0] === '/' ? result : '/' + result;
1153 }
1154
1155 // extract nohoist from package.json then prefix them with branch path
1156 // so we can matched against the branch tree ("originalPath") later
1157}
1158
1159exports.NohoistResolver = NohoistResolver;
\No newline at end of file