clean.js 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. var fs = require('graceful-fs');
  2. var path = require('path');
  3. var mout = require('mout');
  4. var Q = require('q');
  5. var rimraf = require('rimraf');
  6. var endpointParser = require('bower-endpoint-parser');
  7. var PackageRepository = require('../../core/PackageRepository');
  8. var semver = require('../../util/semver');
  9. var cli = require('../../util/cli');
  10. var defaultConfig = require('../../config');
  11. function clean(logger, endpoints, options, config) {
  12. var decEndpoints;
  13. var names;
  14. options = options || {};
  15. config = defaultConfig(config);
  16. // If endpoints is an empty array, null them
  17. if (endpoints && !endpoints.length) {
  18. endpoints = null;
  19. }
  20. // Generate decomposed endpoints and names based on the endpoints
  21. if (endpoints) {
  22. decEndpoints = endpoints.map(function (endpoint) {
  23. return endpointParser.decompose(endpoint);
  24. });
  25. names = decEndpoints.map(function (decEndpoint) {
  26. return decEndpoint.name || decEndpoint.source;
  27. });
  28. }
  29. return Q.all([
  30. clearPackages(decEndpoints, config, logger),
  31. clearLinks(names, config, logger),
  32. !names ? clearCompletion(config, logger) : null
  33. ])
  34. .spread(function (entries) {
  35. return entries;
  36. });
  37. }
  38. function clearPackages(decEndpoints, config, logger) {
  39. var repository = new PackageRepository(config, logger);
  40. return repository.list()
  41. .then(function (entries) {
  42. var promises;
  43. // Filter entries according to the specified packages
  44. if (decEndpoints) {
  45. entries = entries.filter(function (entry) {
  46. return !!mout.array.find(decEndpoints, function (decEndpoint) {
  47. var entryPkgMeta = entry.pkgMeta;
  48. // Check if name or source match the entry
  49. if (decEndpoint.name !== entryPkgMeta.name &&
  50. decEndpoint.source !== entryPkgMeta.name &&
  51. decEndpoint.source !== entryPkgMeta._source
  52. ) {
  53. return false;
  54. }
  55. // If target is a wildcard, simply return true
  56. if (decEndpoint.target === '*') {
  57. return true;
  58. }
  59. // If it's a semver target, compare using semver spec
  60. if (semver.validRange(decEndpoint.target)) {
  61. return semver.satisfies(entryPkgMeta.version, decEndpoint.target);
  62. }
  63. // Otherwise, compare against target/release
  64. return decEndpoint.target === entryPkgMeta._target ||
  65. decEndpoint.target === entryPkgMeta._release;
  66. });
  67. });
  68. }
  69. promises = entries.map(function (entry) {
  70. return repository.eliminate(entry.pkgMeta)
  71. .then(function () {
  72. logger.info('deleted', 'Cached package ' + entry.pkgMeta.name + ': ' + entry.canonicalDir, {
  73. file: entry.canonicalDir
  74. });
  75. });
  76. });
  77. return Q.all(promises)
  78. .then(function () {
  79. if (!decEndpoints) {
  80. // Ensure that everything is cleaned,
  81. // even invalid packages in the cache
  82. return repository.clear();
  83. }
  84. })
  85. .then(function () {
  86. return entries;
  87. });
  88. });
  89. }
  90. function clearLinks(names, config, logger) {
  91. var promise;
  92. var dir = config.storage.links;
  93. // If no names are passed, grab all links
  94. if (!names) {
  95. promise = Q.nfcall(fs.readdir, dir)
  96. .fail(function (err) {
  97. if (err.code === 'ENOENT') {
  98. return [];
  99. }
  100. throw err;
  101. });
  102. // Otherwise use passed ones
  103. } else {
  104. promise = Q.resolve(names);
  105. }
  106. return promise
  107. .then(function (names) {
  108. var promises;
  109. var linksToRemove = [];
  110. // Decide which links to delete
  111. promises = names.map(function (name) {
  112. var link = path.join(config.storage.links, name);
  113. return Q.nfcall(fs.readlink, link)
  114. .then(function (linkTarget) {
  115. // Link exists, check if it points to a folder
  116. // that still exists
  117. return Q.nfcall(fs.stat, linkTarget)
  118. .then(function (stat) {
  119. // Target is not a folder..
  120. if (!stat.isDirectory()) {
  121. linksToRemove.push(link);
  122. }
  123. })
  124. // Error occurred reading the link
  125. .fail(function () {
  126. linksToRemove.push(link);
  127. });
  128. // Ignore if link does not exist
  129. }, function (err) {
  130. if (err.code !== 'ENOENT') {
  131. linksToRemove.push(link);
  132. }
  133. });
  134. });
  135. return Q.all(promises)
  136. .then(function () {
  137. var promises;
  138. // Remove each link that was declared as invalid
  139. promises = linksToRemove.map(function (link) {
  140. return Q.nfcall(rimraf, link)
  141. .then(function () {
  142. logger.info('deleted', 'Invalid link: ' + link, {
  143. file: link
  144. });
  145. });
  146. });
  147. return Q.all(promises);
  148. });
  149. });
  150. }
  151. function clearCompletion(config, logger) {
  152. var dir = config.storage.completion;
  153. return Q.nfcall(fs.stat, dir)
  154. .then(function () {
  155. return Q.nfcall(rimraf, dir)
  156. .then(function () {
  157. logger.info('deleted', 'Completion cache', {
  158. file: dir
  159. });
  160. });
  161. }, function (error) {
  162. if (error.code !== 'ENOENT') {
  163. throw error;
  164. }
  165. });
  166. }
  167. // -------------------
  168. clean.line = function (logger, argv) {
  169. var options = cli.readOptions(argv);
  170. var endpoints = options.argv.remain.slice(2);
  171. return clean(logger, endpoints, options);
  172. };
  173. clean.completion = function () {
  174. // TODO:
  175. };
  176. module.exports = clean;