UNPKG

35.7 kBJavaScriptView Raw
1var glob = require('glob');
2var path = require('path');
3var fs = require('../util/fs');
4var Q = require('q');
5var mout = require('mout');
6var rimraf = require('../util/rimraf');
7var endpointParser = require('bower-endpoint-parser');
8var Logger = require('bower-logger');
9var md5 = require('md5-hex');
10var Manager = require('./Manager');
11var semver = require('../util/semver');
12var createError = require('../util/createError');
13var readJson = require('../util/readJson');
14var validLink = require('../util/validLink');
15var scripts = require('./scripts');
16var relativeToBaseDir = require('../util/relativeToBaseDir');
17
18function Project(config, logger) {
19 this._config = config;
20 this._logger = logger || new Logger();
21 this._manager = new Manager(this._config, this._logger);
22
23 this._options = {};
24}
25
26// -----------------
27
28Project.prototype.install = function(decEndpoints, options, config) {
29 var that = this;
30 var targets = [];
31 var resolved = {};
32 var incompatibles = [];
33
34 // If already working, error out
35 if (this._working) {
36 return Q.reject(createError('Already working', 'EWORKING'));
37 }
38
39 this._options = options || {};
40 this._config = config || {};
41 this._working = true;
42
43 // Analyse the project
44 return this._analyse()
45 .spread(function(json, tree) {
46 // It shows an error when issuing `bower install`
47 // and no bower.json is present in current directory
48 if (!that._jsonFile && decEndpoints.length === 0) {
49 throw createError('No bower.json present', 'ENOENT');
50 }
51
52 // Recover tree
53 that.walkTree(
54 tree,
55 function(node, name) {
56 if (node.incompatible) {
57 incompatibles.push(node);
58 } else if (
59 node.missing ||
60 node.different ||
61 that._config.force
62 ) {
63 targets.push(node);
64 } else {
65 resolved[name] = node;
66 }
67 },
68 true
69 );
70
71 // Add decomposed endpoints as targets
72 decEndpoints = decEndpoints || [];
73 decEndpoints.forEach(function(decEndpoint) {
74 // Mark as new so that a conflict for this target
75 // always require a choice
76 // Also allows for the target to be converted in case
77 // of being *
78 decEndpoint.newly = true;
79 targets.push(decEndpoint);
80 });
81
82 // Bootstrap the process
83 return that._bootstrap(targets, resolved, incompatibles);
84 })
85 .then(function() {
86 return that._manager.preinstall(that._json);
87 })
88 .then(function() {
89 return that._manager.install(that._json);
90 })
91 .then(function(installed) {
92 // Handle save and saveDev options
93 if (
94 that._options.save ||
95 that._options.saveDev ||
96 that._options.saveExact ||
97 that._config.save ||
98 that._config.saveExact
99 ) {
100 // Cycle through the specified endpoints
101 decEndpoints.forEach(function(decEndpoint) {
102 var jsonEndpoint;
103
104 jsonEndpoint = endpointParser.decomposed2json(decEndpoint);
105
106 if (that._options.saveExact || that._config.saveExact) {
107 if (decEndpoint.name !== decEndpoint.source) {
108 jsonEndpoint[decEndpoint.name] =
109 decEndpoint.source +
110 '#' +
111 decEndpoint.pkgMeta.version;
112 } else {
113 jsonEndpoint[decEndpoint.name] =
114 decEndpoint.pkgMeta.version;
115 }
116 }
117
118 if (that._options.saveDev) {
119 that._json.devDependencies = mout.object.mixIn(
120 that._json.devDependencies || {},
121 jsonEndpoint
122 );
123 } else {
124 that._json.dependencies = mout.object.mixIn(
125 that._json.dependencies || {},
126 jsonEndpoint
127 );
128 }
129 });
130 }
131
132 // Save JSON, might contain changes to dependencies and resolutions
133 return that.saveJson().then(function() {
134 return that._manager.postinstall(that._json).then(function() {
135 return installed;
136 });
137 });
138 })
139 .fin(function() {
140 that._installed = null;
141 that._working = false;
142 });
143};
144
145Project.prototype.update = function(names, options) {
146 var that = this;
147 var targets = [];
148 var resolved = {};
149 var incompatibles = [];
150
151 // If already working, error out
152 if (this._working) {
153 return Q.reject(createError('Already working', 'EWORKING'));
154 }
155
156 this._options = options || {};
157 this._working = true;
158
159 // Analyse the project
160 return this._analyse()
161 .spread(function(json, tree, flattened) {
162 // If no names were specified, update every package
163 if (!names) {
164 // Mark each root dependency as targets
165 that.walkTree(
166 tree,
167 function(node) {
168 // We don't know the real source of linked packages
169 // Instead we read its dependencies
170 if (node.linked) {
171 targets.push.apply(
172 targets,
173 mout.object.values(node.dependencies)
174 );
175 } else {
176 targets.push(node);
177 }
178
179 return false;
180 },
181 true
182 );
183 // Otherwise, selectively update the specified ones
184 } else {
185 // Error out if some of the specified names
186 // are not installed
187 names.forEach(function(name) {
188 if (!flattened[name]) {
189 throw createError(
190 'Package ' + name + ' is not installed',
191 'ENOTINS',
192 {
193 name: name
194 }
195 );
196 }
197 });
198
199 // Add packages whose names are specified to be updated
200 that.walkTree(
201 tree,
202 function(node, name) {
203 if (names.indexOf(name) !== -1) {
204 // We don't know the real source of linked packages
205 // Instead we read its dependencies
206 if (node.linked) {
207 targets.push.apply(
208 targets,
209 mout.object.values(node.dependencies)
210 );
211 } else {
212 targets.push(node);
213 }
214
215 return false;
216 }
217 },
218 true
219 );
220
221 // Recover tree
222 that.walkTree(
223 tree,
224 function(node, name) {
225 if (node.missing || node.different) {
226 targets.push(node);
227 } else if (node.incompatible) {
228 incompatibles.push(node);
229 } else {
230 resolved[name] = node;
231 }
232 },
233 true
234 );
235 }
236
237 // Bootstrap the process
238 return that
239 ._bootstrap(targets, resolved, incompatibles)
240 .then(function() {
241 return that._manager.preinstall(that._json);
242 })
243 .then(function() {
244 return that._manager.install(that._json);
245 })
246 .then(function(installed) {
247 // Save JSON, might contain changes to resolutions
248 return that.saveJson().then(function() {
249 return that._manager
250 .postinstall(that._json)
251 .then(function() {
252 return installed;
253 });
254 });
255 });
256 })
257 .fin(function() {
258 that._installed = null;
259 that._working = false;
260 });
261};
262
263function resolveUrlNames(names, flattened) {
264 for (var i = 0; i < names.length; i++)
265 if (!flattened[names[i]]) {
266 var url = names[i].trim().replace(/\/$/, '');
267 var packName;
268 for (packName in flattened)
269 if (!!flattened[packName].source)
270 if (
271 url ==
272 flattened[packName].source.trim().replace(/\/$/, '')
273 )
274 names[i] = packName;
275 }
276}
277
278Project.prototype.uninstall = function(names, options) {
279 var that = this;
280 var packages = {};
281
282 // If already working, error out
283 if (this._working) {
284 return Q.reject(createError('Already working', 'EWORKING'));
285 }
286
287 this._options = options || {};
288 this._working = true;
289
290 // Analyse the project
291 return (
292 this._analyse()
293 // Fill in the packages to be uninstalled
294 .spread(function(json, tree, flattened) {
295 var promise = Q.resolve();
296 resolveUrlNames(names, flattened);
297
298 names.forEach(function(name) {
299 var decEndpoint = flattened[name];
300
301 // Check if it is not installed
302 if (!decEndpoint || decEndpoint.missing) {
303 packages[name] = null;
304 return;
305 }
306
307 promise = promise.then(function() {
308 var message;
309 var data;
310 var dependantsNames;
311 var dependants = [];
312
313 // Walk the down the tree, gathering dependants of the package
314 that.walkTree(
315 tree,
316 function(node, nodeName) {
317 if (name === nodeName) {
318 dependants.push.apply(
319 dependants,
320 mout.object.values(node.dependants)
321 );
322 }
323 },
324 true
325 );
326
327 // Remove duplicates
328 dependants = mout.array.unique(dependants);
329
330 // Note that the root is filtered from the dependants
331 // as well as other dependants marked to be uninstalled
332 dependants = dependants.filter(function(dependant) {
333 return (
334 !dependant.root &&
335 names.indexOf(dependant.name) === -1
336 );
337 });
338
339 // If the package has no dependants or the force config is enabled,
340 // mark it to be removed
341 if (!dependants.length || that._config.force) {
342 packages[name] = decEndpoint.canonicalDir;
343 return;
344 }
345
346 // Otherwise we need to figure it out if the user really wants to remove it,
347 // even with dependants
348 // As such we need to prompt the user with a meaningful message
349 dependantsNames = dependants.map(function(dep) {
350 return dep.name;
351 });
352 dependantsNames.sort(function(name1, name2) {
353 return name1.localeCompare(name2);
354 });
355 dependantsNames = mout.array.unique(dependantsNames);
356 dependants = dependants.map(function(dependant) {
357 return that._manager.toData(dependant);
358 });
359 message =
360 dependantsNames.join(', ') +
361 ' depends on ' +
362 decEndpoint.name;
363 data = {
364 name: decEndpoint.name,
365 dependants: dependants
366 };
367
368 // If interactive is disabled, error out
369 if (!that._config.interactive) {
370 throw createError(message, 'ECONFLICT', {
371 data: data
372 });
373 }
374
375 that._logger.conflict('mutual', message, data);
376
377 // Prompt the user
378 return Q.nfcall(
379 that._logger.prompt.bind(that._logger),
380 {
381 type: 'confirm',
382 message: 'Continue anyway?',
383 default: true
384 }
385 ).then(function(confirmed) {
386 // If the user decided to skip it, remove from the array so that it won't
387 // influence subsequent dependants
388 if (!confirmed) {
389 mout.array.remove(names, name);
390 } else {
391 packages[name] = decEndpoint.canonicalDir;
392 }
393 });
394 });
395 });
396
397 return promise;
398 })
399 // Remove packages
400 .then(function() {
401 return that._removePackages(packages);
402 })
403 .fin(function() {
404 that._installed = null;
405 that._working = false;
406 })
407 );
408};
409
410Project.prototype.getTree = function(options) {
411 this._options = options || {};
412
413 return this._analyse().spread(
414 function(json, tree, flattened) {
415 var extraneous = [];
416 var additionalKeys = [
417 'missing',
418 'extraneous',
419 'different',
420 'linked'
421 ];
422
423 // Convert tree
424 tree = this._manager.toData(tree, additionalKeys);
425
426 // Mark incompatibles
427 this.walkTree(
428 tree,
429 function(node) {
430 var version;
431 var target = node.endpoint.target;
432
433 if (node.pkgMeta && semver.validRange(target)) {
434 version = node.pkgMeta.version;
435
436 // Ignore if target is '*' and resolved to a non-semver release
437 if (!version && target === '*') {
438 return;
439 }
440
441 if (!version || !semver.satisfies(version, target)) {
442 node.incompatible = true;
443 }
444 }
445 },
446 true
447 );
448
449 // Convert extraneous
450 mout.object.forOwn(
451 flattened,
452 function(pkg) {
453 if (pkg.extraneous) {
454 extraneous.push(
455 this._manager.toData(pkg, additionalKeys)
456 );
457 }
458 },
459 this
460 );
461
462 // Convert flattened
463 flattened = mout.object.map(
464 flattened,
465 function(node) {
466 return this._manager.toData(node, additionalKeys);
467 },
468 this
469 );
470
471 return [tree, flattened, extraneous];
472 }.bind(this)
473 );
474};
475
476Project.prototype.walkTree = function(node, fn, onlyOnce) {
477 var result;
478 var dependencies;
479 var queue = mout.object.values(node.dependencies);
480
481 if (onlyOnce === true) {
482 onlyOnce = [];
483 }
484
485 while (queue.length) {
486 node = queue.shift();
487 result = fn(node, node.endpoint ? node.endpoint.name : node.name);
488
489 // Abort traversal if result is false
490 if (result === false) {
491 continue;
492 }
493
494 // Add dependencies to the queue
495 dependencies = mout.object.values(node.dependencies);
496
497 // If onlyOnce was true, do not add if already traversed
498 if (onlyOnce) {
499 dependencies = dependencies.filter(function(dependency) {
500 return !mout.array.find(onlyOnce, function(stacked) {
501 if (dependency.endpoint) {
502 return mout.object.equals(
503 dependency.endpoint,
504 stacked.endpoint
505 );
506 }
507
508 return (
509 dependency.name === stacked.name &&
510 dependency.source === stacked.source &&
511 dependency.target === stacked.target
512 );
513 });
514 });
515
516 onlyOnce.push.apply(onlyOnce, dependencies);
517 }
518
519 queue.unshift.apply(queue, dependencies);
520 }
521};
522
523Project.prototype.saveJson = function(forceCreate) {
524 var file;
525 var jsonStr = JSON.stringify(this._json, null, ' ') + '\n';
526 var jsonHash = md5(jsonStr);
527
528 // Save only if there's something different
529 if (jsonHash === this._jsonHash) {
530 return Q.resolve();
531 }
532
533 // Error out if the json file does not exist, unless force create
534 // is true
535 if (!this._jsonFile && !forceCreate) {
536 this._logger.warn(
537 'no-json',
538 'No bower.json file to save to, use bower init to create one'
539 );
540 return Q.resolve();
541 }
542
543 file = this._jsonFile || path.join(this._config.cwd, 'bower.json');
544 return Q.nfcall(fs.writeFile, file, jsonStr).then(
545 function() {
546 this._jsonHash = jsonHash;
547 this._jsonFile = file;
548 return this._json;
549 }.bind(this)
550 );
551};
552
553Project.prototype.hasJson = function() {
554 return this._readJson().then(
555 function(json) {
556 return json ? this._jsonFile : false;
557 }.bind(this)
558 );
559};
560
561Project.prototype.getJson = function() {
562 return this._readJson();
563};
564
565Project.prototype.getManager = function() {
566 return this._manager;
567};
568
569Project.prototype.getPackageRepository = function() {
570 return this._manager.getPackageRepository();
571};
572
573// -----------------
574
575Project.prototype._analyse = function() {
576 return Q.all([
577 this._readJson(),
578 this._readInstalled(),
579 this._readLinks()
580 ]).spread(
581 function(json, installed, links) {
582 var root;
583 var jsonCopy = mout.lang.deepClone(json);
584
585 root = {
586 name: json.name,
587 source: this._config.cwd,
588 target: json.version || '*',
589 pkgMeta: jsonCopy,
590 canonicalDir: this._config.cwd,
591 root: true
592 };
593
594 mout.object.mixIn(installed, links);
595
596 // Mix direct extraneous as dependencies
597 // (dependencies installed without --save/--save-dev)
598 jsonCopy.dependencies = jsonCopy.dependencies || {};
599 jsonCopy.devDependencies = jsonCopy.devDependencies || {};
600 mout.object.forOwn(installed, function(decEndpoint, key) {
601 var pkgMeta = decEndpoint.pkgMeta;
602 var isSaved =
603 jsonCopy.dependencies[key] || jsonCopy.devDependencies[key];
604
605 // The _direct propery is saved by the manager when .newly is specified
606 // It may happen pkgMeta is undefined if package is uninstalled
607 if (!isSaved && pkgMeta && pkgMeta._direct) {
608 decEndpoint.extraneous = true;
609
610 if (decEndpoint.linked) {
611 jsonCopy.dependencies[key] = pkgMeta.version || '*';
612 } else {
613 jsonCopy.dependencies[key] =
614 (pkgMeta._originalSource || pkgMeta._source) +
615 '#' +
616 pkgMeta._target;
617 }
618 }
619 });
620
621 // Restore the original dependencies cross-references,
622 // that is, the parent-child relationships
623 this._restoreNode(root, installed, 'dependencies');
624 // Do the same for the dev dependencies
625 if (!this._options.production) {
626 this._restoreNode(root, installed, 'devDependencies');
627 }
628
629 // Restore the rest of the extraneous (not installed directly)
630 mout.object.forOwn(
631 installed,
632 function(decEndpoint, name) {
633 if (!decEndpoint.dependants) {
634 decEndpoint.extraneous = true;
635 this._restoreNode(
636 decEndpoint,
637 installed,
638 'dependencies'
639 );
640 // Note that it has no dependants, just dependencies!
641 root.dependencies[name] = decEndpoint;
642 }
643 },
644 this
645 );
646
647 // Remove root from the flattened tree
648 delete installed[json.name];
649
650 return [json, root, installed];
651 }.bind(this)
652 );
653};
654
655Project.prototype._bootstrap = function(targets, resolved, incompatibles) {
656 var installed = mout.object.map(this._installed, function(decEndpoint) {
657 return decEndpoint.pkgMeta;
658 });
659
660 this._json.resolutions = this._json.resolutions || {};
661
662 // Configure the manager and kick in the resolve process
663 return this._manager
664 .configure({
665 targets: targets,
666 resolved: resolved,
667 incompatibles: incompatibles,
668 resolutions: this._json.resolutions,
669 installed: installed,
670 forceLatest: this._options.forceLatest
671 })
672 .resolve()
673 .then(
674 function() {
675 // If the resolutions is empty, delete key
676 if (!mout.object.size(this._json.resolutions)) {
677 delete this._json.resolutions;
678 }
679 }.bind(this)
680 );
681};
682
683Project.prototype._readJson = function() {
684 var that = this;
685
686 if (this._json) {
687 return Q.resolve(this._json);
688 }
689
690 return Q.fcall(function() {
691 // This will throw if package.json does not exist
692 return fs.readFileSync(path.join(that._config.cwd, 'package.json'));
693 })
694 .then(function(buffer) {
695 // If package.json exists, use it's values as defaults
696 var defaults = {},
697 npm = JSON.parse(buffer.toString());
698
699 defaults.name =
700 npm.name || path.basename(that._config.cwd) || 'root';
701 defaults.description = npm.description;
702 defaults.main = npm.main;
703 defaults.authors = npm.contributors || npm.author;
704 defaults.license = npm.license;
705 defaults.keywords = npm.keywords;
706
707 return defaults;
708 })
709 .catch(function(err) {
710 // Most likely no package.json so just set default name
711 return { name: path.basename(that._config.cwd) || 'root' };
712 })
713 .then(function(defaults) {
714 return (that._json = readJson(that._config.cwd, {
715 assume: defaults,
716 logger: that._logger
717 }));
718 })
719 .spread(function(json, deprecated, assumed) {
720 var jsonStr;
721
722 if (deprecated) {
723 that._logger.warn(
724 'deprecated',
725 'You are using the deprecated ' + deprecated + ' file'
726 );
727 }
728
729 if (!assumed) {
730 that._jsonFile = path.join(
731 that._config.cwd,
732 deprecated ? deprecated : 'bower.json'
733 );
734 }
735
736 jsonStr = JSON.stringify(json, null, ' ') + '\n';
737 that._jsonHash = md5(jsonStr);
738 return (that._json = json);
739 });
740};
741
742Project.prototype._readInstalled = function() {
743 var componentsDir;
744 var that = this;
745
746 if (this._installed) {
747 return Q.resolve(this._installed);
748 }
749
750 // Gather all folders that are actual packages by
751 // looking for the package metadata file
752 componentsDir = relativeToBaseDir(this._config.cwd)(this._config.directory);
753 return (this._installed = Q.nfcall(glob, '*/.bower.json', {
754 cwd: componentsDir,
755 dot: true
756 }).then(function(filenames) {
757 var promises;
758 var decEndpoints = {};
759
760 // Foreach bower.json found
761 promises = filenames.map(function(filename) {
762 var name = path.dirname(filename);
763 var metaFile = path.join(componentsDir, filename);
764
765 // Read package metadata
766 return readJson(metaFile).spread(function(pkgMeta) {
767 decEndpoints[name] = {
768 name: name,
769 source: pkgMeta._originalSource || pkgMeta._source,
770 target: pkgMeta._target,
771 canonicalDir: path.dirname(metaFile),
772 pkgMeta: pkgMeta
773 };
774 });
775 });
776
777 // Wait until all files have been read
778 // and resolve with the decomposed endpoints
779 return Q.all(promises).then(function() {
780 return (that._installed = decEndpoints);
781 });
782 }));
783};
784
785Project.prototype._readLinks = function() {
786 var componentsDir;
787 var that = this;
788
789 // Read directory, looking for links
790 componentsDir = relativeToBaseDir(this._config.cwd)(this._config.directory);
791 return Q.nfcall(fs.readdir, componentsDir).then(
792 function(filenames) {
793 var promises;
794 var decEndpoints = {};
795
796 promises = filenames.map(function(filename) {
797 var dir = path.join(componentsDir, filename);
798
799 // Filter only those that are valid links
800 return validLink(dir).spread(function(valid, err) {
801 var name;
802
803 if (!valid) {
804 if (err) {
805 that._logger.debug(
806 'read-link',
807 'Link ' + dir + ' is invalid',
808 {
809 filename: dir,
810 error: err
811 }
812 );
813 }
814 return;
815 }
816
817 // Skip links to files (see #783)
818 if (!valid.isDirectory()) {
819 return;
820 }
821
822 name = path.basename(dir);
823 return readJson(dir, {
824 assume: { name: name }
825 }).spread(function(json, deprecated) {
826 if (deprecated) {
827 that._logger.warn(
828 'deprecated',
829 'Package ' +
830 name +
831 ' is using the deprecated ' +
832 deprecated
833 );
834 }
835
836 json._direct = true; // Mark as a direct dep of root
837 decEndpoints[name] = {
838 name: name,
839 source: dir,
840 target: '*',
841 canonicalDir: dir,
842 pkgMeta: json,
843 linked: true
844 };
845 });
846 });
847 });
848
849 // Wait until all links have been read
850 // and resolve with the decomposed endpoints
851 return Q.all(promises).then(function() {
852 return decEndpoints;
853 });
854 // Ignore if folder does not exist
855 },
856 function(err) {
857 if (err.code !== 'ENOENT') {
858 throw err;
859 }
860
861 return {};
862 }
863 );
864};
865
866Project.prototype._removePackages = function(packages) {
867 var that = this;
868 var promises = [];
869
870 return (
871 scripts
872 .preuninstall(
873 that._config,
874 that._logger,
875 packages,
876 that._installed,
877 that._json
878 )
879 .then(function() {
880 mout.object.forOwn(packages, function(dir, name) {
881 var promise;
882
883 // Delete directory
884 if (!dir) {
885 promise = Q.resolve();
886 that._logger.warn(
887 'not-installed',
888 "'" +
889 name +
890 "'" +
891 ' cannot be uninstalled as it is not currently installed',
892 {
893 name: name
894 }
895 );
896 } else {
897 promise = Q.nfcall(rimraf, dir);
898 that._logger.action('uninstall', name, {
899 name: name,
900 dir: dir
901 });
902 }
903
904 // Remove from json only if successfully deleted
905 if (
906 (that._options.save || that._config.save) &&
907 that._json.dependencies
908 ) {
909 promise = promise.then(function() {
910 delete that._json.dependencies[name];
911 });
912 }
913
914 if (that._options.saveDev && that._json.devDependencies) {
915 promise = promise.then(function() {
916 delete that._json.devDependencies[name];
917 });
918 }
919
920 promises.push(promise);
921 });
922
923 return Q.all(promises);
924 })
925 .then(function() {
926 return that.saveJson();
927 })
928 // Run post-uninstall hook before resolving with removed packages.
929 .then(
930 scripts.postuninstall.bind(
931 null,
932 that._config,
933 that._logger,
934 packages,
935 that._installed,
936 that._json
937 )
938 )
939 // Resolve with removed packages
940 .then(function() {
941 return mout.object.filter(packages, function(dir) {
942 return !!dir;
943 });
944 })
945 );
946};
947
948Project.prototype._restoreNode = function(node, flattened, jsonKey, processed) {
949 var deps;
950
951 // Do not restore if the node is missing
952 if (node.missing) {
953 return;
954 }
955
956 node.dependencies = node.dependencies || {};
957 node.dependants = node.dependants || {};
958 processed = processed || {};
959
960 // Only process deps that are not yet processed
961 deps = mout.object.filter(node.pkgMeta[jsonKey], function(value, key) {
962 return !processed[node.name + ':' + key];
963 });
964
965 mout.object.forOwn(
966 deps,
967 function(value, key) {
968 var local = flattened[key];
969 var json = endpointParser.json2decomposed(key, value);
970 var restored;
971 var compatible;
972 var originalSource;
973
974 // Check if the dependency is not installed
975 if (!local) {
976 flattened[key] = restored = json;
977 restored.missing = true;
978 // Even if it is installed, check if it's compatible
979 // Note that linked packages are interpreted as compatible
980 // This might change in the future: #673
981 } else {
982 compatible =
983 local.linked ||
984 (!local.missing && json.target === local.pkgMeta._target);
985
986 if (!compatible) {
987 restored = json;
988
989 if (!local.missing) {
990 restored.pkgMeta = local.pkgMeta;
991 restored.canonicalDir = local.canonicalDir;
992 restored.incompatible = true;
993 } else {
994 restored.missing = true;
995 }
996 } else {
997 restored = local;
998 mout.object.mixIn(local, json);
999 }
1000
1001 // Check if source changed, marking as different if it did
1002 // We only do this for direct root dependencies that are compatible
1003 if (node.root && compatible) {
1004 originalSource = mout.object.get(
1005 local,
1006 'pkgMeta._originalSource'
1007 );
1008 if (originalSource && originalSource !== json.source) {
1009 restored.different = true;
1010 }
1011 }
1012 }
1013
1014 // Cross reference
1015 node.dependencies[key] = restored;
1016 processed[node.name + ':' + key] = true;
1017
1018 restored.dependants = restored.dependants || {};
1019 restored.dependants[node.name] = mout.object.mixIn({}, node); // We need to clone due to shared objects in the manager!
1020
1021 // Call restore for this dependency
1022 this._restoreNode(restored, flattened, 'dependencies', processed);
1023
1024 // Do the same for the incompatible local package
1025 if (local && restored !== local) {
1026 this._restoreNode(local, flattened, 'dependencies', processed);
1027 }
1028 },
1029 this
1030 );
1031};
1032
1033module.exports = Project;