All files / lib/utils update-notifier.js

100% Statements 53/53
100% Branches 47/47
100% Functions 6/6
100% Lines 52/52

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116        1x 1x 1x 1x 1x 1x 1x 1x   1x 27x           1x 1x   1x 25x   25x   25x   25x     23x 23x   2x     1x   28x     3x       25x 25x     25x 4x     25x     25x 2x       23x   23x       1x     23x 1x   22x           22x 5x     17x 5x         12x 12x       12x       12x 12x 12x 12x 12x 12x 12x       12x   12x    
// 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.flatOptions.global &&
    ['install', 'update'].includes(npm.command) &&
    npm.argv.includes('npm')
}
 
// 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
}
 
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) ||
      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 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
}