-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
new npm-specific update-notifier implementation
This drops our usage of the update-notifier module, in favor of checking ourselves, using the modules and UX patterns that npm already has in place. - While on a prerelease version, updates are checked for every day, instead of every week, and always checks for a new beta in the current release family. Ie, ^7.0.0-beta.2 instead of latest. - Latest version is suggested if newer than current. - If current version is newer than latest, then we check again for an update in the current version family. Ie, ^7.0.0 instead of latest, if current is 7.0.0 and latest is 6.x. - Output is printed using log.notice, at the end of all other log output, so that it's both less visually disruptive, and less likely to be missed among other warnings and notices. This has the side effect of requiring that we set npm.flatOptions as soon as config is loaded, rather than waiting for a command to be run. Since the cli runs a command immediately after loading anyway, this is not a relevant change for our purposes, but worth mentioning here.
- Loading branch information
Showing
7 changed files
with
388 additions
and
199 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,57 +1,122 @@ | ||
// print a banner telling the user to upgrade npm to latest | ||
// but not in CI, and not if we're doing that already. | ||
// Check daily for betas, and weekly otherwise. | ||
|
||
const pacote = require('pacote') | ||
const ciDetect = require('@npmcli/ci-detect') | ||
const semver = require('semver') | ||
const chalk = require('chalk') | ||
const { promisify } = require('util') | ||
const stat = promisify(require('fs').stat) | ||
const writeFile = promisify(require('fs').writeFile) | ||
const { resolve } = require('path') | ||
|
||
const isGlobalNpmUpdate = npm => { | ||
return npm.config.get('global') && | ||
return npm.flatOptions.global && | ||
['install', 'update'].includes(npm.command) && | ||
npm.argv.includes('npm') | ||
} | ||
|
||
const { checkVersion } = require('./unsupported.js') | ||
// update check frequency | ||
const DAILY = 1000 * 60 * 60 * 24 | ||
const WEEKLY = DAILY * 7 | ||
|
||
const updateTimeout = async (npm, duration) => { | ||
const t = new Date(Date.now() - duration) | ||
// don't put it in the _cacache folder, just in npm's cache | ||
const f = resolve(npm.flatOptions.cache, '../_update-notifier-last-checked') | ||
// if we don't have a file, then definitely check it. | ||
const st = await stat(f).catch(() => ({ mtime: t - 1 })) | ||
|
||
if (t > st.mtime) { | ||
// best effort, if this fails, it's ok. | ||
// might be using /dev/null as the cache or something weird like that. | ||
await writeFile(f, '').catch(() => {}) | ||
return true | ||
} else { | ||
return false | ||
} | ||
} | ||
|
||
module.exports = (npm) => { | ||
const updateNotifier = module.exports = async (npm, spec = 'latest') => { | ||
// never check for updates in CI, when updating npm already, or opted out | ||
if (!npm.config.get('update-notifier') || | ||
isGlobalNpmUpdate(npm) || | ||
checkVersion(process.version).unsupported) { | ||
return | ||
isGlobalNpmUpdate(npm) || | ||
ciDetect()) { | ||
return null | ||
} | ||
|
||
// if we're on a prerelease train, then updates are coming fast | ||
// check for a new one daily. otherwise, weekly. | ||
const { version } = npm | ||
const current = semver.parse(version) | ||
|
||
// if we're on a beta train, always get the next beta | ||
if (current.prerelease.length) { | ||
spec = `^${version}` | ||
} | ||
|
||
// while on a beta train, get updates daily | ||
const duration = spec !== 'latest' ? DAILY : WEEKLY | ||
|
||
// if we've already checked within the specified duration, don't check again | ||
if (!(await updateTimeout(npm, duration))) { | ||
return null | ||
} | ||
|
||
// if they're currently using a prerelease, nudge to the next prerelease | ||
// otherwise, nudge to latest. | ||
const useColor = npm.log.useColor() | ||
|
||
const mani = await pacote.manifest(`npm@${spec}`, { | ||
// always prefer latest, even if doing --tag=whatever on the cmd | ||
defaultTag: 'latest', | ||
...npm.flatOptions | ||
}).catch(() => null) | ||
|
||
// if pacote failed, give up | ||
if (!mani) { | ||
return null | ||
} | ||
|
||
const pkg = require('../../package.json') | ||
const notifier = require('update-notifier')({ pkg }) | ||
const ciDetect = require('@npmcli/ci-detect') | ||
if ( | ||
notifier.update && | ||
notifier.update.latest !== pkg.version && | ||
!ciDetect() | ||
) { | ||
const chalk = require('chalk') | ||
const useColor = npm.color | ||
const useUnicode = npm.config.get('unicode') | ||
const old = notifier.update.current | ||
const latest = notifier.update.latest | ||
const type = notifier.update.type | ||
const typec = !useColor ? type | ||
: type === 'major' ? chalk.red(type) | ||
: type === 'minor' ? chalk.yellow(type) | ||
: chalk.green(type) | ||
|
||
const changelog = `https://github.com/npm/cli/releases/tag/v${latest}` | ||
notifier.notify({ | ||
message: `New ${typec} version of ${pkg.name} available! ${ | ||
useColor ? chalk.red(old) : old | ||
} ${useUnicode ? '→' : '->'} ${ | ||
useColor ? chalk.green(latest) : latest | ||
}\n` + | ||
`${ | ||
useColor ? chalk.yellow('Changelog:') : 'Changelog:' | ||
} ${ | ||
useColor ? chalk.cyan(changelog) : changelog | ||
}\n` + | ||
`Run ${ | ||
useColor | ||
? chalk.green(`npm install -g ${pkg.name}`) | ||
: `npm i -g ${pkg.name}` | ||
} to update!` | ||
}) | ||
const latest = mani.version | ||
|
||
// if the current version is *greater* than latest, we're on a 'next' | ||
// and should get the updates from that release train. | ||
// Note that this isn't another http request over the network, because | ||
// the packument will be cached by pacote from previous request. | ||
if (semver.gt(version, latest) && spec === 'latest') { | ||
return updateNotifier(npm, `^${version}`) | ||
} | ||
|
||
// if we already have something >= the desired spec, then we're done | ||
if (semver.gte(version, latest)) { | ||
return null | ||
} | ||
|
||
// ok! notify the user about this update they should get. | ||
// The message is saved for printing at process exit so it will not get | ||
// lost in any other messages being printed as part of the command. | ||
const update = semver.parse(mani.version) | ||
const type = update.major !== current.major ? 'major' | ||
: update.minor !== current.minor ? 'minor' | ||
: update.patch !== current.patch ? 'patch' | ||
: 'prerelease' | ||
const typec = !useColor ? type | ||
: type === 'major' ? chalk.red(type) | ||
: type === 'minor' ? chalk.yellow(type) | ||
: chalk.green(type) | ||
const oldc = !useColor ? current : chalk.red(current) | ||
const latestc = !useColor ? latest : chalk.green(latest) | ||
const changelog = `https://github.com/npm/cli/releases/tag/v${latest}` | ||
const changelogc = !useColor ? `<${changelog}>` : chalk.cyan(changelog) | ||
const cmd = `npm install -g npm@${latest}` | ||
const cmdc = !useColor ? `\`${cmd}\`` : chalk.green(cmd) | ||
const message = `\nNew ${typec} version of npm available! ` + | ||
`${oldc} -> ${latestc}\n` + | ||
`Changelog: ${changelogc}\n` + | ||
`Run ${cmdc} to update!\n` | ||
const messagec = !useColor ? message : chalk.bgBlack.white(message) | ||
|
||
return messagec | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.