# All files update-notifier.js

98% Statements 49/50
100% Branches 45/45
75% Functions 3/4
100% Lines 49/49

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
    117
    118

    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x

    1x
    24x

    1x
    1x

    1x
    22x

    22x

    22x

    22x

    20x
    20x

    2x

    1x

    25x

    3x

    22x
    22x

    22x
    4x

    22x

    22x
    2x

    20x

    20x

    20x

    20x
    5x

    15x
    3x

    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(/* istanbul ignore next */ () => {})
        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
      })

      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
    }