|
1 | 1 | // print a banner telling the user to upgrade npm to latest
|
2 | 2 | // but not in CI, and not if we're doing that already.
|
| 3 | +// Check daily for betas, and weekly otherwise. |
| 4 | + |
| 5 | +const pacote = require('pacote') |
| 6 | +const ciDetect = require('@npmcli/ci-detect') |
| 7 | +const semver = require('semver') |
| 8 | +const chalk = require('chalk') |
| 9 | +const { promisify } = require('util') |
| 10 | +const stat = promisify(require('fs').stat) |
| 11 | +const writeFile = promisify(require('fs').writeFile) |
| 12 | +const { resolve } = require('path') |
3 | 13 |
|
4 | 14 | const isGlobalNpmUpdate = npm => {
|
5 |
| - return npm.config.get('global') && |
| 15 | + return npm.flatOptions.global && |
6 | 16 | ['install', 'update'].includes(npm.command) &&
|
7 | 17 | npm.argv.includes('npm')
|
8 | 18 | }
|
9 | 19 |
|
10 |
| -const { checkVersion } = require('./unsupported.js') |
| 20 | +// update check frequency |
| 21 | +const DAILY = 1000 * 60 * 60 * 24 |
| 22 | +const WEEKLY = DAILY * 7 |
| 23 | + |
| 24 | +const updateTimeout = async (npm, duration) => { |
| 25 | + const t = new Date(Date.now() - duration) |
| 26 | + // don't put it in the _cacache folder, just in npm's cache |
| 27 | + const f = resolve(npm.flatOptions.cache, '../_update-notifier-last-checked') |
| 28 | + // if we don't have a file, then definitely check it. |
| 29 | + const st = await stat(f).catch(() => ({ mtime: t - 1 })) |
| 30 | + |
| 31 | + if (t > st.mtime) { |
| 32 | + // best effort, if this fails, it's ok. |
| 33 | + // might be using /dev/null as the cache or something weird like that. |
| 34 | + await writeFile(f, '').catch(() => {}) |
| 35 | + return true |
| 36 | + } else { |
| 37 | + return false |
| 38 | + } |
| 39 | +} |
11 | 40 |
|
12 |
| -module.exports = (npm) => { |
| 41 | +const updateNotifier = module.exports = async (npm, spec = 'latest') => { |
| 42 | + // never check for updates in CI, when updating npm already, or opted out |
13 | 43 | if (!npm.config.get('update-notifier') ||
|
14 |
| - isGlobalNpmUpdate(npm) || |
15 |
| - checkVersion(process.version).unsupported) { |
16 |
| - return |
| 44 | + isGlobalNpmUpdate(npm) || |
| 45 | + ciDetect()) { |
| 46 | + return null |
| 47 | + } |
| 48 | + |
| 49 | + // if we're on a prerelease train, then updates are coming fast |
| 50 | + // check for a new one daily. otherwise, weekly. |
| 51 | + const { version } = npm |
| 52 | + const current = semver.parse(version) |
| 53 | + |
| 54 | + // if we're on a beta train, always get the next beta |
| 55 | + if (current.prerelease.length) { |
| 56 | + spec = `^${version}` |
| 57 | + } |
| 58 | + |
| 59 | + // while on a beta train, get updates daily |
| 60 | + const duration = spec !== 'latest' ? DAILY : WEEKLY |
| 61 | + |
| 62 | + // if we've already checked within the specified duration, don't check again |
| 63 | + if (!(await updateTimeout(npm, duration))) { |
| 64 | + return null |
| 65 | + } |
| 66 | + |
| 67 | + // if they're currently using a prerelease, nudge to the next prerelease |
| 68 | + // otherwise, nudge to latest. |
| 69 | + const useColor = npm.log.useColor() |
| 70 | + |
| 71 | + const mani = await pacote.manifest(`npm@${spec}`, { |
| 72 | + // always prefer latest, even if doing --tag=whatever on the cmd |
| 73 | + defaultTag: 'latest', |
| 74 | + ...npm.flatOptions |
| 75 | + }).catch(() => null) |
| 76 | + |
| 77 | + // if pacote failed, give up |
| 78 | + if (!mani) { |
| 79 | + return null |
17 | 80 | }
|
18 | 81 |
|
19 |
| - const pkg = require('../../package.json') |
20 |
| - const notifier = require('update-notifier')({ pkg }) |
21 |
| - const ciDetect = require('@npmcli/ci-detect') |
22 |
| - if ( |
23 |
| - notifier.update && |
24 |
| - notifier.update.latest !== pkg.version && |
25 |
| - !ciDetect() |
26 |
| - ) { |
27 |
| - const chalk = require('chalk') |
28 |
| - const useColor = npm.color |
29 |
| - const useUnicode = npm.config.get('unicode') |
30 |
| - const old = notifier.update.current |
31 |
| - const latest = notifier.update.latest |
32 |
| - const type = notifier.update.type |
33 |
| - const typec = !useColor ? type |
34 |
| - : type === 'major' ? chalk.red(type) |
35 |
| - : type === 'minor' ? chalk.yellow(type) |
36 |
| - : chalk.green(type) |
37 |
| - |
38 |
| - const changelog = `https://github.com/npm/cli/releases/tag/v${latest}` |
39 |
| - notifier.notify({ |
40 |
| - message: `New ${typec} version of ${pkg.name} available! ${ |
41 |
| - useColor ? chalk.red(old) : old |
42 |
| - } ${useUnicode ? '→' : '->'} ${ |
43 |
| - useColor ? chalk.green(latest) : latest |
44 |
| - }\n` + |
45 |
| - `${ |
46 |
| - useColor ? chalk.yellow('Changelog:') : 'Changelog:' |
47 |
| - } ${ |
48 |
| - useColor ? chalk.cyan(changelog) : changelog |
49 |
| - }\n` + |
50 |
| - `Run ${ |
51 |
| - useColor |
52 |
| - ? chalk.green(`npm install -g ${pkg.name}`) |
53 |
| - : `npm i -g ${pkg.name}` |
54 |
| - } to update!` |
55 |
| - }) |
| 82 | + const latest = mani.version |
| 83 | + |
| 84 | + // if the current version is *greater* than latest, we're on a 'next' |
| 85 | + // and should get the updates from that release train. |
| 86 | + // Note that this isn't another http request over the network, because |
| 87 | + // the packument will be cached by pacote from previous request. |
| 88 | + if (semver.gt(version, latest) && spec === 'latest') { |
| 89 | + return updateNotifier(npm, `^${version}`) |
| 90 | + } |
| 91 | + |
| 92 | + // if we already have something >= the desired spec, then we're done |
| 93 | + if (semver.gte(version, latest)) { |
| 94 | + return null |
56 | 95 | }
|
| 96 | + |
| 97 | + // ok! notify the user about this update they should get. |
| 98 | + // The message is saved for printing at process exit so it will not get |
| 99 | + // lost in any other messages being printed as part of the command. |
| 100 | + const update = semver.parse(mani.version) |
| 101 | + const type = update.major !== current.major ? 'major' |
| 102 | + : update.minor !== current.minor ? 'minor' |
| 103 | + : update.patch !== current.patch ? 'patch' |
| 104 | + : 'prerelease' |
| 105 | + const typec = !useColor ? type |
| 106 | + : type === 'major' ? chalk.red(type) |
| 107 | + : type === 'minor' ? chalk.yellow(type) |
| 108 | + : chalk.green(type) |
| 109 | + const oldc = !useColor ? current : chalk.red(current) |
| 110 | + const latestc = !useColor ? latest : chalk.green(latest) |
| 111 | + const changelog = `https://github.com/npm/cli/releases/tag/v${latest}` |
| 112 | + const changelogc = !useColor ? `<${changelog}>` : chalk.cyan(changelog) |
| 113 | + const cmd = `npm install -g npm@${latest}` |
| 114 | + const cmdc = !useColor ? `\`${cmd}\`` : chalk.green(cmd) |
| 115 | + const message = `\nNew ${typec} version of npm available! ` + |
| 116 | + `${oldc} -> ${latestc}\n` + |
| 117 | + `Changelog: ${changelogc}\n` + |
| 118 | + `Run ${cmdc} to update!\n` |
| 119 | + const messagec = !useColor ? message : chalk.bgBlack.white(message) |
| 120 | + |
| 121 | + return messagec |
57 | 122 | }
|
0 commit comments