Project.js 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829
  1. var glob = require('glob');
  2. var path = require('path');
  3. var fs = require('graceful-fs');
  4. var Q = require('q');
  5. var mout = require('mout');
  6. var rimraf = require('rimraf');
  7. var endpointParser = require('bower-endpoint-parser');
  8. var Logger = require('bower-logger');
  9. var Manager = require('./Manager');
  10. var defaultConfig = require('../config');
  11. var semver = require('../util/semver');
  12. var md5 = require('../util/md5');
  13. var createError = require('../util/createError');
  14. var readJson = require('../util/readJson');
  15. var validLink = require('../util/validLink');
  16. var scripts = require('./scripts');
  17. function Project(config, logger) {
  18. // This is the only architecture component that ensures defaults
  19. // on config and logger
  20. // The reason behind it is that users can likely use this component
  21. // directly if commands do not fulfil their needs
  22. this._config = defaultConfig(config);
  23. this._logger = logger || new Logger();
  24. this._manager = new Manager(this._config, this._logger);
  25. this._options = {};
  26. }
  27. // -----------------
  28. Project.prototype.install = function (decEndpoints, options, config) {
  29. var that = this;
  30. var targets = [];
  31. var resolved = {};
  32. var incompatibles = [];
  33. // If already working, error out
  34. if (this._working) {
  35. return Q.reject(createError('Already working', 'EWORKING'));
  36. }
  37. this._options = options || {};
  38. this._config = config || {};
  39. this._working = true;
  40. // Analyse the project
  41. return this._analyse()
  42. .spread(function (json, tree) {
  43. // It shows an error when issuing `bower install`
  44. // and no bower.json is present in current directory
  45. if(!that._jsonFile && decEndpoints.length === 0 ) {
  46. throw createError('No bower.json present', 'ENOENT');
  47. }
  48. // Recover tree
  49. that.walkTree(tree, function (node, name) {
  50. if (node.incompatible) {
  51. incompatibles.push(node);
  52. } else if (node.missing || node.different || that._config.force) {
  53. targets.push(node);
  54. } else {
  55. resolved[name] = node;
  56. }
  57. }, true);
  58. // Add decomposed endpoints as targets
  59. decEndpoints = decEndpoints || [];
  60. decEndpoints.forEach(function (decEndpoint) {
  61. // Mark as new so that a conflict for this target
  62. // always require a choice
  63. // Also allows for the target to be converted in case
  64. // of being *
  65. decEndpoint.newly = true;
  66. targets.push(decEndpoint);
  67. });
  68. // Bootstrap the process
  69. return that._bootstrap(targets, resolved, incompatibles);
  70. })
  71. .then(function () {
  72. return that._manager.preinstall(that._json);
  73. })
  74. .then(function () {
  75. return that._manager.install(that._json);
  76. })
  77. .then(function (installed) {
  78. // Handle save and saveDev options
  79. if (that._options.save || that._options.saveDev) {
  80. // Cycle through the specified endpoints
  81. decEndpoints.forEach(function (decEndpoint) {
  82. var jsonEndpoint;
  83. jsonEndpoint = endpointParser.decomposed2json(decEndpoint);
  84. if (that._options.save) {
  85. that._json.dependencies = mout.object.mixIn(that._json.dependencies || {}, jsonEndpoint);
  86. }
  87. if (that._options.saveDev) {
  88. that._json.devDependencies = mout.object.mixIn(that._json.devDependencies || {}, jsonEndpoint);
  89. }
  90. });
  91. }
  92. // Save JSON, might contain changes to dependencies and resolutions
  93. return that.saveJson()
  94. .then(function () {
  95. return that._manager.postinstall(that._json).then(function () {
  96. return installed;
  97. });
  98. });
  99. })
  100. .fin(function () {
  101. that._installed = null;
  102. that._working = false;
  103. });
  104. };
  105. Project.prototype.update = function (names, options) {
  106. var that = this;
  107. var targets = [];
  108. var resolved = {};
  109. var incompatibles = [];
  110. // If already working, error out
  111. if (this._working) {
  112. return Q.reject(createError('Already working', 'EWORKING'));
  113. }
  114. this._options = options || {};
  115. this._working = true;
  116. // Analyse the project
  117. return this._analyse()
  118. .spread(function (json, tree, flattened) {
  119. // If no names were specified, update every package
  120. if (!names) {
  121. // Mark each root dependency as targets
  122. that.walkTree(tree, function (node) {
  123. // We don't know the real source of linked packages
  124. // Instead we read its dependencies
  125. if (node.linked) {
  126. targets.push.apply(targets, mout.object.values(node.dependencies));
  127. } else {
  128. targets.push(node);
  129. }
  130. return false;
  131. }, true);
  132. // Otherwise, selectively update the specified ones
  133. } else {
  134. // Error out if some of the specified names
  135. // are not installed
  136. names.forEach(function (name) {
  137. if (!flattened[name]) {
  138. throw createError('Package ' + name + ' is not installed', 'ENOTINS', {
  139. name: name
  140. });
  141. }
  142. });
  143. // Add packages whose names are specified to be updated
  144. that.walkTree(tree, function (node, name) {
  145. if (names.indexOf(name) !== -1) {
  146. // We don't know the real source of linked packages
  147. // Instead we read its dependencies
  148. if (node.linked) {
  149. targets.push.apply(targets, mout.object.values(node.dependencies));
  150. } else {
  151. targets.push(node);
  152. }
  153. return false;
  154. }
  155. }, true);
  156. // Recover tree
  157. that.walkTree(tree, function (node, name) {
  158. if (node.missing || node.different) {
  159. targets.push(node);
  160. } else if (node.incompatible) {
  161. incompatibles.push(node);
  162. } else {
  163. resolved[name] = node;
  164. }
  165. }, true);
  166. }
  167. // Bootstrap the process
  168. return that._bootstrap(targets, resolved, incompatibles)
  169. .then(function () {
  170. return that._manager.preinstall(that._json);
  171. })
  172. .then(function () {
  173. return that._manager.install(that._json);
  174. })
  175. .then(function (installed) {
  176. // Save JSON, might contain changes to resolutions
  177. return that.saveJson()
  178. .then(function () {
  179. return that._manager.postinstall(that._json).then(function () {
  180. return installed;
  181. });
  182. });
  183. });
  184. })
  185. .fin(function () {
  186. that._installed = null;
  187. that._working = false;
  188. });
  189. };
  190. Project.prototype.uninstall = function (names, options) {
  191. var that = this;
  192. var packages = {};
  193. // If already working, error out
  194. if (this._working) {
  195. return Q.reject(createError('Already working', 'EWORKING'));
  196. }
  197. this._options = options || {};
  198. this._working = true;
  199. // Analyse the project
  200. return this._analyse()
  201. // Fill in the packages to be uninstalled
  202. .spread(function (json, tree, flattened) {
  203. var promise = Q.resolve();
  204. names.forEach(function (name) {
  205. var decEndpoint = flattened[name];
  206. // Check if it is not installed
  207. if (!decEndpoint || decEndpoint.missing) {
  208. packages[name] = null;
  209. return;
  210. }
  211. promise = promise
  212. .then(function () {
  213. var message;
  214. var data;
  215. var dependantsNames;
  216. var dependants = [];
  217. // Walk the down the tree, gathering dependants of the package
  218. that.walkTree(tree, function (node, nodeName) {
  219. if (name === nodeName) {
  220. dependants.push.apply(dependants, mout.object.values(node.dependants));
  221. }
  222. }, true);
  223. // Remove duplicates
  224. dependants = mout.array.unique(dependants);
  225. // Note that the root is filtered from the dependants
  226. // as well as other dependants marked to be uninstalled
  227. dependants = dependants.filter(function (dependant) {
  228. return !dependant.root && names.indexOf(dependant.name) === -1;
  229. });
  230. // If the package has no dependants or the force config is enabled,
  231. // mark it to be removed
  232. if (!dependants.length || that._config.force) {
  233. packages[name] = decEndpoint.canonicalDir;
  234. return;
  235. }
  236. // Otherwise we need to figure it out if the user really wants to remove it,
  237. // even with dependants
  238. // As such we need to prompt the user with a meaningful message
  239. dependantsNames = dependants.map(function (dep) { return dep.name; });
  240. dependantsNames.sort(function (name1, name2) { return name1.localeCompare(name2); });
  241. dependantsNames = mout.array.unique(dependantsNames);
  242. dependants = dependants.map(function (dependant) { return that._manager.toData(dependant); });
  243. message = dependantsNames.join(', ') + ' depends on ' + decEndpoint.name;
  244. data = {
  245. name: decEndpoint.name,
  246. dependants: dependants
  247. };
  248. // If interactive is disabled, error out
  249. if (!that._config.interactive) {
  250. throw createError(message, 'ECONFLICT', {
  251. data: data
  252. });
  253. }
  254. that._logger.conflict('mutual', message, data);
  255. // Prompt the user
  256. return Q.nfcall(that._logger.prompt.bind(that._logger), {
  257. type: 'confirm',
  258. message: 'Continue anyway?',
  259. default: true
  260. })
  261. .then(function (confirmed) {
  262. // If the user decided to skip it, remove from the array so that it won't
  263. // influence subsequent dependants
  264. if (!confirmed) {
  265. mout.array.remove(names, name);
  266. } else {
  267. packages[name] = decEndpoint.canonicalDir;
  268. }
  269. });
  270. });
  271. });
  272. return promise;
  273. })
  274. // Remove packages
  275. .then(function () {
  276. return that._removePackages(packages);
  277. })
  278. .fin(function () {
  279. that._installed = null;
  280. that._working = false;
  281. });
  282. };
  283. Project.prototype.getTree = function (options) {
  284. this._options = options || {};
  285. return this._analyse()
  286. .spread(function (json, tree, flattened) {
  287. var extraneous = [];
  288. var additionalKeys = ['missing', 'extraneous', 'different', 'linked'];
  289. // Convert tree
  290. tree = this._manager.toData(tree, additionalKeys);
  291. // Mark incompatibles
  292. this.walkTree(tree, function (node) {
  293. var version;
  294. var target = node.endpoint.target;
  295. if (node.pkgMeta && semver.validRange(target)) {
  296. version = node.pkgMeta.version;
  297. // Ignore if target is '*' and resolved to a non-semver release
  298. if (!version && target === '*') {
  299. return;
  300. }
  301. if (!version || !semver.satisfies(version, target)) {
  302. node.incompatible = true;
  303. }
  304. }
  305. }, true);
  306. // Convert extraneous
  307. mout.object.forOwn(flattened, function (pkg) {
  308. if (pkg.extraneous) {
  309. extraneous.push(this._manager.toData(pkg, additionalKeys));
  310. }
  311. }, this);
  312. // Convert flattened
  313. flattened = mout.object.map(flattened, function (node) {
  314. return this._manager.toData(node, additionalKeys);
  315. }, this);
  316. return [tree, flattened, extraneous];
  317. }.bind(this));
  318. };
  319. Project.prototype.walkTree = function (node, fn, onlyOnce) {
  320. var result;
  321. var dependencies;
  322. var queue = mout.object.values(node.dependencies);
  323. if (onlyOnce === true) {
  324. onlyOnce = [];
  325. }
  326. while (queue.length) {
  327. node = queue.shift();
  328. result = fn(node, node.endpoint ? node.endpoint.name : node.name);
  329. // Abort traversal if result is false
  330. if (result === false) {
  331. continue;
  332. }
  333. // Add dependencies to the queue
  334. dependencies = mout.object.values(node.dependencies);
  335. // If onlyOnce was true, do not add if already traversed
  336. if (onlyOnce) {
  337. dependencies = dependencies.filter(function (dependency) {
  338. return !mout.array.find(onlyOnce, function (stacked) {
  339. if (dependency.endpoint) {
  340. return mout.object.equals(dependency.endpoint, stacked.endpoint);
  341. }
  342. return dependency.name === stacked.name &&
  343. dependency.source === stacked.source &&
  344. dependency.target === stacked.target;
  345. });
  346. });
  347. onlyOnce.push.apply(onlyOnce, dependencies);
  348. }
  349. queue.unshift.apply(queue, dependencies);
  350. }
  351. };
  352. Project.prototype.saveJson = function (forceCreate) {
  353. var file;
  354. var jsonStr = JSON.stringify(this._json, null, ' ') + '\n';
  355. var jsonHash = md5(jsonStr);
  356. // Save only if there's something different
  357. if (jsonHash === this._jsonHash) {
  358. return Q.resolve();
  359. }
  360. // Error out if the json file does not exist, unless force create
  361. // is true
  362. if (!this._jsonFile && !forceCreate) {
  363. this._logger.warn('no-json', 'No bower.json file to save to, use bower init to create one');
  364. return Q.resolve();
  365. }
  366. file = this._jsonFile || path.join(this._config.cwd, 'bower.json');
  367. return Q.nfcall(fs.writeFile, file, jsonStr)
  368. .then(function () {
  369. this._jsonHash = jsonHash;
  370. this._jsonFile = file;
  371. return this._json;
  372. }.bind(this));
  373. };
  374. Project.prototype.hasJson = function () {
  375. return this._readJson()
  376. .then(function (json) {
  377. return json ? this._jsonFile : false;
  378. }.bind(this));
  379. };
  380. Project.prototype.getJson = function () {
  381. return this._readJson();
  382. };
  383. Project.prototype.getManager = function () {
  384. return this._manager;
  385. };
  386. Project.prototype.getPackageRepository = function () {
  387. return this._manager.getPackageRepository();
  388. };
  389. // -----------------
  390. Project.prototype._analyse = function () {
  391. return Q.all([
  392. this._readJson(),
  393. this._readInstalled(),
  394. this._readLinks()
  395. ])
  396. .spread(function (json, installed, links) {
  397. var root;
  398. var jsonCopy = mout.lang.deepClone(json);
  399. root = {
  400. name: json.name,
  401. source: this._config.cwd,
  402. target: json.version || '*',
  403. pkgMeta: jsonCopy,
  404. canonicalDir: this._config.cwd,
  405. root: true
  406. };
  407. mout.object.mixIn(installed, links);
  408. // Mix direct extraneous as dependencies
  409. // (dependencies installed without --save/--save-dev)
  410. jsonCopy.dependencies = jsonCopy.dependencies || {};
  411. jsonCopy.devDependencies = jsonCopy.devDependencies || {};
  412. mout.object.forOwn(installed, function (decEndpoint, key) {
  413. var pkgMeta = decEndpoint.pkgMeta;
  414. var isSaved = jsonCopy.dependencies[key] || jsonCopy.devDependencies[key];
  415. // The _direct propery is saved by the manager when .newly is specified
  416. // It may happen pkgMeta is undefined if package is uninstalled
  417. if (!isSaved && pkgMeta && pkgMeta._direct) {
  418. decEndpoint.extraneous = true;
  419. if (decEndpoint.linked) {
  420. jsonCopy.dependencies[key] = pkgMeta.version || '*';
  421. } else {
  422. jsonCopy.dependencies[key] = (pkgMeta._originalSource || pkgMeta._source) + '#' + pkgMeta._target;
  423. }
  424. }
  425. });
  426. // Restore the original dependencies cross-references,
  427. // that is, the parent-child relationships
  428. this._restoreNode(root, installed, 'dependencies');
  429. // Do the same for the dev dependencies
  430. if (!this._options.production) {
  431. this._restoreNode(root, installed, 'devDependencies');
  432. }
  433. // Restore the rest of the extraneous (not installed directly)
  434. mout.object.forOwn(installed, function (decEndpoint, name) {
  435. if (!decEndpoint.dependants) {
  436. decEndpoint.extraneous = true;
  437. this._restoreNode(decEndpoint, installed, 'dependencies');
  438. // Note that it has no dependants, just dependencies!
  439. root.dependencies[name] = decEndpoint;
  440. }
  441. }, this);
  442. // Remove root from the flattened tree
  443. delete installed[json.name];
  444. return [json, root, installed];
  445. }.bind(this));
  446. };
  447. Project.prototype._bootstrap = function (targets, resolved, incompatibles) {
  448. var installed = mout.object.map(this._installed, function (decEndpoint) {
  449. return decEndpoint.pkgMeta;
  450. });
  451. this._json.resolutions = this._json.resolutions || {};
  452. // Configure the manager and kick in the resolve process
  453. return this._manager
  454. .configure({
  455. targets: targets,
  456. resolved: resolved,
  457. incompatibles: incompatibles,
  458. resolutions: this._json.resolutions,
  459. installed: installed,
  460. forceLatest: this._options.forceLatest
  461. })
  462. .resolve()
  463. .then(function () {
  464. // If the resolutions is empty, delete key
  465. if (!mout.object.size(this._json.resolutions)) {
  466. delete this._json.resolutions;
  467. }
  468. }.bind(this));
  469. };
  470. Project.prototype._readJson = function () {
  471. var that = this;
  472. if (this._json) {
  473. return Q.resolve(this._json);
  474. }
  475. // Read local json
  476. return this._json = readJson(this._config.cwd, {
  477. assume: { name: path.basename(this._config.cwd) || 'root' }
  478. })
  479. .spread(function (json, deprecated, assumed) {
  480. var jsonStr;
  481. if (deprecated) {
  482. that._logger.warn('deprecated', 'You are using the deprecated ' + deprecated + ' file');
  483. }
  484. if (!assumed) {
  485. that._jsonFile = path.join(that._config.cwd, deprecated ? deprecated : 'bower.json');
  486. }
  487. jsonStr = JSON.stringify(json, null, ' ') + '\n';
  488. that._jsonHash = md5(jsonStr);
  489. return that._json = json;
  490. });
  491. };
  492. Project.prototype._readInstalled = function () {
  493. var componentsDir;
  494. var that = this;
  495. if (this._installed) {
  496. return Q.resolve(this._installed);
  497. }
  498. // Gather all folders that are actual packages by
  499. // looking for the package metadata file
  500. componentsDir = path.join(this._config.cwd, this._config.directory);
  501. return this._installed = Q.nfcall(glob, '*/.bower.json', {
  502. cwd: componentsDir,
  503. dot: true
  504. })
  505. .then(function (filenames) {
  506. var promises;
  507. var decEndpoints = {};
  508. // Foreach bower.json found
  509. promises = filenames.map(function (filename) {
  510. var name = path.dirname(filename);
  511. var metaFile = path.join(componentsDir, filename);
  512. // Read package metadata
  513. return readJson(metaFile)
  514. .spread(function (pkgMeta) {
  515. decEndpoints[name] = {
  516. name: name,
  517. source: pkgMeta._originalSource || pkgMeta._source,
  518. target: pkgMeta._target,
  519. canonicalDir: path.dirname(metaFile),
  520. pkgMeta: pkgMeta
  521. };
  522. });
  523. });
  524. // Wait until all files have been read
  525. // and resolve with the decomposed endpoints
  526. return Q.all(promises)
  527. .then(function () {
  528. return that._installed = decEndpoints;
  529. });
  530. });
  531. };
  532. Project.prototype._readLinks = function () {
  533. var componentsDir;
  534. var that = this;
  535. // Read directory, looking for links
  536. componentsDir = path.join(this._config.cwd, this._config.directory);
  537. return Q.nfcall(fs.readdir, componentsDir)
  538. .then(function (filenames) {
  539. var promises;
  540. var decEndpoints = {};
  541. promises = filenames.map(function (filename) {
  542. var dir = path.join(componentsDir, filename);
  543. // Filter only those that are valid links
  544. return validLink(dir)
  545. .spread(function (valid, err) {
  546. var name;
  547. if (!valid) {
  548. if (err) {
  549. that._logger.debug('read-link', 'Link ' + dir + ' is invalid', {
  550. filename: dir,
  551. error: err
  552. });
  553. }
  554. return;
  555. }
  556. // Skip links to files (see #783)
  557. if (!valid.isDirectory()) {
  558. return;
  559. }
  560. name = path.basename(dir);
  561. return readJson(dir, {
  562. assume: { name: name }
  563. })
  564. .spread(function (json, deprecated) {
  565. if (deprecated) {
  566. that._logger.warn('deprecated', 'Package ' + name + ' is using the deprecated ' + deprecated);
  567. }
  568. json._direct = true; // Mark as a direct dep of root
  569. decEndpoints[name] = {
  570. name: name,
  571. source: dir,
  572. target: '*',
  573. canonicalDir: dir,
  574. pkgMeta: json,
  575. linked: true
  576. };
  577. });
  578. });
  579. });
  580. // Wait until all links have been read
  581. // and resolve with the decomposed endpoints
  582. return Q.all(promises)
  583. .then(function () {
  584. return decEndpoints;
  585. });
  586. // Ignore if folder does not exist
  587. }, function (err) {
  588. if (err.code !== 'ENOENT') {
  589. throw err;
  590. }
  591. return {};
  592. });
  593. };
  594. Project.prototype._removePackages = function (packages) {
  595. var that = this;
  596. var promises = [];
  597. return scripts.preuninstall(that._config, that._logger, packages, that._installed, that._json)
  598. .then(function () {
  599. mout.object.forOwn(packages, function (dir, name) {
  600. var promise;
  601. // Delete directory
  602. if (!dir) {
  603. promise = Q.resolve();
  604. that._logger.warn('not-installed', '\'' + name + '\'' + ' cannot be uninstalled as it is not currently installed', {
  605. name: name
  606. });
  607. } else {
  608. promise = Q.nfcall(rimraf, dir);
  609. that._logger.action('uninstall', name, {
  610. name: name,
  611. dir: dir
  612. });
  613. }
  614. // Remove from json only if successfully deleted
  615. if (that._options.save && that._json.dependencies) {
  616. promise = promise
  617. .then(function () {
  618. delete that._json.dependencies[name];
  619. });
  620. }
  621. if (that._options.saveDev && that._json.devDependencies) {
  622. promise = promise
  623. .then(function () {
  624. delete that._json.devDependencies[name];
  625. });
  626. }
  627. promises.push(promise);
  628. });
  629. return Q.all(promises);
  630. })
  631. .then(function () {
  632. return that.saveJson();
  633. })
  634. // Resolve with removed packages
  635. .then(function () {
  636. return mout.object.filter(packages, function (dir) {
  637. return !!dir;
  638. });
  639. });
  640. };
  641. Project.prototype._restoreNode = function (node, flattened, jsonKey, processed) {
  642. var deps;
  643. // Do not restore if the node is missing
  644. if (node.missing) {
  645. return;
  646. }
  647. node.dependencies = node.dependencies || {};
  648. node.dependants = node.dependants || {};
  649. processed = processed || {};
  650. // Only process deps that are not yet processed
  651. deps = mout.object.filter(node.pkgMeta[jsonKey], function (value, key) {
  652. return !processed[node.name + ':' + key];
  653. });
  654. mout.object.forOwn(deps, function (value, key) {
  655. var local = flattened[key];
  656. var json = endpointParser.json2decomposed(key, value);
  657. var restored;
  658. var compatible;
  659. var originalSource;
  660. // Check if the dependency is not installed
  661. if (!local) {
  662. flattened[key] = restored = json;
  663. restored.missing = true;
  664. // Even if it is installed, check if it's compatible
  665. // Note that linked packages are interpreted as compatible
  666. // This might change in the future: #673
  667. } else {
  668. compatible = local.linked || (!local.missing && json.target === local.pkgMeta._target);
  669. if (!compatible) {
  670. restored = json;
  671. if (!local.missing) {
  672. restored.pkgMeta = local.pkgMeta;
  673. restored.canonicalDir = local.canonicalDir;
  674. restored.incompatible = true;
  675. } else {
  676. restored.missing = true;
  677. }
  678. } else {
  679. restored = local;
  680. mout.object.mixIn(local, json);
  681. }
  682. // Check if source changed, marking as different if it did
  683. // We only do this for direct root dependencies that are compatible
  684. if (node.root && compatible) {
  685. originalSource = mout.object.get(local, 'pkgMeta._originalSource');
  686. if (originalSource && originalSource !== json.source) {
  687. restored.different = true;
  688. }
  689. }
  690. }
  691. // Cross reference
  692. node.dependencies[key] = restored;
  693. processed[node.name + ':' + key] = true;
  694. restored.dependants = restored.dependants || {};
  695. restored.dependants[node.name] = mout.object.mixIn({}, node); // We need to clone due to shared objects in the manager!
  696. // Call restore for this dependency
  697. this._restoreNode(restored, flattened, 'dependencies', processed);
  698. // Do the same for the incompatible local package
  699. if (local && restored !== local) {
  700. this._restoreNode(local, flattened, 'dependencies', processed);
  701. }
  702. }, this);
  703. };
  704. module.exports = Project;