From 05f5fd5ad2a46694cd1e06ba8ffb3fadf46b7903 Mon Sep 17 00:00:00 2001 From: LitoMore Date: Tue, 9 Jan 2018 23:22:52 +0800 Subject: [PATCH 01/10] Suggest yarn when intalled with yarn --- index.js | 35 +++++++++++++++++++++++++++++++++-- package.json | 1 + 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index c12eb3c..fa55820 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,6 @@ 'use strict'; const spawn = require('child_process').spawn; +const fs = require('fs'); const path = require('path'); const format = require('util').format; const importLazy = require('import-lazy')(require); @@ -13,6 +14,7 @@ const isInstalledGlobally = importLazy('is-installed-globally'); const boxen = importLazy('boxen'); const xdgBasedir = importLazy('xdg-basedir'); const isCi = importLazy('is-ci'); +const which = importLazy('which'); const ONE_DAY = 1000 * 60 * 60 * 24; class UpdateNotifier { @@ -25,7 +27,8 @@ class UpdateNotifier { // TODO: Remove deprecated options at some point far into the future options.pkg = { name: options.pkg.name || options.packageName, - version: options.pkg.version || options.packageVersion + version: options.pkg.version || options.packageVersion, + bin: options.pkg.bin }; if (!options.pkg.name || !options.pkg.version) { @@ -34,6 +37,7 @@ class UpdateNotifier { this.packageName = options.pkg.name; this.packageVersion = options.pkg.version; + this.packageBin = options.pkg.bin; this.updateCheckInterval = typeof options.updateCheckInterval === 'number' ? options.updateCheckInterval : ONE_DAY; this.hasCallback = typeof options.callback === 'function'; this.callback = options.callback || (() => {}); @@ -107,15 +111,42 @@ class UpdateNotifier { }; }); } + isYarn() { + const bin = this.packageBin; + let cliName = null; + + switch (typeof bin) { + case 'string': + cliName = this.packageName; + break; + case 'object': + cliName = Object.keys(bin)[0]; + break; + case 'undefined': + default: + return false; + } + + try { + const realpath = fs.realpathSync(which().sync(cliName, {nothrow: true})); + if (realpath.match(/yarn\/global/)) { + return true; + } + return false; + } catch (err) { + return false; + } + } notify(opts) { if (!process.stdout.isTTY || isNpm() || !this.update) { return this; } opts = Object.assign({isGlobal: isInstalledGlobally()}, opts); + const installCommand = this.isYarn() ? 'yarn global upgrade ' + this.packageName : 'npm i ' + (opts.isGlobal ? '-g ' : '') + this.packageName; opts.message = opts.message || 'Update available ' + chalk().dim(this.update.current) + chalk().reset(' → ') + - chalk().green(this.update.latest) + ' \nRun ' + chalk().cyan('npm i ' + (opts.isGlobal ? '-g ' : '') + this.packageName) + ' to update'; + chalk().green(this.update.latest) + ' \nRun ' + chalk().cyan(installCommand) + ' to update'; opts.boxenOpts = opts.boxenOpts || { padding: 1, diff --git a/package.json b/package.json index 07d07aa..f8b33ef 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "is-npm": "^1.0.0", "latest-version": "^3.0.0", "semver-diff": "^2.0.0", + "which": "^1.3.0", "xdg-basedir": "^3.0.0" }, "devDependencies": { From 29ce070ef9e1219014b9e0c05f8b406231cfc642 Mon Sep 17 00:00:00 2001 From: LitoMore Date: Thu, 11 Jan 2018 22:41:43 +0800 Subject: [PATCH 02/10] Use `process.cwd()` instead --- index.js | 34 +++++++--------------------------- 1 file changed, 7 insertions(+), 27 deletions(-) diff --git a/index.js b/index.js index fa55820..c8839b7 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,5 @@ 'use strict'; const spawn = require('child_process').spawn; -const fs = require('fs'); const path = require('path'); const format = require('util').format; const importLazy = require('import-lazy')(require); @@ -14,7 +13,6 @@ const isInstalledGlobally = importLazy('is-installed-globally'); const boxen = importLazy('boxen'); const xdgBasedir = importLazy('xdg-basedir'); const isCi = importLazy('is-ci'); -const which = importLazy('which'); const ONE_DAY = 1000 * 60 * 60 * 24; class UpdateNotifier { @@ -111,31 +109,13 @@ class UpdateNotifier { }; }); } - isYarn() { - const bin = this.packageBin; - let cliName = null; - - switch (typeof bin) { - case 'string': - cliName = this.packageName; - break; - case 'object': - cliName = Object.keys(bin)[0]; - break; - case 'undefined': - default: - return false; - } - - try { - const realpath = fs.realpathSync(which().sync(cliName, {nothrow: true})); - if (realpath.match(/yarn\/global/)) { - return true; - } - return false; - } catch (err) { - return false; + isYarnGlobal() { + const isWindows = process.platform === 'win32' || process.env.OSTYPE === 'cygwin' || process.env.OSTYPE === 'msys'; + const yarnPath = isWindows ? path.join('Yarn', 'config', 'global') : path.join('.config', 'yarn', 'global'); + if (process.cwd().includes(yarnPath)) { + return true; } + return false; } notify(opts) { if (!process.stdout.isTTY || isNpm() || !this.update) { @@ -143,7 +123,7 @@ class UpdateNotifier { } opts = Object.assign({isGlobal: isInstalledGlobally()}, opts); - const installCommand = this.isYarn() ? 'yarn global upgrade ' + this.packageName : 'npm i ' + (opts.isGlobal ? '-g ' : '') + this.packageName; + const installCommand = this.isYarnGlobal() ? 'yarn global upgrade ' + this.packageName : 'npm i ' + (opts.isGlobal ? '-g ' : '') + this.packageName; opts.message = opts.message || 'Update available ' + chalk().dim(this.update.current) + chalk().reset(' → ') + chalk().green(this.update.latest) + ' \nRun ' + chalk().cyan(installCommand) + ' to update'; From 9d7f479d4f2427800e0fa12979b8f30c2e9fd57e Mon Sep 17 00:00:00 2001 From: LitoMore Date: Thu, 11 Jan 2018 22:46:19 +0800 Subject: [PATCH 03/10] Remove unused code --- index.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/index.js b/index.js index c8839b7..448c16a 100644 --- a/index.js +++ b/index.js @@ -25,8 +25,7 @@ class UpdateNotifier { // TODO: Remove deprecated options at some point far into the future options.pkg = { name: options.pkg.name || options.packageName, - version: options.pkg.version || options.packageVersion, - bin: options.pkg.bin + version: options.pkg.version || options.packageVersion }; if (!options.pkg.name || !options.pkg.version) { @@ -35,7 +34,6 @@ class UpdateNotifier { this.packageName = options.pkg.name; this.packageVersion = options.pkg.version; - this.packageBin = options.pkg.bin; this.updateCheckInterval = typeof options.updateCheckInterval === 'number' ? options.updateCheckInterval : ONE_DAY; this.hasCallback = typeof options.callback === 'function'; this.callback = options.callback || (() => {}); From 6b3eb004d3a809ac82d6a268a534b0b1c8c0afbc Mon Sep 17 00:00:00 2001 From: LitoMore Date: Thu, 11 Jan 2018 22:47:40 +0800 Subject: [PATCH 04/10] Remove unused dependency --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index f8b33ef..07d07aa 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,6 @@ "is-npm": "^1.0.0", "latest-version": "^3.0.0", "semver-diff": "^2.0.0", - "which": "^1.3.0", "xdg-basedir": "^3.0.0" }, "devDependencies": { From 77238f0cdd85f6fbc5a5a5a5149cc4f9ff189a90 Mon Sep 17 00:00:00 2001 From: LitoMore Date: Tue, 23 Jan 2018 19:12:22 +0800 Subject: [PATCH 05/10] Use is-yarn-global package --- index.js | 13 +++---------- package.json | 1 + 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/index.js b/index.js index 448c16a..c2d9888 100644 --- a/index.js +++ b/index.js @@ -10,6 +10,7 @@ const semverDiff = importLazy('semver-diff'); const latestVersion = importLazy('latest-version'); const isNpm = importLazy('is-npm'); const isInstalledGlobally = importLazy('is-installed-globally'); +const isYarnGlobal = importLazy('isYarnGlobal'); const boxen = importLazy('boxen'); const xdgBasedir = importLazy('xdg-basedir'); const isCi = importLazy('is-ci'); @@ -107,21 +108,13 @@ class UpdateNotifier { }; }); } - isYarnGlobal() { - const isWindows = process.platform === 'win32' || process.env.OSTYPE === 'cygwin' || process.env.OSTYPE === 'msys'; - const yarnPath = isWindows ? path.join('Yarn', 'config', 'global') : path.join('.config', 'yarn', 'global'); - if (process.cwd().includes(yarnPath)) { - return true; - } - return false; - } notify(opts) { if (!process.stdout.isTTY || isNpm() || !this.update) { return this; } - opts = Object.assign({isGlobal: isInstalledGlobally()}, opts); - const installCommand = this.isYarnGlobal() ? 'yarn global upgrade ' + this.packageName : 'npm i ' + (opts.isGlobal ? '-g ' : '') + this.packageName; + opts = Object.assign({isGlobal: isInstalledGlobally(), isYarnGlobal: isYarnGlobal()()}, opts); + const installCommand = opts.isYarnGlobal ? `yarn global add ${this.packageName}` : `npm i ${opts.isGlobal ? '-g ' : ''}${this.packageName}`; opts.message = opts.message || 'Update available ' + chalk().dim(this.update.current) + chalk().reset(' → ') + chalk().green(this.update.latest) + ' \nRun ' + chalk().cyan(installCommand) + ' to update'; diff --git a/package.json b/package.json index 07d07aa..b62a22d 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "is-ci": "^1.0.10", "is-installed-globally": "^0.1.0", "is-npm": "^1.0.0", + "is-yarn-global": "^0.2.0", "latest-version": "^3.0.0", "semver-diff": "^2.0.0", "xdg-basedir": "^3.0.0" From bc85ad19ddf2133f94780425b6a913a046580636 Mon Sep 17 00:00:00 2001 From: LitoMore Date: Tue, 23 Jan 2018 19:26:18 +0800 Subject: [PATCH 06/10] Fix package name --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index c2d9888..2c5b0c0 100644 --- a/index.js +++ b/index.js @@ -10,7 +10,7 @@ const semverDiff = importLazy('semver-diff'); const latestVersion = importLazy('latest-version'); const isNpm = importLazy('is-npm'); const isInstalledGlobally = importLazy('is-installed-globally'); -const isYarnGlobal = importLazy('isYarnGlobal'); +const isYarnGlobal = importLazy('is-yarn-global'); const boxen = importLazy('boxen'); const xdgBasedir = importLazy('xdg-basedir'); const isCi = importLazy('is-ci'); From a1b00245faecc99668e1f803bd9ca050d727bdf0 Mon Sep 17 00:00:00 2001 From: LitoMore Date: Thu, 15 Feb 2018 17:03:47 +0800 Subject: [PATCH 07/10] Update is-yarn-global to 0.3.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b62a22d..4078bcb 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "is-ci": "^1.0.10", "is-installed-globally": "^0.1.0", "is-npm": "^1.0.0", - "is-yarn-global": "^0.2.0", + "is-yarn-global": "^0.3.0", "latest-version": "^3.0.0", "semver-diff": "^2.0.0", "xdg-basedir": "^3.0.0" From b52ac347112cfa31b2b0555c81ad89fa2852958f Mon Sep 17 00:00:00 2001 From: LitoMore Date: Fri, 29 Mar 2019 11:18:00 +0800 Subject: [PATCH 08/10] Merge master and fix conflict --- .gitattributes | 3 +- .travis.yml | 4 +- check.js | 11 ++-- index.js | 70 +++++++++++++++----------- license | 9 ++++ package.json | 108 ++++++++++++++++++++-------------------- readme.md | 38 ++++++++++++-- test/fs-error.js | 5 +- test/notify.js | 45 ++++++++++++----- test/update-notifier.js | 5 +- 10 files changed, 185 insertions(+), 113 deletions(-) create mode 100644 license diff --git a/.gitattributes b/.gitattributes index 391f0a4..6313b56 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1 @@ -* text=auto -*.js text eol=lf +* text=auto eol=lf diff --git a/.travis.yml b/.travis.yml index 97519af..f3fa8cd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,4 @@ language: node_js node_js: - - '6' - - '4' + - '10' + - '8' diff --git a/check.js b/check.js index 521f84a..1d0eee8 100644 --- a/check.js +++ b/check.js @@ -6,7 +6,9 @@ const options = JSON.parse(process.argv[2]); updateNotifier = new updateNotifier.UpdateNotifier(options); -updateNotifier.checkNpm().then(update => { +(async () => { + const update = await updateNotifier.checkNpm(); + // Only update the last update check time on success updateNotifier.config.set('lastUpdateCheck', Date.now()); @@ -14,9 +16,10 @@ updateNotifier.checkNpm().then(update => { updateNotifier.config.set('update', update); } - // Call process exit explicitly to terminate the child process - // Otherwise the child process will run forever, according to the Node.js docs + // Call process exit explicitly to terminate the child process, + // otherwise the child process will run forever, according to the Node.js docs process.exit(); -}).catch(() => { +})().catch(error => { + console.error(error); process.exit(1); }); diff --git a/index.js b/index.js index 2c5b0c0..5226c7a 100644 --- a/index.js +++ b/index.js @@ -1,7 +1,7 @@ 'use strict'; -const spawn = require('child_process').spawn; +const {spawn} = require('child_process'); const path = require('path'); -const format = require('util').format; +const {format} = require('util'); const importLazy = require('import-lazy')(require); const configstore = importLazy('configstore'); @@ -14,11 +14,11 @@ const isYarnGlobal = importLazy('is-yarn-global'); const boxen = importLazy('boxen'); const xdgBasedir = importLazy('xdg-basedir'); const isCi = importLazy('is-ci'); + const ONE_DAY = 1000 * 60 * 60 * 24; class UpdateNotifier { - constructor(options) { - options = options || {}; + constructor(options = {}) { this.options = options; options.pkg = options.pkg || {}; @@ -39,8 +39,9 @@ class UpdateNotifier { this.hasCallback = typeof options.callback === 'function'; this.callback = options.callback || (() => {}); this.disabled = 'NO_UPDATE_NOTIFIER' in process.env || - process.argv.indexOf('--no-update-notifier') !== -1 || + process.argv.includes('--no-update-notifier') || isCi(); + this.shouldNotifyInNpmScript = options.shouldNotifyInNpmScript; if (!this.disabled && !this.hasCallback) { try { @@ -51,25 +52,31 @@ class UpdateNotifier { // after the set interval, so not to bother users right away lastUpdateCheck: Date.now() }); - } catch (err) { + } catch (error) { // Expecting error code EACCES or EPERM - const msg = + const message = chalk().yellow(format(' %s update check failed ', options.pkg.name)) + format('\n Try running with %s or get access ', chalk().cyan('sudo')) + '\n to the local update config store via \n' + chalk().cyan(format(' sudo chown -R $USER:$(id -gn $USER) %s ', xdgBasedir().config)); process.on('exit', () => { - console.error('\n' + boxen()(msg, {align: 'center'})); + console.error('\n' + boxen()(message, {align: 'center'})); }); } } } + check() { if (this.hasCallback) { - this.checkNpm() - .then(update => this.callback(null, update)) - .catch(err => this.callback(err)); + (async () => { + try { + this.callback(null, await this.checkNpm()); + } catch (error) { + this.callback(error); + } + })(); + return; } @@ -98,28 +105,35 @@ class UpdateNotifier { stdio: 'ignore' }).unref(); } - checkNpm() { - return latestVersion()(this.packageName).then(latestVersion => { - return { - latest: latestVersion, - current: this.packageVersion, - type: semverDiff()(this.packageVersion, latestVersion) || 'latest', - name: this.packageName - }; - }); + + async checkNpm() { + const latest = await latestVersion()(this.packageName); + + return { + latest, + current: this.packageVersion, + type: semverDiff()(this.packageVersion, latest) || 'latest', + name: this.packageName + }; } - notify(opts) { - if (!process.stdout.isTTY || isNpm() || !this.update) { + + notify(options) { + const suppressForNpm = !this.shouldNotifyInNpmScript && isNpm().isNpm; + if (!process.stdout.isTTY || suppressForNpm || !this.update) { return this; } - opts = Object.assign({isGlobal: isInstalledGlobally(), isYarnGlobal: isYarnGlobal()()}, opts); - const installCommand = opts.isYarnGlobal ? `yarn global add ${this.packageName}` : `npm i ${opts.isGlobal ? '-g ' : ''}${this.packageName}`; + options = { + isGlobal: isInstalledGlobally(), + isYarnGlobal: isYarnGlobal()(), + ...options + }; + const installCommand = options.isYarnGlobal ? `yarn global add ${this.packageName}` : `npm i ${options.isGlobal ? '-g ' : ''}${this.packageName}`; - opts.message = opts.message || 'Update available ' + chalk().dim(this.update.current) + chalk().reset(' → ') + + options.message = options.message || 'Update available ' + chalk().dim(this.update.current) + chalk().reset(' → ') + chalk().green(this.update.latest) + ' \nRun ' + chalk().cyan(installCommand) + ' to update'; - opts.boxenOpts = opts.boxenOpts || { + options.boxenOpts = options.boxenOpts || { padding: 1, margin: 1, align: 'center', @@ -127,9 +141,9 @@ class UpdateNotifier { borderStyle: 'round' }; - const message = '\n' + boxen()(opts.message, opts.boxenOpts); + const message = '\n' + boxen()(options.message, options.boxenOpts); - if (opts.defer === false) { + if (options.defer === false) { console.error(message); } else { process.on('exit', () => { diff --git a/license b/license new file mode 100644 index 0000000..cea5a35 --- /dev/null +++ b/license @@ -0,0 +1,9 @@ +Copyright Google + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/package.json b/package.json index 4078bcb..c660790 100644 --- a/package.json +++ b/package.json @@ -1,56 +1,56 @@ { - "name": "update-notifier", - "version": "2.3.0", - "description": "Update notifications for your CLI app", - "license": "BSD-2-Clause", - "repository": "yeoman/update-notifier", - "author": { - "name": "Sindre Sorhus", - "email": "sindresorhus@gmail.com", - "url": "sindresorhus.com" - }, - "engines": { - "node": ">=4" - }, - "scripts": { - "test": "xo && ava --timeout=20s" - }, - "files": [ - "index.js", - "check.js" - ], - "keywords": [ - "npm", - "update", - "updater", - "notify", - "notifier", - "check", - "checker", - "cli", - "module", - "package", - "version" - ], - "dependencies": { - "boxen": "^1.2.1", - "chalk": "^2.0.1", - "configstore": "^3.0.0", - "import-lazy": "^2.1.0", - "is-ci": "^1.0.10", - "is-installed-globally": "^0.1.0", - "is-npm": "^1.0.0", - "is-yarn-global": "^0.3.0", - "latest-version": "^3.0.0", - "semver-diff": "^2.0.0", - "xdg-basedir": "^3.0.0" - }, - "devDependencies": { - "ava": "*", - "clear-module": "^2.1.0", - "fixture-stdout": "^0.2.1", - "mock-require": "^2.0.2", - "strip-ansi": "^4.0.0", - "xo": "^0.18.2" - } + "name": "update-notifier", + "version": "2.5.0", + "description": "Update notifications for your CLI app", + "license": "BSD-2-Clause", + "repository": "yeoman/update-notifier", + "author": { + "name": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "https://sindresorhus.com" + }, + "engines": { + "node": ">=8" + }, + "scripts": { + "test": "xo && ava --timeout=20s" + }, + "files": [ + "index.js", + "check.js" + ], + "keywords": [ + "npm", + "update", + "updater", + "notify", + "notifier", + "check", + "checker", + "cli", + "module", + "package", + "version" + ], + "dependencies": { + "boxen": "^3.0.0", + "chalk": "^2.0.1", + "configstore": "^4.0.0", + "import-lazy": "^2.1.0", + "is-ci": "^2.0.0", + "is-installed-globally": "^0.1.0", + "is-npm": "^3.0.0", + "is-yarn-global": "^0.3.0", + "latest-version": "^5.0.0", + "semver-diff": "^2.0.0", + "xdg-basedir": "^3.0.0" + }, + "devDependencies": { + "ava": "^1.3.1", + "clear-module": "^3.1.0", + "fixture-stdout": "^0.2.1", + "mock-require": "^3.0.3", + "strip-ansi": "^5.2.0", + "xo": "^0.24.0" + } } diff --git a/readme.md b/readme.md index 6c2de19..62f9b9e 100644 --- a/readme.md +++ b/readme.md @@ -80,11 +80,26 @@ The update check is done in a unref'ed [child process](https://nodejs.org/api/ch The first time the user runs your app, it will check for an update, and even if an update is available, it will wait the specified `updateCheckInterval` before notifying the user. This is done to not be annoying to the user, but might surprise you as an implementer if you're testing whether it works. Check out [`example.js`](example.js) to quickly test out `update-notifier` and see how you can test that it works in your app. +--- + +
+ + Get professional support for this package with a Tidelift subscription + +
+ + Tidelift helps make open source sustainable for maintainers while giving companies
assurances about security, maintenance, and licensing for their dependencies. +
+
+ +--- + + ## API ### notifier = updateNotifier(options) -Checks if there is an available update. Accepts options defined below. Returns an instance with an `.update` property there is an available update, otherwise `undefined`. +Checks if there is an available update. Accepts options defined below. Returns an instance with an `.update` property if there is an available update, otherwise `undefined`. ### options @@ -115,6 +130,13 @@ Type: `Function` Passing a callback here will make it check for an update directly and report right away. Not recommended as you won't get the benefits explained in [`How`](#how). `update` is equal to `notifier.update`. +#### shouldNotifyInNpmScript + +Type: `boolean`
+Default: `false` + +Allows notification to be shown when running as an npm script. + ### notifier.notify([options]) Convenience method to display a notification message. *(See screenshot)* @@ -142,7 +164,7 @@ Message that will be shown when an update is available. ##### isGlobal Type: `boolean`
-Default: `true` +Default: auto-detect Include the `-g` argument in the default message's `npm i` recommendation. You may want to change this if your CLI package can be installed as a dependency of another project, and don't want to recommend a global installation. This option is ignored if you supply your own `message` (see above). @@ -153,6 +175,7 @@ Default: `{padding: 1, margin: 1, align: 'center', borderColor: 'yellow', border Options object that will be passed to [`boxen`](https://github.com/sindresorhus/boxen). + ### User settings Users of your module have the ability to opt-out of the update notifier by changing the `optOut` property to `true` in `~/.config/configstore/update-notifier-[your-module-name].json`. The path is available in `notifier.config.path`. @@ -174,13 +197,18 @@ There are a bunch projects using it: - [npm](https://github.com/npm/npm) - Package manager for JavaScript - [Yeoman](http://yeoman.io) - Modern workflows for modern webapps - [AVA](https://ava.li) - Simple concurrent test runner -- [XO](https://github.com/sindresorhus/xo) - JavaScript happiness style linter +- [XO](https://github.com/xojs/xo) - JavaScript happiness style linter - [Pageres](https://github.com/sindresorhus/pageres) - Capture website screenshots - [Node GH](http://nodegh.io) - GitHub command line tool -[And 1600+ more…](https://www.npmjs.org/browse/depended/update-notifier) +[And 2700+ more…](https://www.npmjs.org/browse/depended/update-notifier) + + +## Security + +To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure. ## License -[BSD license](http://opensource.org/licenses/bsd-license.php) and copyright Google +BSD-2-Clause © Google diff --git a/test/fs-error.js b/test/fs-error.js index 1d0d4cd..d6d920c 100644 --- a/test/fs-error.js +++ b/test/fs-error.js @@ -4,9 +4,8 @@ import test from 'ava'; let updateNotifier; test.before(() => { - ['.', 'configstore', 'xdg-basedir'].forEach(clearModule); - // Set configstore.config to something - // that requires root access + ['..', 'configstore', 'xdg-basedir'].forEach(clearModule); + // Set configstore.config to something that requires root access process.env.XDG_CONFIG_HOME = '/usr'; updateNotifier = require('..'); }); diff --git a/test/notify.js b/test/notify.js index ab24d5d..3f9efde 100644 --- a/test/notify.js +++ b/test/notify.js @@ -3,33 +3,33 @@ import clearModule from 'clear-module'; import FixtureStdout from 'fixture-stdout'; import stripAnsi from 'strip-ansi'; import test from 'ava'; +import mock from 'mock-require'; const stderr = new FixtureStdout({ stream: process.stderr }); -let updateNotifier = require('..'); -test.before(() => { - ['.', 'is-npm'].forEach(clearModule); - ['npm_config_username', 'npm_package_name', 'npm_config_heading'].forEach(name => { - delete process.env[name]; - }); - process.stdout.isTTY = true; - updateNotifier = require('..'); -}); - -function Control() { +function Control(shouldNotifyInNpmScript) { this.packageName = 'update-notifier-tester'; this.update = { current: '0.0.2', latest: '1.0.0' }; + this.shouldNotifyInNpmScript = shouldNotifyInNpmScript; } -util.inherits(Control, updateNotifier.UpdateNotifier); + +const setupTest = isNpmReturnValue => { + ['..', 'is-npm'].forEach(clearModule); + process.stdout.isTTY = true; + mock('is-npm', isNpmReturnValue || false); + const updateNotifier = require('..'); + util.inherits(Control, updateNotifier.UpdateNotifier); +}; let errorLogs = ''; test.beforeEach(() => { + setupTest(); stderr.capture(s => { errorLogs += s; return false; @@ -37,6 +37,7 @@ test.beforeEach(() => { }); test.afterEach(() => { + mock.stopAll(); stderr.release(); errorLogs = ''; }); @@ -62,3 +63,23 @@ test('exclude -g argument when `isGlobal` option is `false`', t => { notifier.notify({defer: false, isGlobal: false}); t.not(stripAnsi(errorLogs).indexOf('Run npm i update-notifier-tester to update'), -1); }); + +test('shouldNotifyInNpmScript should default to false', t => { + const notifier = new Control(); + notifier.notify({defer: false}); + t.not(stripAnsi(errorLogs).indexOf('Update available'), -1); +}); + +test('suppress output when running as npm script', t => { + setupTest(true); + const notifier = new Control(); + notifier.notify({defer: false}); + t.false(stripAnsi(errorLogs).includes('Update available')); +}); + +test('should ouput if running as npm script and shouldNotifyInNpmScript option set', t => { + setupTest(true); + const notifier = new Control(true); + notifier.notify({defer: false}); + t.true(stripAnsi(errorLogs).includes('Update available')); +}); diff --git a/test/update-notifier.js b/test/update-notifier.js index 9e51f82..d70f03b 100644 --- a/test/update-notifier.js +++ b/test/update-notifier.js @@ -7,14 +7,13 @@ mockRequire('is-ci', false); // eslint-disable-next-line import/first import updateNotifier from '..'; -const generateSettings = options => { - options = options || {}; +const generateSettings = (options = {}) => { return { pkg: { name: 'update-notifier-tester', version: '0.0.2' }, - callback: options.callback || null + callback: options.callback }; }; From 5352d8bbef14dd220d165bc91eda53aa7b07f41e Mon Sep 17 00:00:00 2001 From: LitoMore Date: Tue, 9 Apr 2019 01:48:55 +0800 Subject: [PATCH 09/10] Add support for local Yarn command --- index.js | 7 ++++++- package.json | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 239c333..2586b79 100644 --- a/index.js +++ b/index.js @@ -11,6 +11,7 @@ const latestVersion = importLazy('latest-version'); const isNpm = importLazy('is-npm'); const isInstalledGlobally = importLazy('is-installed-globally'); const isYarnGlobal = importLazy('is-yarn-global'); +const hasYarn = importLazy('has-yarn'); const boxen = importLazy('boxen'); const xdgBasedir = importLazy('xdg-basedir'); const isCi = importLazy('is-ci'); @@ -130,7 +131,11 @@ class UpdateNotifier { isYarnGlobal: isYarnGlobal()(), ...options }; - const installCommand = options.isYarnGlobal ? `yarn global add ${this.packageName}` : `npm i ${options.isGlobal ? '-g ' : ''}${this.packageName}`; + const installCommand = options.isYarnGlobal ? + `yarn global add ${this.packageName}` : + hasYarn()() ? + `yarn add ${this.packageName}` : + `npm i ${options.isGlobal ? '-g ' : ''}${this.packageName}`; options.message = options.message || 'Update available ' + chalk().dim(this.update.current) + chalk().reset(' → ') + chalk().green(this.update.latest) + ' \nRun ' + chalk().cyan(installCommand) + ' to update'; diff --git a/package.json b/package.json index c660790..d6e754b 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "boxen": "^3.0.0", "chalk": "^2.0.1", "configstore": "^4.0.0", + "has-yarn": "^2.1.0", "import-lazy": "^2.1.0", "is-ci": "^2.0.0", "is-installed-globally": "^0.1.0", From a85b59fa697d6c2ee7f4676e5fb717ed38f623a7 Mon Sep 17 00:00:00 2001 From: LitoMore Date: Fri, 10 May 2019 16:10:52 +0800 Subject: [PATCH 10/10] Cleanup code --- index.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/index.js b/index.js index 2586b79..50425ac 100644 --- a/index.js +++ b/index.js @@ -131,11 +131,16 @@ class UpdateNotifier { isYarnGlobal: isYarnGlobal()(), ...options }; - const installCommand = options.isYarnGlobal ? - `yarn global add ${this.packageName}` : - hasYarn()() ? - `yarn add ${this.packageName}` : - `npm i ${options.isGlobal ? '-g ' : ''}${this.packageName}`; + + let installCommand; + + if (options.isYarnGlobal) { + installCommand = `yarn global add ${this.packageName}`; + } else if (hasYarn()()) { + installCommand = `yarn add ${this.packageName}`; + } else { + installCommand = `npm i ${options.isGlobal ? '-g ' : ''}${this.packageName}`; + } options.message = options.message || 'Update available ' + chalk().dim(this.update.current) + chalk().reset(' → ') + chalk().green(this.update.latest) + ' \nRun ' + chalk().cyan(installCommand) + ' to update';