StandardRenderer.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495
  1. var chalk = require('chalk');
  2. var path = require('path');
  3. var mout = require('mout');
  4. var archy = require('archy');
  5. var Q = require('q');
  6. var stringifyObject = require('stringify-object');
  7. var os = require('os');
  8. var pkg = require(path.join(__dirname, '../..', 'package.json'));
  9. var template = require('../util/template');
  10. function StandardRenderer(command, config) {
  11. this._sizes = {
  12. id: 13, // Id max chars
  13. label: 20, // Label max chars
  14. sumup: 5 // Amount to sum when the label exceeds
  15. };
  16. this._colors = {
  17. warn: chalk.yellow,
  18. error: chalk.red,
  19. conflict: chalk.magenta,
  20. debug: chalk.gray,
  21. default: chalk.cyan
  22. };
  23. this._command = command;
  24. this._config = config;
  25. if (this.constructor._wideCommands.indexOf(command) === -1) {
  26. this._compact = true;
  27. } else {
  28. this._compact = process.stdout.columns < 120;
  29. }
  30. var exitOnPipeError = function (err) {
  31. if (err.code === 'EPIPE') {
  32. process.exit(0);
  33. }
  34. };
  35. // It happens when piping command to "head" util
  36. process.stdout.on('error', exitOnPipeError);
  37. process.stderr.on('error', exitOnPipeError);
  38. }
  39. StandardRenderer.prototype.end = function (data) {
  40. var method = '_' + mout.string.camelCase(this._command);
  41. if (this[method]) {
  42. this[method](data);
  43. }
  44. };
  45. StandardRenderer.prototype.error = function (err) {
  46. var str;
  47. var stack;
  48. this._guessOrigin(err);
  49. err.id = err.code || 'error';
  50. err.level = 'error';
  51. str = this._prefix(err) + ' ' + err.message.replace(/\r?\n/g, ' ').trim() + '\n';
  52. this._write(process.stderr, 'bower ' + str);
  53. // Check if additional details were provided
  54. if (err.details) {
  55. str = chalk.yellow('\nAdditional error details:\n') + err.details.trim() + '\n';
  56. this._write(process.stderr, str);
  57. }
  58. // Print trace if verbose, the error has no code
  59. // or if the error is a node error
  60. if (this._config.verbose || !err.code || err.errno) {
  61. /*jshint camelcase:false*/
  62. stack = err.fstream_stack || err.stack || 'N/A';
  63. str = chalk.yellow('\nStack trace:\n');
  64. str += (Array.isArray(stack) ? stack.join('\n') : stack) + '\n';
  65. str += chalk.yellow('\nConsole trace:\n');
  66. /*jshint camelcase:true*/
  67. this._write(process.stderr, str);
  68. console.trace();
  69. // Print bower version, node version and system info.
  70. this._write(process.stderr, chalk.yellow('\nSystem info:\n'));
  71. this._write(process.stderr, 'Bower version: ' + pkg.version + '\n');
  72. this._write(process.stderr, 'Node version: ' + process.versions.node + '\n');
  73. this._write(process.stderr, 'OS: ' + os.type() + ' ' + os.release() + ' ' + os.arch() + '\n');
  74. }
  75. };
  76. StandardRenderer.prototype.log = function (log) {
  77. var method = '_' + mout.string.camelCase(log.id) + 'Log';
  78. this._guessOrigin(log);
  79. // Call render method for this log entry or the generic one
  80. if (this[method]) {
  81. this[method](log);
  82. } else {
  83. this._genericLog(log);
  84. }
  85. };
  86. StandardRenderer.prototype.prompt = function (prompts) {
  87. var deferred;
  88. // Strip colors from the prompt if color is disabled
  89. if (!this._config.color) {
  90. prompts.forEach(function (prompt) {
  91. prompt.message = chalk.stripColor(prompt.message);
  92. });
  93. }
  94. // Prompt
  95. deferred = Q.defer();
  96. var inquirer = require('inquirer');
  97. inquirer.prompt(prompts, deferred.resolve);
  98. return deferred.promise;
  99. };
  100. // -------------------------
  101. StandardRenderer.prototype._help = function (data) {
  102. var str;
  103. var that = this;
  104. var specific;
  105. if (!data.command) {
  106. str = template.render('std/help.std', data);
  107. that._write(process.stdout, str);
  108. } else {
  109. // Check if a specific template exists for the command
  110. specific = 'std/help-' + data.command.replace(/\s+/g, '/') + '.std';
  111. if (template.exists(specific)) {
  112. str = template.render(specific, data);
  113. } else {
  114. str = template.render('std/help-generic.std', data);
  115. }
  116. that._write(process.stdout, str);
  117. }
  118. };
  119. StandardRenderer.prototype._install = function (packages) {
  120. var str = '';
  121. mout.object.forOwn(packages, function (pkg) {
  122. var cliTree;
  123. // List only 1 level deep dependencies
  124. mout.object.forOwn(pkg.dependencies, function (dependency) {
  125. dependency.dependencies = {};
  126. });
  127. // Make canonical dir relative
  128. pkg.canonicalDir = path.relative(this._config.cwd, pkg.canonicalDir);
  129. // Signal as root
  130. pkg.root = true;
  131. cliTree = this._tree2archy(pkg);
  132. str += '\n' + archy(cliTree);
  133. }, this);
  134. if (str) {
  135. this._write(process.stdout, str);
  136. }
  137. };
  138. StandardRenderer.prototype._update = function (packages) {
  139. this._install(packages);
  140. };
  141. StandardRenderer.prototype._list = function (tree) {
  142. var cliTree;
  143. if (tree.pkgMeta) {
  144. tree.root = true;
  145. cliTree = archy(this._tree2archy(tree));
  146. } else {
  147. cliTree = stringifyObject(tree, { indent: ' ' }).replace(/[{}]/g, '') + '\n';
  148. }
  149. this._write(process.stdout, cliTree);
  150. };
  151. StandardRenderer.prototype._search = function (results) {
  152. var str = template.render('std/search-results.std', results);
  153. this._write(process.stdout, str);
  154. };
  155. StandardRenderer.prototype._info = function (data) {
  156. var str = '';
  157. var pkgMeta = data;
  158. var includeVersions = false;
  159. // If the response is the whole package info, the package meta
  160. // is under the "latest" property
  161. if (typeof data === 'object' && data.versions) {
  162. pkgMeta = data.latest;
  163. includeVersions = true;
  164. }
  165. // Render package meta
  166. if (pkgMeta != null) {
  167. str += '\n' + this._highlightJson(pkgMeta) + '\n';
  168. }
  169. // Render the versions at the end
  170. if (includeVersions) {
  171. str += '\n' + template.render('std/info.std', data);
  172. }
  173. this._write(process.stdout, str);
  174. };
  175. StandardRenderer.prototype._lookup = function (data) {
  176. var str = template.render('std/lookup.std', data);
  177. this._write(process.stdout, str);
  178. };
  179. StandardRenderer.prototype._link = function (data) {
  180. this._sizes.id = 4;
  181. this.log({
  182. id: 'link',
  183. level: 'info',
  184. message: data.dst + ' > ' + data.src
  185. });
  186. // Print also a tree of the installed packages
  187. if (data.installed) {
  188. this._install(data.installed);
  189. }
  190. };
  191. StandardRenderer.prototype._register = function (data) {
  192. var str;
  193. // If no data passed, it means the user aborted
  194. if (!data) {
  195. return;
  196. }
  197. str = '\n' + template.render('std/register.std', data);
  198. this._write(process.stdout, str);
  199. };
  200. StandardRenderer.prototype._cacheList = function (entries) {
  201. entries.forEach(function (entry) {
  202. var pkgMeta = entry.pkgMeta;
  203. var version = pkgMeta.version || pkgMeta._target;
  204. this._write(process.stdout, pkgMeta.name + '=' + pkgMeta._source + '#' + version + '\n');
  205. }, this);
  206. };
  207. // -------------------------
  208. StandardRenderer.prototype._genericLog = function (log) {
  209. var stream;
  210. var str;
  211. if (log.level === 'warn') {
  212. stream = process.stderr;
  213. } else {
  214. stream = process.stdout;
  215. }
  216. str = this._prefix(log) + ' ' + log.message + '\n';
  217. this._write(stream, 'bower ' + str);
  218. };
  219. StandardRenderer.prototype._checkoutLog = function (log) {
  220. if (this._compact) {
  221. log.message = log.origin.split('#')[0] + '#' + log.message;
  222. }
  223. this._genericLog(log);
  224. };
  225. StandardRenderer.prototype._progressLog = function (log) {
  226. if (this._compact) {
  227. log.message = log.origin + ' ' + log.message;
  228. }
  229. this._genericLog(log);
  230. };
  231. StandardRenderer.prototype._extractLog = function (log) {
  232. if (this._compact) {
  233. log.message = log.origin + ' ' + log.message;
  234. }
  235. this._genericLog(log);
  236. };
  237. StandardRenderer.prototype._incompatibleLog = function (log) {
  238. var str;
  239. var templatePath;
  240. // Generate dependants string for each pick
  241. log.data.picks.forEach(function (pick) {
  242. pick.dependants = pick.dependants.map(function (dependant) {
  243. var release = dependant.pkgMeta._release;
  244. return dependant.endpoint.name + (release ? '#' + release : '');
  245. }).join(', ');
  246. });
  247. templatePath = log.data.suitable ? 'std/conflict-resolved.std' : 'std/conflict.std';
  248. str = template.render(templatePath, log.data);
  249. this._write(process.stdout, '\n');
  250. this._write(process.stdout, str);
  251. this._write(process.stdout, '\n');
  252. };
  253. StandardRenderer.prototype._solvedLog = function (log) {
  254. this._incompatibleLog(log);
  255. };
  256. StandardRenderer.prototype._jsonLog = function (log) {
  257. this._write(process.stdout, '\n' + this._highlightJson(log.data.json) + '\n\n');
  258. };
  259. StandardRenderer.prototype._cachedEntryLog = function (log) {
  260. if (this._compact) {
  261. log.message = log.origin;
  262. }
  263. this._genericLog(log);
  264. };
  265. // -------------------------
  266. StandardRenderer.prototype._guessOrigin = function (log) {
  267. var data = log.data;
  268. if (!data) {
  269. return;
  270. }
  271. if (data.endpoint) {
  272. log.origin = data.endpoint.name || (data.registry && data.endpoint.source);
  273. // Resort to using the resolver name for unnamed endpoints
  274. if (!log.origin && data.resolver) {
  275. log.origin = data.resolver.name;
  276. }
  277. if (log.origin && data.endpoint.target) {
  278. log.origin += '#' + data.endpoint.target;
  279. }
  280. } else if (data.name) {
  281. log.origin = data.name;
  282. if (data.version) {
  283. log.origin += '#' + data.version;
  284. }
  285. }
  286. };
  287. StandardRenderer.prototype._prefix = function (log) {
  288. var label;
  289. var length;
  290. var nrSpaces;
  291. var id = this.constructor._idMappings[log.id] || log.id;
  292. var idColor = this._colors[log.level] || this._colors.default;
  293. if (this._compact) {
  294. // If there's not enough space for the id, adjust it
  295. // for subsequent logs
  296. if (id.length > this._sizes.id) {
  297. this._sizes.id = id.length += this._sizes.sumup;
  298. }
  299. return idColor(mout.string.rpad(id, this._sizes.id));
  300. }
  301. // Construct the label
  302. label = log.origin || '';
  303. length = id.length + label.length + 1;
  304. nrSpaces = this._sizes.id + this._sizes.label - length;
  305. // Ensure at least one space between the label and the id
  306. if (nrSpaces < 1) {
  307. // Also adjust the label size for subsequent logs
  308. this._sizes.label = label.length + this._sizes.sumup;
  309. nrSpaces = this._sizes.id + this._sizes.label - length;
  310. }
  311. return chalk.green(label) + mout.string.repeat(' ', nrSpaces) + idColor(id);
  312. };
  313. StandardRenderer.prototype._write = function (stream, str) {
  314. if (!this._config.color) {
  315. str = chalk.stripColor(str);
  316. }
  317. stream.write(str);
  318. };
  319. StandardRenderer.prototype._highlightJson = function (json) {
  320. var cardinal = require('cardinal');
  321. return cardinal.highlight(stringifyObject(json, { indent: ' ' }), {
  322. theme: {
  323. String: {
  324. _default: function (str) {
  325. return chalk.cyan(str);
  326. }
  327. },
  328. Identifier: {
  329. _default: function (str) {
  330. return chalk.green(str);
  331. }
  332. }
  333. },
  334. json: true
  335. });
  336. };
  337. StandardRenderer.prototype._tree2archy = function (node) {
  338. var dependencies = mout.object.values(node.dependencies);
  339. var version = !node.missing ? node.pkgMeta._release || node.pkgMeta.version : null;
  340. var label = node.endpoint.name + (version ? '#' + version : '');
  341. var update;
  342. if (node.root) {
  343. label += ' ' + node.canonicalDir;
  344. }
  345. // State labels
  346. if (node.missing) {
  347. label += chalk.red(' not installed');
  348. return label;
  349. }
  350. if (node.different) {
  351. label += chalk.red(' different');
  352. }
  353. if (node.linked) {
  354. label += chalk.magenta(' linked');
  355. }
  356. if (node.incompatible) {
  357. label += chalk.yellow(' incompatible') + ' with ' + node.endpoint.target;
  358. } else if (node.extraneous) {
  359. label += chalk.green(' extraneous');
  360. }
  361. // New versions
  362. if (node.update) {
  363. update = '';
  364. if (node.update.target && node.pkgMeta.version !== node.update.target) {
  365. update += node.update.target + ' available';
  366. }
  367. if (node.update.latest !== node.update.target) {
  368. update += (update ? ', ' : '');
  369. update += 'latest is ' + node.update.latest;
  370. }
  371. if (update) {
  372. label += ' (' + chalk.cyan(update) + ')';
  373. }
  374. }
  375. if (!dependencies.length) {
  376. return label;
  377. }
  378. return {
  379. label: label,
  380. nodes: mout.object.values(dependencies).map(this._tree2archy, this)
  381. };
  382. };
  383. StandardRenderer._wideCommands = [
  384. 'install',
  385. 'update',
  386. 'link',
  387. 'info',
  388. 'home',
  389. 'register'
  390. ];
  391. StandardRenderer._idMappings = {
  392. 'mutual': 'conflict',
  393. 'cached-entry': 'cached'
  394. };
  395. module.exports = StandardRenderer;