123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223 |
- const cacache = require('cacache')
- const { promisify } = require('util')
- const log = require('npmlog')
- const pacote = require('pacote')
- const path = require('path')
- const rimraf = promisify(require('rimraf'))
- const semver = require('semver')
- const BaseCommand = require('./base-command.js')
- const npa = require('npm-package-arg')
- const jsonParse = require('json-parse-even-better-errors')
- const localeCompare = require('@isaacs/string-locale-compare')('en')
- const searchCachePackage = async (path, spec, cacheKeys) => {
- const parsed = npa(spec)
- if (parsed.rawSpec !== '' && parsed.type === 'tag')
- throw new Error(`Cannot list cache keys for a tagged package.`)
- const searchMFH = new RegExp(`^make-fetch-happen:request-cache:.*(?<!/[@a-zA-Z]+)/${parsed.name}/-/(${parsed.name}[^/]+.tgz)$`)
- const searchPack = new RegExp(`^make-fetch-happen:request-cache:.*/${parsed.escapedName}$`)
- const results = new Set()
- cacheKeys = new Set(cacheKeys)
- for (const key of cacheKeys) {
- // match on the public key registry url format
- if (searchMFH.test(key)) {
- // extract the version from the filename
- const filename = key.match(searchMFH)[1]
- const noExt = filename.slice(0, -4)
- const noScope = `${parsed.name.split('/').pop()}-`
- const ver = noExt.slice(noScope.length)
- if (semver.satisfies(ver, parsed.rawSpec))
- results.add(key)
- continue
- }
- // is this key a packument?
- if (!searchPack.test(key))
- continue
- results.add(key)
- let packument, details
- try {
- details = await cacache.get(path, key)
- packument = jsonParse(details.data)
- } catch (_) {
- // if we couldn't parse the packument, abort
- continue
- }
- if (!packument.versions || typeof packument.versions !== 'object')
- continue
- // assuming this is a packument
- for (const ver of Object.keys(packument.versions)) {
- if (semver.satisfies(ver, parsed.rawSpec)) {
- if (packument.versions[ver].dist
- && typeof packument.versions[ver].dist === 'object'
- && packument.versions[ver].dist.tarball !== undefined
- && cacheKeys.has(`make-fetch-happen:request-cache:${packument.versions[ver].dist.tarball}`))
- results.add(`make-fetch-happen:request-cache:${packument.versions[ver].dist.tarball}`)
- }
- }
- }
- return results
- }
- class Cache extends BaseCommand {
- static get description () {
- return 'Manipulates packages cache'
- }
- /* istanbul ignore next - see test/lib/load-all-commands.js */
- static get name () {
- return 'cache'
- }
- /* istanbul ignore next - see test/lib/load-all-commands.js */
- static get params () {
- return ['cache']
- }
- /* istanbul ignore next - see test/lib/load-all-commands.js */
- static get usage () {
- return [
- 'add <tarball file>',
- 'add <folder>',
- 'add <tarball url>',
- 'add <git url>',
- 'add <name>@<version>',
- 'clean [<key>]',
- 'ls [<name>@<version>]',
- 'verify',
- ]
- }
- async completion (opts) {
- const argv = opts.conf.argv.remain
- if (argv.length === 2)
- return ['add', 'clean', 'verify', 'ls', 'delete']
- // TODO - eventually...
- switch (argv[2]) {
- case 'verify':
- case 'clean':
- case 'add':
- case 'ls':
- case 'delete':
- return []
- }
- }
- exec (args, cb) {
- this.cache(args).then(() => cb()).catch(cb)
- }
- async cache (args) {
- const cmd = args.shift()
- switch (cmd) {
- case 'rm': case 'clear': case 'clean':
- return await this.clean(args)
- case 'add':
- return await this.add(args)
- case 'verify': case 'check':
- return await this.verify()
- case 'ls':
- return await this.ls(args)
- default:
- throw Object.assign(new Error(this.usage), { code: 'EUSAGE' })
- }
- }
- // npm cache clean [pkg]*
- async clean (args) {
- const cachePath = path.join(this.npm.cache, '_cacache')
- if (args.length === 0) {
- if (!this.npm.config.get('force')) {
- throw new Error(`As of npm@5, the npm cache self-heals from corruption issues
- by treating integrity mismatches as cache misses. As a result,
- data extracted from the cache is guaranteed to be valid. If you
- want to make sure everything is consistent, use \`npm cache verify\`
- instead. Deleting the cache can only make npm go slower, and is
- not likely to correct any problems you may be encountering!
- On the other hand, if you're debugging an issue with the installer,
- or race conditions that depend on the timing of writing to an empty
- cache, you can use \`npm install --cache /tmp/empty-cache\` to use a
- temporary cache instead of nuking the actual one.
- If you're sure you want to delete the entire cache, rerun this command
- with --force.`)
- }
- return rimraf(cachePath)
- }
- for (const key of args) {
- let entry
- try {
- entry = await cacache.get(cachePath, key)
- } catch (err) {
- this.npm.log.warn(`Not Found: ${key}`)
- break
- }
- this.npm.output(`Deleted: ${key}`)
- await cacache.rm.entry(cachePath, key)
- await cacache.rm.content(cachePath, entry.integrity)
- }
- }
- // npm cache add <tarball-url>...
- // npm cache add <pkg> <ver>...
- // npm cache add <tarball>...
- // npm cache add <folder>...
- async add (args) {
- const usage = 'Usage:\n' +
- ' npm cache add <tarball-url>...\n' +
- ' npm cache add <pkg>@<ver>...\n' +
- ' npm cache add <tarball>...\n' +
- ' npm cache add <folder>...\n'
- log.silly('cache add', 'args', args)
- if (args.length === 0)
- throw Object.assign(new Error(usage), { code: 'EUSAGE' })
- return Promise.all(args.map(spec => {
- log.silly('cache add', 'spec', spec)
- // we ask pacote for the thing, and then just throw the data
- // away so that it tee-pipes it into the cache like it does
- // for a normal request.
- return pacote.tarball.stream(spec, stream => {
- stream.resume()
- return stream.promise()
- }, this.npm.flatOptions)
- }))
- }
- async verify () {
- const cache = path.join(this.npm.cache, '_cacache')
- const prefix = cache.indexOf(process.env.HOME) === 0
- ? `~${cache.substr(process.env.HOME.length)}`
- : cache
- const stats = await cacache.verify(cache)
- this.npm.output(`Cache verified and compressed (${prefix})`)
- this.npm.output(`Content verified: ${stats.verifiedContent} (${stats.keptSize} bytes)`)
- stats.badContentCount && this.npm.output(`Corrupted content removed: ${stats.badContentCount}`)
- stats.reclaimedCount && this.npm.output(`Content garbage-collected: ${stats.reclaimedCount} (${stats.reclaimedSize} bytes)`)
- stats.missingContent && this.npm.output(`Missing content: ${stats.missingContent}`)
- this.npm.output(`Index entries: ${stats.totalEntries}`)
- this.npm.output(`Finished in ${stats.runTime.total / 1000}s`)
- }
- // npm cache ls [--package <spec> ...]
- async ls (specs) {
- const cachePath = path.join(this.npm.cache, '_cacache')
- const cacheKeys = Object.keys(await cacache.ls(cachePath))
- if (specs.length > 0) {
- // get results for each package spec specified
- const results = new Set()
- for (const spec of specs) {
- const keySet = await searchCachePackage(cachePath, spec, cacheKeys)
- for (const key of keySet)
- results.add(key)
- }
- [...results].sort(localeCompare).forEach(key => this.npm.output(key))
- return
- }
- cacheKeys.sort(localeCompare).forEach(key => this.npm.output(key))
- }
- }
- module.exports = Cache
|