Manager.js 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011
  1. var Q = require('q');
  2. var mout = require('mout');
  3. var path = require('path');
  4. var mkdirp = require('mkdirp');
  5. var rimraf = require('rimraf');
  6. var fs = require('graceful-fs');
  7. var endpointParser = require('bower-endpoint-parser');
  8. var PackageRepository = require('./PackageRepository');
  9. var semver = require('../util/semver');
  10. var copy = require('../util/copy');
  11. var createError = require('../util/createError');
  12. var scripts = require('./scripts');
  13. function Manager(config, logger) {
  14. this._config = config;
  15. this._logger = logger;
  16. this._repository = new PackageRepository(this._config, this._logger);
  17. this.configure({});
  18. }
  19. // -----------------
  20. Manager.prototype.configure = function (setup) {
  21. var targetsHash = {};
  22. this._conflicted = {};
  23. // Targets
  24. this._targets = setup.targets || [];
  25. this._targets.forEach(function (decEndpoint) {
  26. decEndpoint.initialName = decEndpoint.name;
  27. decEndpoint.dependants = mout.object.values(decEndpoint.dependants);
  28. targetsHash[decEndpoint.name] = true;
  29. // If the endpoint is marked as newly, make it unresolvable
  30. decEndpoint.unresolvable = !!decEndpoint.newly;
  31. });
  32. // Resolved & installed
  33. this._resolved = {};
  34. this._installed = {};
  35. mout.object.forOwn(setup.resolved, function (decEndpoint, name) {
  36. decEndpoint.dependants = mout.object.values(decEndpoint.dependants);
  37. this._resolved[name] = [decEndpoint];
  38. this._installed[name] = decEndpoint.pkgMeta;
  39. }, this);
  40. // Installed
  41. mout.object.mixIn(this._installed, setup.installed);
  42. // Incompatibles
  43. this._incompatibles = {};
  44. setup.incompatibles = this._uniquify(setup.incompatibles || []);
  45. setup.incompatibles.forEach(function (decEndpoint) {
  46. var name = decEndpoint.name;
  47. this._incompatibles[name] = this._incompatibles[name] || [];
  48. this._incompatibles[name].push(decEndpoint);
  49. decEndpoint.dependants = mout.object.values(decEndpoint.dependants);
  50. // Mark as conflicted so that the resolution is not removed
  51. this._conflicted[name] = true;
  52. // If not a target/resolved, add as target
  53. if (!targetsHash[name] && !this._resolved[name]) {
  54. this._targets.push(decEndpoint);
  55. }
  56. }, this);
  57. // Resolutions
  58. this._resolutions = setup.resolutions || {};
  59. // Uniquify targets
  60. this._targets = this._uniquify(this._targets);
  61. // Force-latest
  62. this._forceLatest = !!setup.forceLatest;
  63. return this;
  64. };
  65. Manager.prototype.resolve = function () {
  66. // If already resolving, error out
  67. if (this._working) {
  68. return Q.reject(createError('Already working', 'EWORKING'));
  69. }
  70. // Reset stuff
  71. this._fetching = {};
  72. this._nrFetching = 0;
  73. this._failed = {};
  74. this._hasFailed = false;
  75. this._deferred = Q.defer();
  76. // If there's nothing to resolve, simply dissect
  77. if (!this._targets.length) {
  78. process.nextTick(this._dissect.bind(this));
  79. // Otherwise, fetch each target from the repository
  80. // and let the process roll out
  81. } else {
  82. this._targets.forEach(this._fetch.bind(this));
  83. }
  84. // Unset working flag when done
  85. return this._deferred.promise
  86. .fin(function () {
  87. this._working = false;
  88. }.bind(this));
  89. };
  90. Manager.prototype.preinstall = function (json) {
  91. var that = this;
  92. var componentsDir = path.join(this._config.cwd, this._config.directory);
  93. // If nothing to install, skip the code bellow
  94. if (mout.lang.isEmpty(that._dissected)) {
  95. return Q.resolve({});
  96. }
  97. return Q.nfcall(mkdirp, componentsDir)
  98. .then(function () {
  99. return scripts.preinstall(
  100. that._config, that._logger, that._dissected, that._installed, json
  101. );
  102. });
  103. };
  104. Manager.prototype.postinstall = function (json) {
  105. var that = this;
  106. var componentsDir = path.join(this._config.cwd, this._config.directory);
  107. // If nothing to install, skip the code bellow
  108. if (mout.lang.isEmpty(that._dissected)) {
  109. return Q.resolve({});
  110. }
  111. return Q.nfcall(mkdirp, componentsDir)
  112. .then(function () {
  113. return scripts.postinstall(
  114. that._config, that._logger, that._dissected, that._installed, json
  115. );
  116. });
  117. };
  118. Manager.prototype.install = function (json) {
  119. var componentsDir;
  120. var that = this;
  121. // If already resolving, error out
  122. if (this._working) {
  123. return Q.reject(createError('Already working', 'EWORKING'));
  124. }
  125. // If nothing to install, skip the code bellow
  126. if (mout.lang.isEmpty(that._dissected)) {
  127. return Q.resolve({});
  128. }
  129. componentsDir = path.join(this._config.cwd, this._config.directory);
  130. return Q.nfcall(mkdirp, componentsDir)
  131. .then(function () {
  132. var promises = [];
  133. mout.object.forOwn(that._dissected, function (decEndpoint, name) {
  134. var promise;
  135. var dst;
  136. var release = decEndpoint.pkgMeta._release;
  137. that._logger.action('install', name + (release ? '#' + release : ''), that.toData(decEndpoint));
  138. dst = path.join(componentsDir, name);
  139. // Remove existent and copy canonical dir
  140. promise = Q.nfcall(rimraf, dst)
  141. .then(copy.copyDir.bind(copy, decEndpoint.canonicalDir, dst))
  142. .then(function () {
  143. var metaFile = path.join(dst, '.bower.json');
  144. decEndpoint.canonicalDir = dst;
  145. // Store additional metadata in bower.json
  146. return Q.nfcall(fs.readFile, metaFile)
  147. .then(function (contents) {
  148. var json = JSON.parse(contents.toString());
  149. json._target = decEndpoint.target;
  150. json._originalSource = decEndpoint.source;
  151. if (decEndpoint.newly) {
  152. json._direct = true;
  153. }
  154. json = JSON.stringify(json, null, ' ');
  155. return Q.nfcall(fs.writeFile, metaFile, json);
  156. });
  157. });
  158. promises.push(promise);
  159. });
  160. return Q.all(promises);
  161. })
  162. .then(function () {
  163. // Sync up dissected dependencies and dependants
  164. // See: https://github.com/bower/bower/issues/879
  165. mout.object.forOwn(that._dissected, function (pkg) {
  166. // Sync dependencies
  167. mout.object.forOwn(pkg.dependencies, function (dependency, name) {
  168. var dissected = this._dissected[name] || (this._resolved[name] ? this._resolved[name][0] : dependency);
  169. pkg.dependencies[name] = dissected;
  170. }, this);
  171. // Sync dependants
  172. pkg.dependants = pkg.dependants.map(function (dependant) {
  173. var name = dependant.name;
  174. var dissected = this._dissected[name] || (this._resolved[name] ? this._resolved[name][0] : dependant);
  175. return dissected;
  176. }, this);
  177. }, that);
  178. // Resolve with meaningful data
  179. return mout.object.map(that._dissected, function (decEndpoint) {
  180. return this.toData(decEndpoint);
  181. }, that);
  182. })
  183. .fin(function () {
  184. this._working = false;
  185. }.bind(this));
  186. };
  187. Manager.prototype.toData = function (decEndpoint, extraKeys, upperDeps) {
  188. var names;
  189. var extra;
  190. var data = {};
  191. upperDeps = upperDeps || [];
  192. data.endpoint = mout.object.pick(decEndpoint, ['name', 'source', 'target']);
  193. if (decEndpoint.canonicalDir) {
  194. data.canonicalDir = decEndpoint.canonicalDir;
  195. data.pkgMeta = decEndpoint.pkgMeta;
  196. }
  197. if (extraKeys) {
  198. extra = mout.object.pick(decEndpoint, extraKeys);
  199. extra = mout.object.filter(extra, function (value) {
  200. return !!value;
  201. });
  202. mout.object.mixIn(data, extra);
  203. }
  204. if (decEndpoint.dependencies) {
  205. data.dependencies = {};
  206. // Call recursively for each dependency but ordered
  207. // by dependency names
  208. names = Object.keys(decEndpoint.dependencies).sort();
  209. names.forEach(function (name) {
  210. // Prevent from infinite recursion when installing cyclic
  211. // dependencies
  212. if (!mout.array.contains(upperDeps, name)) {
  213. data.dependencies[name] = this.toData(decEndpoint.dependencies[name],
  214. extraKeys,
  215. upperDeps.concat(decEndpoint.name));
  216. }
  217. }, this);
  218. }
  219. data.nrDependants = mout.object.size(decEndpoint.dependants);
  220. return data;
  221. };
  222. Manager.prototype.getPackageRepository = function () {
  223. return this._repository;
  224. };
  225. // -----------------
  226. Manager.prototype._fetch = function (decEndpoint) {
  227. var name = decEndpoint.name;
  228. // Check if the whole process started to fail fast
  229. if (this._hasFailed) {
  230. return;
  231. }
  232. // Mark as being fetched
  233. this._fetching[name] = this._fetching[name] || [];
  234. this._fetching[name].push(decEndpoint);
  235. this._nrFetching++;
  236. // Fetch it from the repository
  237. // Note that the promise is stored in the decomposed endpoint
  238. // because it might be reused if a similar endpoint needs to be resolved
  239. return decEndpoint.promise = this._repository.fetch(decEndpoint)
  240. // When done, call onFetchSuccess
  241. .spread(this._onFetchSuccess.bind(this, decEndpoint))
  242. // If it fails, call onFetchFailure
  243. .fail(this._onFetchError.bind(this, decEndpoint));
  244. };
  245. Manager.prototype._onFetchSuccess = function (decEndpoint, canonicalDir, pkgMeta, isTargetable) {
  246. var name;
  247. var resolved;
  248. var index;
  249. var incompatibles;
  250. var initialName = decEndpoint.initialName != null ? decEndpoint.initialName : decEndpoint.name;
  251. var fetching = this._fetching[initialName];
  252. // Remove from being fetched list
  253. mout.array.remove(fetching, decEndpoint);
  254. this._nrFetching--;
  255. // Store some needed stuff
  256. decEndpoint.name = name = decEndpoint.name || pkgMeta.name;
  257. decEndpoint.canonicalDir = canonicalDir;
  258. decEndpoint.pkgMeta = pkgMeta;
  259. delete decEndpoint.promise;
  260. // Add to the resolved list
  261. // If there's an exact equal endpoint, replace instead of adding
  262. // This can happen because the name might not be known from the start
  263. resolved = this._resolved[name] = this._resolved[name] || [];
  264. index = mout.array.findIndex(resolved, function (resolved) {
  265. return resolved.target === decEndpoint.target;
  266. });
  267. if (index !== -1) {
  268. // Merge dependants
  269. decEndpoint.dependants.push.apply(decEndpoint.dependants, resolved[index.dependants]);
  270. decEndpoint.dependants = this._uniquify(decEndpoint.dependants);
  271. resolved.splice(index, 1);
  272. }
  273. resolved.push(decEndpoint);
  274. // Parse dependencies
  275. this._parseDependencies(decEndpoint, pkgMeta, 'dependencies');
  276. // Check if there are incompatibilities for this package name
  277. // If there are, we need to fetch them
  278. incompatibles = this._incompatibles[name];
  279. if (incompatibles) {
  280. // Filter already resolved
  281. incompatibles = incompatibles.filter(function (incompatible) {
  282. return !resolved.some(function (decEndpoint) {
  283. return incompatible.target === decEndpoint.target;
  284. });
  285. }, this);
  286. // Filter being resolved
  287. incompatibles = incompatibles.filter(function (incompatible) {
  288. return !fetching.some(function (decEndpoint) {
  289. return incompatible.target === decEndpoint.target;
  290. });
  291. }, this);
  292. incompatibles.forEach(this._fetch.bind(this));
  293. delete this._incompatibles[name];
  294. }
  295. // If the package is not targetable, flag it
  296. // It will be needed later so that untargetable endpoints
  297. // will not get * converted to ~version
  298. if (!isTargetable) {
  299. decEndpoint.untargetable = true;
  300. }
  301. // If there are no more packages being fetched,
  302. // finish the resolve process by dissecting all resolved packages
  303. if (this._nrFetching <= 0) {
  304. process.nextTick(this._dissect.bind(this));
  305. }
  306. };
  307. Manager.prototype._onFetchError = function (decEndpoint, err) {
  308. var name = decEndpoint.name;
  309. err.data = err.data || {};
  310. err.data.endpoint = mout.object.pick(decEndpoint, ['name', 'source', 'target']);
  311. // Remove from being fetched list
  312. mout.array.remove(this._fetching[name], decEndpoint);
  313. this._nrFetching--;
  314. // Add to the failed list
  315. this._failed[name] = this._failed[name] || [];
  316. this._failed[name].push(err);
  317. delete decEndpoint.promise;
  318. // Make the whole process to fail fast
  319. this._failFast();
  320. // If there are no more packages being fetched,
  321. // finish the resolve process (with an error)
  322. if (this._nrFetching <= 0) {
  323. process.nextTick(this._dissect.bind(this));
  324. }
  325. };
  326. Manager.prototype._failFast = function () {
  327. if (this._hasFailed) {
  328. return;
  329. }
  330. this._hasFailed = true;
  331. // If after some amount of time all pending tasks haven't finished,
  332. // we force the process to end
  333. this._failFastTimeout = setTimeout(function () {
  334. this._nrFetching = Infinity;
  335. this._dissect();
  336. }.bind(this), 20000);
  337. };
  338. Manager.prototype._parseDependencies = function (decEndpoint, pkgMeta, jsonKey) {
  339. var pending = [];
  340. decEndpoint.dependencies = decEndpoint.dependencies || {};
  341. // Parse package dependencies
  342. mout.object.forOwn(pkgMeta[jsonKey], function (value, key) {
  343. var resolved;
  344. var fetching;
  345. var compatible;
  346. var childDecEndpoint = endpointParser.json2decomposed(key, value);
  347. // Check if a compatible one is already resolved
  348. // If there's one, we don't need to resolve it twice
  349. resolved = this._resolved[key];
  350. if (resolved) {
  351. // Find if there's one with the exact same target
  352. compatible = mout.array.find(resolved, function (resolved) {
  353. return childDecEndpoint.target === resolved.target;
  354. }, this);
  355. // If we found one, merge stuff instead of adding as resolved
  356. if (compatible) {
  357. decEndpoint.dependencies[key] = compatible;
  358. compatible.dependants.push(decEndpoint);
  359. compatible.dependants = this._uniquify(compatible.dependants);
  360. return;
  361. }
  362. // Find one that is compatible
  363. compatible = mout.array.find(resolved, function (resolved) {
  364. return this._areCompatible(childDecEndpoint, resolved);
  365. }, this);
  366. // If we found one, add as resolved
  367. // and copy resolved properties from the compatible one
  368. if (compatible) {
  369. decEndpoint.dependencies[key] = compatible;
  370. childDecEndpoint.canonicalDir = compatible.canonicalDir;
  371. childDecEndpoint.pkgMeta = compatible.pkgMeta;
  372. childDecEndpoint.dependencies = compatible.dependencies;
  373. childDecEndpoint.dependants = [decEndpoint];
  374. this._resolved[key].push(childDecEndpoint);
  375. return;
  376. }
  377. }
  378. // Check if a compatible one is being fetched
  379. // If there's one, we wait and reuse it to avoid resolving it twice
  380. fetching = this._fetching[key];
  381. if (fetching) {
  382. compatible = mout.array.find(fetching, function (fetching) {
  383. return this._areCompatible(childDecEndpoint, fetching);
  384. }, this);
  385. if (compatible) {
  386. pending.push(compatible.promise);
  387. return;
  388. }
  389. }
  390. // Mark endpoint as unresolvable if the parent is also unresolvable
  391. childDecEndpoint.unresolvable = !!decEndpoint.unresolvable;
  392. // Otherwise, just fetch it from the repository
  393. decEndpoint.dependencies[key] = childDecEndpoint;
  394. childDecEndpoint.dependants = [decEndpoint];
  395. this._fetch(childDecEndpoint);
  396. }, this);
  397. if (pending.length > 0) {
  398. Q.all(pending)
  399. .then(function () {
  400. this._parseDependencies(decEndpoint, pkgMeta, jsonKey);
  401. }.bind(this));
  402. }
  403. };
  404. Manager.prototype._dissect = function () {
  405. var err;
  406. var componentsDir;
  407. var promise = Q.resolve();
  408. var suitables = {};
  409. var that = this;
  410. // If something failed, reject the whole resolve promise
  411. // with the first error
  412. if (this._hasFailed) {
  413. clearTimeout(this._failFastTimeout); // Cancel fail fast timeout
  414. err = mout.object.values(this._failed)[0][0];
  415. this._deferred.reject(err);
  416. return;
  417. }
  418. // Find a suitable version for each package name
  419. mout.object.forOwn(this._resolved, function (decEndpoints, name) {
  420. var semvers;
  421. var nonSemvers;
  422. // Filter out non-semver ones
  423. semvers = decEndpoints.filter(function (decEndpoint) {
  424. return !!decEndpoint.pkgMeta.version;
  425. });
  426. // Sort semver ones DESC
  427. semvers.sort(function (first, second) {
  428. var result = semver.rcompare(first.pkgMeta.version, second.pkgMeta.version);
  429. // If they are equal and one of them is a wildcard target,
  430. // give lower priority
  431. if (!result) {
  432. if (first.target === '*') {
  433. return 1;
  434. }
  435. if (second.target === '*') {
  436. return -1;
  437. }
  438. }
  439. return result;
  440. });
  441. // Convert wildcard targets to semver range targets if they are newly
  442. // Note that this can only be made if they can be targetable
  443. // If they are not, the resolver is incapable of handling targets
  444. semvers.forEach(function (decEndpoint) {
  445. if (decEndpoint.newly && decEndpoint.target === '*' && !decEndpoint.untargetable) {
  446. decEndpoint.target = '~' + decEndpoint.pkgMeta.version;
  447. decEndpoint.originalTarget = '*';
  448. }
  449. });
  450. // Filter non-semver ones
  451. nonSemvers = decEndpoints.filter(function (decEndpoint) {
  452. return !decEndpoint.pkgMeta.version;
  453. });
  454. promise = promise.then(function () {
  455. return that._electSuitable(name, semvers, nonSemvers)
  456. .then(function (suitable) {
  457. suitables[name] = suitable;
  458. });
  459. });
  460. }, this);
  461. // After a suitable version has been elected for every package
  462. promise
  463. .then(function () {
  464. // Look for extraneous resolutions
  465. mout.object.forOwn(this._resolutions, function (resolution, name) {
  466. if (this._conflicted[name]) {
  467. return;
  468. }
  469. this._logger.warn('extra-resolution', 'Unnecessary resolution: ' + name + '#' + resolution, {
  470. name: name,
  471. resolution: resolution,
  472. action: 'delete'
  473. });
  474. }, this);
  475. // Filter only packages that need to be installed
  476. componentsDir = path.resolve(that._config.cwd, that._config.directory);
  477. this._dissected = mout.object.filter(suitables, function (decEndpoint, name) {
  478. var installedMeta = this._installed[name];
  479. var dst;
  480. // Skip linked dependencies
  481. if (decEndpoint.linked) {
  482. return false;
  483. }
  484. // Skip if source is the same as dest
  485. dst = path.join(componentsDir, name);
  486. if (dst === decEndpoint.canonicalDir) {
  487. return false;
  488. }
  489. // Analyse a few props
  490. if (installedMeta &&
  491. installedMeta._target === decEndpoint.target &&
  492. installedMeta._originalSource === decEndpoint.source &&
  493. installedMeta._release === decEndpoint.pkgMeta._release
  494. ) {
  495. return this._config.force;
  496. }
  497. return true;
  498. }, this);
  499. }.bind(this))
  500. .then(this._deferred.resolve, this._deferred.reject);
  501. };
  502. Manager.prototype._electSuitable = function (name, semvers, nonSemvers) {
  503. var suitable;
  504. var resolution;
  505. var unresolvable;
  506. var dataPicks;
  507. var save;
  508. var choices;
  509. var picks = [];
  510. // If there are both semver and non-semver, there's no way
  511. // to figure out the suitable one
  512. if (semvers.length && nonSemvers.length) {
  513. picks.push.apply(picks, semvers);
  514. picks.push.apply(picks, nonSemvers);
  515. // If there are only non-semver ones, the suitable is elected
  516. // only if there's one
  517. } else if (nonSemvers.length) {
  518. if (nonSemvers.length === 1) {
  519. return Q.resolve(nonSemvers[0]);
  520. }
  521. picks.push.apply(picks, nonSemvers);
  522. // If there are only semver ones, figure out which one is
  523. // compatible with every requirement
  524. } else {
  525. suitable = mout.array.find(semvers, function (subject) {
  526. return semvers.every(function (decEndpoint) {
  527. return subject === decEndpoint ||
  528. semver.satisfies(subject.pkgMeta.version, decEndpoint.target);
  529. });
  530. });
  531. if (suitable) {
  532. return Q.resolve(suitable);
  533. }
  534. picks.push.apply(picks, semvers);
  535. }
  536. // At this point, there's a conflict
  537. this._conflicted[name] = true;
  538. // Prepare data to be sent bellow
  539. // 1 - Sort picks by version/release
  540. picks.sort(function (pick1, pick2) {
  541. var version1 = pick1.pkgMeta.version;
  542. var version2 = pick2.pkgMeta.version;
  543. var comp;
  544. // If both have versions, compare their versions using semver
  545. if (version1 && version2) {
  546. comp = semver.compare(version1, version2);
  547. if (comp) {
  548. return comp;
  549. }
  550. } else {
  551. // If one of them has a version, it's considered higher
  552. if (version1) {
  553. return 1;
  554. }
  555. if (version2) {
  556. return -1;
  557. }
  558. }
  559. // Give priority to the one with most dependants
  560. if (pick1.dependants.length > pick2.dependants.length) {
  561. return -1;
  562. }
  563. if (pick1.dependants.length < pick2.dependants.length) {
  564. return 1;
  565. }
  566. return 0;
  567. });
  568. // 2 - Transform data
  569. dataPicks = picks.map(function (pick) {
  570. var dataPick = this.toData(pick);
  571. dataPick.dependants = pick.dependants.map(this.toData, this);
  572. dataPick.dependants.sort(function (dependant1, dependant2) {
  573. return dependant1.endpoint.name.localeCompare(dependant2.endpoint.name);
  574. });
  575. return dataPick;
  576. }, this);
  577. // Check if there's a resolution that resolves the conflict
  578. // Note that if one of them is marked as unresolvable,
  579. // the resolution has no effect
  580. resolution = this._resolutions[name];
  581. unresolvable = mout.object.find(picks, function (pick) {
  582. return pick.unresolvable;
  583. });
  584. if (resolution && !unresolvable) {
  585. suitable = -1;
  586. // Range resolution
  587. if (semver.validRange(resolution)) {
  588. suitable = mout.array.findIndex(picks, function (pick) {
  589. return pick.pkgMeta.version &&
  590. semver.satisfies(pick.pkgMeta.version, resolution);
  591. });
  592. }
  593. // Exact match resolution (e.g. branches/tags)
  594. if (suitable === -1) {
  595. suitable = mout.array.findIndex(picks, function (pick) {
  596. return pick.target === resolution ||
  597. pick.pkgMeta._release === resolution;
  598. });
  599. }
  600. if (suitable === -1) {
  601. this._logger.warn('resolution', 'Unsuitable resolution declared for ' + name + ': ' + resolution, {
  602. name: name,
  603. picks: dataPicks,
  604. resolution: resolution
  605. });
  606. } else {
  607. this._logger.conflict('solved', 'Unable to find suitable version for ' + name, {
  608. name: name,
  609. picks: dataPicks,
  610. resolution: resolution,
  611. suitable: dataPicks[suitable]
  612. });
  613. return Q.resolve(picks[suitable]);
  614. }
  615. }
  616. // If force latest is enabled, resolve to the highest semver version
  617. // or whatever non-semver if none available
  618. if (this._forceLatest) {
  619. suitable = picks.length - 1;
  620. this._logger.conflict('solved', 'Unable to find suitable version for ' + name, {
  621. name: name,
  622. picks: dataPicks,
  623. suitable: dataPicks[suitable],
  624. forced: true
  625. });
  626. // Save resolution
  627. this._storeResolution(picks[suitable]);
  628. return Q.resolve(picks[suitable]);
  629. }
  630. // If interactive is disabled, error out
  631. if (!this._config.interactive) {
  632. throw createError('Unable to find suitable version for ' + name, 'ECONFLICT', {
  633. name: name,
  634. picks: dataPicks
  635. });
  636. }
  637. // At this point the user needs to make a decision
  638. this._logger.conflict('incompatible', 'Unable to find suitable version for ' + name, {
  639. name: name,
  640. picks: dataPicks
  641. });
  642. choices = picks.map(function (pick, index) { return index + 1; });
  643. return Q.nfcall(this._logger.prompt.bind(this._logger), {
  644. type: 'input',
  645. message: 'Answer:',
  646. validate: function (choice) {
  647. choice = Number(mout.string.trim(choice.trim(), '!'));
  648. if (!choice || choice < 1 || choice > picks.length) {
  649. return 'Invalid choice';
  650. }
  651. return true;
  652. }
  653. })
  654. .then(function (choice) {
  655. var pick;
  656. // Sanitize choice
  657. choice = choice.trim();
  658. save = /^!/.test(choice) || /!$/.test(choice); // Save if prefixed or suffixed with !
  659. choice = Number(mout.string.trim(choice, '!'));
  660. pick = picks[choice - 1];
  661. // Save resolution
  662. if (save) {
  663. this._storeResolution(pick);
  664. }
  665. return pick;
  666. }.bind(this));
  667. };
  668. Manager.prototype._storeResolution = function (pick) {
  669. var resolution;
  670. var name = pick.name;
  671. if (pick.target === '*') {
  672. resolution = pick.pkgMeta._release || '*';
  673. } else {
  674. resolution = pick.target;
  675. }
  676. this._logger.info('resolution', 'Saved ' + name + '#' + resolution + ' as resolution', {
  677. name: name,
  678. resolution: resolution,
  679. action: this._resolutions[name] ? 'edit' : 'add'
  680. });
  681. this._resolutions[name] = resolution;
  682. };
  683. /**
  684. * Checks if some endpoint is compatible with already resolved target.
  685. *
  686. * It is used in two situations:
  687. * * checks if resolved component matches dependency constraint
  688. * * checks if not resolved component matches alredy fetched component
  689. *
  690. * If candidate matches already resolved component, it won't be downloaded.
  691. *
  692. * @param {Endpoint} candidate endpoint
  693. * @param {Endpoint} resolved endpoint
  694. *
  695. * @return {Boolean}
  696. */
  697. Manager.prototype._areCompatible = function (candidate, resolved) {
  698. var resolvedVersion;
  699. var highestCandidate;
  700. var highestResolved;
  701. var candidateIsRange = semver.validRange(candidate.target);
  702. var resolvedIsRange = semver.validRange(resolved.target);
  703. var candidateIsVersion = semver.valid(candidate.target);
  704. var resolvedIsVersion = semver.valid(resolved.target);
  705. // Check if targets are equal
  706. if (candidate.target === resolved.target) {
  707. return true;
  708. }
  709. resolvedVersion = resolved.pkgMeta && resolved.pkgMeta.version;
  710. // If there is no pkgMeta, resolvedVersion is downloading now
  711. // Check based on target requirements
  712. if (!resolvedVersion) {
  713. // If one of the targets is range and other is version,
  714. // check version against the range
  715. if (candidateIsVersion && resolvedIsRange) {
  716. return semver.satisfies(candidate.target, resolved.target);
  717. }
  718. if (resolvedIsVersion && candidateIsRange) {
  719. return semver.satisfies(resolved.target, candidate.target);
  720. }
  721. if (resolvedIsVersion && candidateIsVersion) {
  722. return semver.eq(resolved.target, candidate.target);
  723. }
  724. // If both targets are range, check that both have same
  725. // higher cap
  726. if (resolvedIsRange && candidateIsRange) {
  727. highestCandidate =
  728. this._getCap(semver.toComparators(candidate.target), 'highest');
  729. highestResolved =
  730. this._getCap(semver.toComparators(resolved.target), 'highest');
  731. // This never happens, but you can't be sure without tests
  732. if (!highestResolved.version || !highestCandidate.version) {
  733. return false;
  734. }
  735. return semver.eq(highestCandidate.version, highestResolved.version) &&
  736. highestCandidate.comparator === highestResolved.comparator;
  737. }
  738. return false;
  739. }
  740. // If target is a version, compare against the resolved version
  741. if (candidateIsVersion) {
  742. return semver.eq(candidate.target, resolvedVersion);
  743. }
  744. // If target is a range, check if resolved version satisfies it
  745. if (candidateIsRange) {
  746. return semver.satisfies(resolvedVersion, candidate.target);
  747. }
  748. return false;
  749. };
  750. /**
  751. * Gets highest/lowest version from set of comparators.
  752. *
  753. * The only thing that matters for this function is version number.
  754. * Returned comparator is splitted to comparator and version parts.
  755. *
  756. * It is used to receive lowest / highest bound of toComparators result:
  757. * semver.toComparators('~0.1.1') // => [ [ '>=0.1.1-0', '<0.2.0-0' ] ]
  758. *
  759. * Examples:
  760. *
  761. * _getCap([['>=2.1.1-0', '<2.2.0-0'], '<3.2.0'], 'highest')
  762. * // => { comparator: '<', version: '3.2.0' }
  763. *
  764. * _getCap([['>=2.1.1-0', '<2.2.0-0'], '<3.2.0'], 'lowest')
  765. * // => { comparator: '>=', version: '2.1.1-0' }
  766. *
  767. * @param {Array.<Array|string>} comparators
  768. * @param {string} side, 'highest' (default) or 'lowest'
  769. *
  770. * @return {{ comparator: string, version: string }}
  771. */
  772. Manager.prototype._getCap = function (comparators, side) {
  773. var matches;
  774. var candidate;
  775. var cap = {};
  776. var compare = side === 'lowest' ? semver.lt : semver.gt;
  777. comparators.forEach(function (comparator) {
  778. // Get version of this comparator
  779. // If it's an array, call recursively
  780. if (Array.isArray(comparator)) {
  781. candidate = this._getCap(comparator, side);
  782. // Compare with the current highest version
  783. if (!cap.version || compare(candidate.version, cap.version)) {
  784. cap = candidate;
  785. }
  786. // Otherwise extract the version from the comparator
  787. // using a simple regexp
  788. } else {
  789. matches = comparator.match(/(.*?)(\d+\.\d+\.\d+.*)$/);
  790. if (!matches) {
  791. return;
  792. }
  793. // Compare with the current highest version
  794. if (!cap.version || compare(matches[2], cap.version)) {
  795. cap.version = matches[2];
  796. cap.comparator = matches[1];
  797. }
  798. }
  799. }, this);
  800. return cap;
  801. };
  802. /**
  803. * Filters out unique endpoints, comparing by name and then source.
  804. *
  805. * It leaves last matching endpoint.
  806. *
  807. * Examples:
  808. *
  809. * manager._uniquify([
  810. * { name: 'foo', source: 'google.com' },
  811. * { name: 'foo', source: 'facebook.com' }
  812. * ]);
  813. * // => { name: 'foo', source: 'facebook.com' }
  814. *
  815. * @param {Array.<Endpoint>} decEndpoints
  816. * @return {Array.<Endpoint>} Filtered elements of decEndpoints
  817. *
  818. */
  819. Manager.prototype._uniquify = function (decEndpoints) {
  820. var length = decEndpoints.length;
  821. return decEndpoints.filter(function (decEndpoint, index) {
  822. var x;
  823. var current;
  824. for (x = index + 1; x < length; ++x) {
  825. current = decEndpoints[x];
  826. if (current === decEndpoint) {
  827. return false;
  828. }
  829. // Compare name if both set
  830. // Fallback to compare sources
  831. if (!current.name && !decEndpoint.name) {
  832. if (current.source !== decEndpoint.source) {
  833. continue;
  834. }
  835. } else if (current.name !== decEndpoint.name) {
  836. continue;
  837. }
  838. // Compare targets if name/sources are equal
  839. if (current.target === decEndpoint.target) {
  840. return false;
  841. }
  842. }
  843. return true;
  844. });
  845. };
  846. module.exports = Manager;