index.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. 'use strict';
  2. var spawn = require('child_process').spawn;
  3. var path = require('path');
  4. var format = require('util').format;
  5. var lazyRequire = require('lazy-req')(require);
  6. var configstore = lazyRequire('configstore');
  7. var chalk = lazyRequire('chalk');
  8. var semverDiff = lazyRequire('semver-diff');
  9. var latestVersion = lazyRequire('latest-version');
  10. var isNpm = lazyRequire('is-npm');
  11. var boxen = lazyRequire('boxen');
  12. var xdgBasedir = lazyRequire('xdg-basedir');
  13. var ONE_DAY = 1000 * 60 * 60 * 24;
  14. function UpdateNotifier(options) {
  15. this.options = options = options || {};
  16. options.pkg = options.pkg || {};
  17. // reduce pkg to the essential keys. with fallback to deprecated options
  18. // TODO: remove deprecated options at some point far into the future
  19. options.pkg = {
  20. name: options.pkg.name || options.packageName,
  21. version: options.pkg.version || options.packageVersion
  22. };
  23. if (!options.pkg.name || !options.pkg.version) {
  24. throw new Error('pkg.name and pkg.version required');
  25. }
  26. this.packageName = options.pkg.name;
  27. this.packageVersion = options.pkg.version;
  28. this.updateCheckInterval = typeof options.updateCheckInterval === 'number' ? options.updateCheckInterval : ONE_DAY;
  29. this.hasCallback = typeof options.callback === 'function';
  30. this.callback = options.callback || function () {};
  31. if (!this.hasCallback) {
  32. try {
  33. var ConfigStore = configstore();
  34. this.config = new ConfigStore('update-notifier-' + this.packageName, {
  35. optOut: false,
  36. // init with the current time so the first check is only
  37. // after the set interval, so not to bother users right away
  38. lastUpdateCheck: Date.now()
  39. });
  40. } catch (err) {
  41. // expecting error code EACCES or EPERM
  42. var msg =
  43. chalk().yellow(format(' %s update check failed ', options.pkg.name)) +
  44. format('\n Try running with %s or get access ', chalk().cyan('sudo')) +
  45. '\n to the local update config store via \n' +
  46. chalk().cyan(format(' sudo chown -R $USER:$(id -gn $USER) %s ', xdgBasedir().config));
  47. process.on('exit', function () {
  48. console.error('\n' + boxen()(msg, {align: 'center'}));
  49. });
  50. }
  51. }
  52. }
  53. UpdateNotifier.prototype.check = function () {
  54. if (this.hasCallback) {
  55. this.checkNpm().then(this.callback.bind(this, null)).catch(this.callback);
  56. return;
  57. }
  58. if (
  59. !this.config ||
  60. this.config.get('optOut') ||
  61. 'NO_UPDATE_NOTIFIER' in process.env ||
  62. process.argv.indexOf('--no-update-notifier') !== -1
  63. ) {
  64. return;
  65. }
  66. this.update = this.config.get('update');
  67. if (this.update) {
  68. this.config.del('update');
  69. }
  70. // Only check for updates on a set interval
  71. if (Date.now() - this.config.get('lastUpdateCheck') < this.updateCheckInterval) {
  72. return;
  73. }
  74. // Spawn a detached process, passing the options as an environment property
  75. spawn(process.execPath, [path.join(__dirname, 'check.js'), JSON.stringify(this.options)], {
  76. detached: true,
  77. stdio: 'ignore'
  78. }).unref();
  79. };
  80. UpdateNotifier.prototype.checkNpm = function () {
  81. return latestVersion()(this.packageName).then(function (latestVersion) {
  82. return {
  83. latest: latestVersion,
  84. current: this.packageVersion,
  85. type: semverDiff()(this.packageVersion, latestVersion) || 'latest',
  86. name: this.packageName
  87. };
  88. }.bind(this));
  89. };
  90. UpdateNotifier.prototype.notify = function (opts) {
  91. if (!process.stdout.isTTY || isNpm() || !this.update) {
  92. return this;
  93. }
  94. opts = opts || {};
  95. opts.message = opts.message || 'Update available ' + chalk().dim(this.update.current) + chalk().reset(' → ') +
  96. chalk().green(this.update.latest) + ' \nRun ' + chalk().cyan('npm i -g ' + this.packageName) + ' to update';
  97. opts.boxenOpts = opts.boxenOpts || {
  98. padding: 1,
  99. margin: 1,
  100. align: 'center',
  101. borderColor: 'yellow',
  102. borderStyle: 'round'
  103. };
  104. var message = '\n' + boxen()(opts.message, opts.boxenOpts);
  105. if (opts.defer === false) {
  106. console.error(message);
  107. } else {
  108. process.on('exit', function () {
  109. console.error(message);
  110. });
  111. process.on('SIGINT', function () {
  112. console.error('');
  113. process.exit();
  114. });
  115. }
  116. return this;
  117. };
  118. module.exports = function (options) {
  119. var updateNotifier = new UpdateNotifier(options);
  120. updateNotifier.check();
  121. return updateNotifier;
  122. };
  123. module.exports.UpdateNotifier = UpdateNotifier;