11cb0ef41Sopenharmony_ciconst cacache = require('cacache')
21cb0ef41Sopenharmony_ciconst pacote = require('pacote')
31cb0ef41Sopenharmony_ciconst fs = require('fs/promises')
41cb0ef41Sopenharmony_ciconst { join } = require('path')
51cb0ef41Sopenharmony_ciconst semver = require('semver')
61cb0ef41Sopenharmony_ciconst BaseCommand = require('../base-command.js')
71cb0ef41Sopenharmony_ciconst npa = require('npm-package-arg')
81cb0ef41Sopenharmony_ciconst jsonParse = require('json-parse-even-better-errors')
91cb0ef41Sopenharmony_ciconst localeCompare = require('@isaacs/string-locale-compare')('en')
101cb0ef41Sopenharmony_ciconst log = require('../utils/log-shim')
111cb0ef41Sopenharmony_ci
121cb0ef41Sopenharmony_ciconst searchCachePackage = async (path, parsed, cacheKeys) => {
131cb0ef41Sopenharmony_ci  /* eslint-disable-next-line max-len */
141cb0ef41Sopenharmony_ci  const searchMFH = new RegExp(`^make-fetch-happen:request-cache:.*(?<!/[@a-zA-Z]+)/${parsed.name}/-/(${parsed.name}[^/]+.tgz)$`)
151cb0ef41Sopenharmony_ci  const searchPack = new RegExp(`^make-fetch-happen:request-cache:.*/${parsed.escapedName}$`)
161cb0ef41Sopenharmony_ci  const results = new Set()
171cb0ef41Sopenharmony_ci  cacheKeys = new Set(cacheKeys)
181cb0ef41Sopenharmony_ci  for (const key of cacheKeys) {
191cb0ef41Sopenharmony_ci    // match on the public key registry url format
201cb0ef41Sopenharmony_ci    if (searchMFH.test(key)) {
211cb0ef41Sopenharmony_ci      // extract the version from the filename
221cb0ef41Sopenharmony_ci      const filename = key.match(searchMFH)[1]
231cb0ef41Sopenharmony_ci      const noExt = filename.slice(0, -4)
241cb0ef41Sopenharmony_ci      const noScope = `${parsed.name.split('/').pop()}-`
251cb0ef41Sopenharmony_ci      const ver = noExt.slice(noScope.length)
261cb0ef41Sopenharmony_ci      if (semver.satisfies(ver, parsed.rawSpec)) {
271cb0ef41Sopenharmony_ci        results.add(key)
281cb0ef41Sopenharmony_ci      }
291cb0ef41Sopenharmony_ci      continue
301cb0ef41Sopenharmony_ci    }
311cb0ef41Sopenharmony_ci    // is this key a packument?
321cb0ef41Sopenharmony_ci    if (!searchPack.test(key)) {
331cb0ef41Sopenharmony_ci      continue
341cb0ef41Sopenharmony_ci    }
351cb0ef41Sopenharmony_ci
361cb0ef41Sopenharmony_ci    results.add(key)
371cb0ef41Sopenharmony_ci    let packument, details
381cb0ef41Sopenharmony_ci    try {
391cb0ef41Sopenharmony_ci      details = await cacache.get(path, key)
401cb0ef41Sopenharmony_ci      packument = jsonParse(details.data)
411cb0ef41Sopenharmony_ci    } catch (_) {
421cb0ef41Sopenharmony_ci      // if we couldn't parse the packument, abort
431cb0ef41Sopenharmony_ci      continue
441cb0ef41Sopenharmony_ci    }
451cb0ef41Sopenharmony_ci    if (!packument.versions || typeof packument.versions !== 'object') {
461cb0ef41Sopenharmony_ci      continue
471cb0ef41Sopenharmony_ci    }
481cb0ef41Sopenharmony_ci
491cb0ef41Sopenharmony_ci    // assuming this is a packument
501cb0ef41Sopenharmony_ci    for (const ver of Object.keys(packument.versions)) {
511cb0ef41Sopenharmony_ci      if (semver.satisfies(ver, parsed.rawSpec)) {
521cb0ef41Sopenharmony_ci        if (packument.versions[ver].dist &&
531cb0ef41Sopenharmony_ci          typeof packument.versions[ver].dist === 'object' &&
541cb0ef41Sopenharmony_ci          packument.versions[ver].dist.tarball !== undefined &&
551cb0ef41Sopenharmony_ci          cacheKeys.has(`make-fetch-happen:request-cache:${packument.versions[ver].dist.tarball}`)
561cb0ef41Sopenharmony_ci        ) {
571cb0ef41Sopenharmony_ci          results.add(`make-fetch-happen:request-cache:${packument.versions[ver].dist.tarball}`)
581cb0ef41Sopenharmony_ci        }
591cb0ef41Sopenharmony_ci      }
601cb0ef41Sopenharmony_ci    }
611cb0ef41Sopenharmony_ci  }
621cb0ef41Sopenharmony_ci  return results
631cb0ef41Sopenharmony_ci}
641cb0ef41Sopenharmony_ci
651cb0ef41Sopenharmony_ciclass Cache extends BaseCommand {
661cb0ef41Sopenharmony_ci  static description = 'Manipulates packages cache'
671cb0ef41Sopenharmony_ci  static name = 'cache'
681cb0ef41Sopenharmony_ci  static params = ['cache']
691cb0ef41Sopenharmony_ci  static usage = [
701cb0ef41Sopenharmony_ci    'add <package-spec>',
711cb0ef41Sopenharmony_ci    'clean [<key>]',
721cb0ef41Sopenharmony_ci    'ls [<name>@<version>]',
731cb0ef41Sopenharmony_ci    'verify',
741cb0ef41Sopenharmony_ci  ]
751cb0ef41Sopenharmony_ci
761cb0ef41Sopenharmony_ci  static async completion (opts) {
771cb0ef41Sopenharmony_ci    const argv = opts.conf.argv.remain
781cb0ef41Sopenharmony_ci    if (argv.length === 2) {
791cb0ef41Sopenharmony_ci      return ['add', 'clean', 'verify', 'ls']
801cb0ef41Sopenharmony_ci    }
811cb0ef41Sopenharmony_ci
821cb0ef41Sopenharmony_ci    // TODO - eventually...
831cb0ef41Sopenharmony_ci    switch (argv[2]) {
841cb0ef41Sopenharmony_ci      case 'verify':
851cb0ef41Sopenharmony_ci      case 'clean':
861cb0ef41Sopenharmony_ci      case 'add':
871cb0ef41Sopenharmony_ci      case 'ls':
881cb0ef41Sopenharmony_ci        return []
891cb0ef41Sopenharmony_ci    }
901cb0ef41Sopenharmony_ci  }
911cb0ef41Sopenharmony_ci
921cb0ef41Sopenharmony_ci  async exec (args) {
931cb0ef41Sopenharmony_ci    const cmd = args.shift()
941cb0ef41Sopenharmony_ci    switch (cmd) {
951cb0ef41Sopenharmony_ci      case 'rm': case 'clear': case 'clean':
961cb0ef41Sopenharmony_ci        return await this.clean(args)
971cb0ef41Sopenharmony_ci      case 'add':
981cb0ef41Sopenharmony_ci        return await this.add(args)
991cb0ef41Sopenharmony_ci      case 'verify': case 'check':
1001cb0ef41Sopenharmony_ci        return await this.verify()
1011cb0ef41Sopenharmony_ci      case 'ls':
1021cb0ef41Sopenharmony_ci        return await this.ls(args)
1031cb0ef41Sopenharmony_ci      default:
1041cb0ef41Sopenharmony_ci        throw this.usageError()
1051cb0ef41Sopenharmony_ci    }
1061cb0ef41Sopenharmony_ci  }
1071cb0ef41Sopenharmony_ci
1081cb0ef41Sopenharmony_ci  // npm cache clean [pkg]*
1091cb0ef41Sopenharmony_ci  async clean (args) {
1101cb0ef41Sopenharmony_ci    const cachePath = join(this.npm.cache, '_cacache')
1111cb0ef41Sopenharmony_ci    if (args.length === 0) {
1121cb0ef41Sopenharmony_ci      if (!this.npm.config.get('force')) {
1131cb0ef41Sopenharmony_ci        throw new Error(`As of npm@5, the npm cache self-heals from corruption issues
1141cb0ef41Sopenharmony_ci  by treating integrity mismatches as cache misses.  As a result,
1151cb0ef41Sopenharmony_ci  data extracted from the cache is guaranteed to be valid.  If you
1161cb0ef41Sopenharmony_ci  want to make sure everything is consistent, use \`npm cache verify\`
1171cb0ef41Sopenharmony_ci  instead.  Deleting the cache can only make npm go slower, and is
1181cb0ef41Sopenharmony_ci  not likely to correct any problems you may be encountering!
1191cb0ef41Sopenharmony_ci
1201cb0ef41Sopenharmony_ci  On the other hand, if you're debugging an issue with the installer,
1211cb0ef41Sopenharmony_ci  or race conditions that depend on the timing of writing to an empty
1221cb0ef41Sopenharmony_ci  cache, you can use \`npm install --cache /tmp/empty-cache\` to use a
1231cb0ef41Sopenharmony_ci  temporary cache instead of nuking the actual one.
1241cb0ef41Sopenharmony_ci
1251cb0ef41Sopenharmony_ci  If you're sure you want to delete the entire cache, rerun this command
1261cb0ef41Sopenharmony_ci  with --force.`)
1271cb0ef41Sopenharmony_ci      }
1281cb0ef41Sopenharmony_ci      return fs.rm(cachePath, { recursive: true, force: true })
1291cb0ef41Sopenharmony_ci    }
1301cb0ef41Sopenharmony_ci    for (const key of args) {
1311cb0ef41Sopenharmony_ci      let entry
1321cb0ef41Sopenharmony_ci      try {
1331cb0ef41Sopenharmony_ci        entry = await cacache.get(cachePath, key)
1341cb0ef41Sopenharmony_ci      } catch (err) {
1351cb0ef41Sopenharmony_ci        log.warn(`Not Found: ${key}`)
1361cb0ef41Sopenharmony_ci        break
1371cb0ef41Sopenharmony_ci      }
1381cb0ef41Sopenharmony_ci      this.npm.output(`Deleted: ${key}`)
1391cb0ef41Sopenharmony_ci      await cacache.rm.entry(cachePath, key)
1401cb0ef41Sopenharmony_ci      // XXX this could leave other entries without content!
1411cb0ef41Sopenharmony_ci      await cacache.rm.content(cachePath, entry.integrity)
1421cb0ef41Sopenharmony_ci    }
1431cb0ef41Sopenharmony_ci  }
1441cb0ef41Sopenharmony_ci
1451cb0ef41Sopenharmony_ci  // npm cache add <tarball-url>...
1461cb0ef41Sopenharmony_ci  // npm cache add <pkg> <ver>...
1471cb0ef41Sopenharmony_ci  // npm cache add <tarball>...
1481cb0ef41Sopenharmony_ci  // npm cache add <folder>...
1491cb0ef41Sopenharmony_ci  async add (args) {
1501cb0ef41Sopenharmony_ci    log.silly('cache add', 'args', args)
1511cb0ef41Sopenharmony_ci    if (args.length === 0) {
1521cb0ef41Sopenharmony_ci      throw this.usageError('First argument to `add` is required')
1531cb0ef41Sopenharmony_ci    }
1541cb0ef41Sopenharmony_ci
1551cb0ef41Sopenharmony_ci    return Promise.all(args.map(spec => {
1561cb0ef41Sopenharmony_ci      log.silly('cache add', 'spec', spec)
1571cb0ef41Sopenharmony_ci      // we ask pacote for the thing, and then just throw the data
1581cb0ef41Sopenharmony_ci      // away so that it tee-pipes it into the cache like it does
1591cb0ef41Sopenharmony_ci      // for a normal request.
1601cb0ef41Sopenharmony_ci      return pacote.tarball.stream(spec, stream => {
1611cb0ef41Sopenharmony_ci        stream.resume()
1621cb0ef41Sopenharmony_ci        return stream.promise()
1631cb0ef41Sopenharmony_ci      }, { ...this.npm.flatOptions })
1641cb0ef41Sopenharmony_ci    }))
1651cb0ef41Sopenharmony_ci  }
1661cb0ef41Sopenharmony_ci
1671cb0ef41Sopenharmony_ci  async verify () {
1681cb0ef41Sopenharmony_ci    const cache = join(this.npm.cache, '_cacache')
1691cb0ef41Sopenharmony_ci    const prefix = cache.indexOf(process.env.HOME) === 0
1701cb0ef41Sopenharmony_ci      ? `~${cache.slice(process.env.HOME.length)}`
1711cb0ef41Sopenharmony_ci      : cache
1721cb0ef41Sopenharmony_ci    const stats = await cacache.verify(cache)
1731cb0ef41Sopenharmony_ci    this.npm.output(`Cache verified and compressed (${prefix})`)
1741cb0ef41Sopenharmony_ci    this.npm.output(`Content verified: ${stats.verifiedContent} (${stats.keptSize} bytes)`)
1751cb0ef41Sopenharmony_ci    if (stats.badContentCount) {
1761cb0ef41Sopenharmony_ci      this.npm.output(`Corrupted content removed: ${stats.badContentCount}`)
1771cb0ef41Sopenharmony_ci    }
1781cb0ef41Sopenharmony_ci    if (stats.reclaimedCount) {
1791cb0ef41Sopenharmony_ci      /* eslint-disable-next-line max-len */
1801cb0ef41Sopenharmony_ci      this.npm.output(`Content garbage-collected: ${stats.reclaimedCount} (${stats.reclaimedSize} bytes)`)
1811cb0ef41Sopenharmony_ci    }
1821cb0ef41Sopenharmony_ci    if (stats.missingContent) {
1831cb0ef41Sopenharmony_ci      this.npm.output(`Missing content: ${stats.missingContent}`)
1841cb0ef41Sopenharmony_ci    }
1851cb0ef41Sopenharmony_ci    this.npm.output(`Index entries: ${stats.totalEntries}`)
1861cb0ef41Sopenharmony_ci    this.npm.output(`Finished in ${stats.runTime.total / 1000}s`)
1871cb0ef41Sopenharmony_ci  }
1881cb0ef41Sopenharmony_ci
1891cb0ef41Sopenharmony_ci  // npm cache ls [--package <spec> ...]
1901cb0ef41Sopenharmony_ci  async ls (specs) {
1911cb0ef41Sopenharmony_ci    const cachePath = join(this.npm.cache, '_cacache')
1921cb0ef41Sopenharmony_ci    const cacheKeys = Object.keys(await cacache.ls(cachePath))
1931cb0ef41Sopenharmony_ci    if (specs.length > 0) {
1941cb0ef41Sopenharmony_ci      // get results for each package spec specified
1951cb0ef41Sopenharmony_ci      const results = new Set()
1961cb0ef41Sopenharmony_ci      for (const spec of specs) {
1971cb0ef41Sopenharmony_ci        const parsed = npa(spec)
1981cb0ef41Sopenharmony_ci        if (parsed.rawSpec !== '' && parsed.type === 'tag') {
1991cb0ef41Sopenharmony_ci          throw this.usageError('Cannot list cache keys for a tagged package.')
2001cb0ef41Sopenharmony_ci        }
2011cb0ef41Sopenharmony_ci        const keySet = await searchCachePackage(cachePath, parsed, cacheKeys)
2021cb0ef41Sopenharmony_ci        for (const key of keySet) {
2031cb0ef41Sopenharmony_ci          results.add(key)
2041cb0ef41Sopenharmony_ci        }
2051cb0ef41Sopenharmony_ci      }
2061cb0ef41Sopenharmony_ci      [...results].sort(localeCompare).forEach(key => this.npm.output(key))
2071cb0ef41Sopenharmony_ci      return
2081cb0ef41Sopenharmony_ci    }
2091cb0ef41Sopenharmony_ci    cacheKeys.sort(localeCompare).forEach(key => this.npm.output(key))
2101cb0ef41Sopenharmony_ci  }
2111cb0ef41Sopenharmony_ci}
2121cb0ef41Sopenharmony_ci
2131cb0ef41Sopenharmony_cimodule.exports = Cache
214