1'use strict' 2/* eslint-disable standard/no-callback-literal */ 3 4const BB = require('bluebird') 5 6const assert = require('assert') 7const cacache = require('cacache') 8const finished = BB.promisify(require('mississippi').finished) 9const log = require('npmlog') 10const npa = require('npm-package-arg') 11const npm = require('./npm.js') 12const npmConfig = require('./config/figgy-config.js') 13const output = require('./utils/output.js') 14const pacote = require('pacote') 15const path = require('path') 16const rm = BB.promisify(require('./utils/gently-rm.js')) 17const unbuild = BB.promisify(npm.commands.unbuild) 18 19cache.usage = 'npm cache add <tarball file>' + 20 '\nnpm cache add <folder>' + 21 '\nnpm cache add <tarball url>' + 22 '\nnpm cache add <git url>' + 23 '\nnpm cache add <name>@<version>' + 24 '\nnpm cache clean' + 25 '\nnpm cache verify' 26 27cache.completion = function (opts, cb) { 28 var argv = opts.conf.argv.remain 29 if (argv.length === 2) { 30 return cb(null, ['add', 'clean']) 31 } 32 33 // TODO - eventually... 34 switch (argv[2]) { 35 case 'clean': 36 case 'add': 37 return cb(null, []) 38 } 39} 40 41exports = module.exports = cache 42function cache (args, cb) { 43 const cmd = args.shift() 44 let result 45 switch (cmd) { 46 case 'rm': case 'clear': case 'clean': 47 result = clean(args) 48 break 49 case 'add': 50 result = add(args, npm.prefix) 51 break 52 case 'verify': case 'check': 53 result = verify() 54 break 55 default: return cb('Usage: ' + cache.usage) 56 } 57 if (!result || !result.then) { 58 throw new Error(`npm cache ${cmd} handler did not return a Promise`) 59 } 60 result.then(() => cb(), cb) 61} 62 63// npm cache clean [pkg]* 64cache.clean = clean 65function clean (args) { 66 if (!args) args = [] 67 if (args.length) { 68 return BB.reject(new Error('npm cache clear does not accept arguments')) 69 } 70 const cachePath = path.join(npm.cache, '_cacache') 71 if (!npm.config.get('force')) { 72 return BB.reject(new Error("As of npm@5, the npm cache self-heals from corruption issues and data extracted from the cache is guaranteed to be valid. If you want to make sure everything is consistent, use 'npm cache verify' instead. On the other hand, if you're debugging an issue with the installer, you can use `npm install --cache /tmp/empty-cache` to use a temporary cache instead of nuking the actual one.\n\nIf you're sure you want to delete the entire cache, rerun this command with --force.")) 73 } 74 // TODO - remove specific packages or package versions 75 return rm(cachePath) 76} 77 78// npm cache add <tarball-url> 79// npm cache add <pkg> <ver> 80// npm cache add <tarball> 81// npm cache add <folder> 82cache.add = function (pkg, ver, where, scrub) { 83 assert(typeof pkg === 'string', 'must include name of package to install') 84 if (scrub) { 85 return clean([]).then(() => { 86 return add([pkg, ver], where) 87 }) 88 } 89 return add([pkg, ver], where) 90} 91 92function add (args, where) { 93 var usage = 'Usage:\n' + 94 ' npm cache add <tarball-url>\n' + 95 ' npm cache add <pkg>@<ver>\n' + 96 ' npm cache add <tarball>\n' + 97 ' npm cache add <folder>\n' 98 var spec 99 log.silly('cache add', 'args', args) 100 if (args[1] === undefined) args[1] = null 101 // at this point the args length must ==2 102 if (args[1] !== null) { 103 spec = args[0] + '@' + args[1] 104 } else if (args.length === 2) { 105 spec = args[0] 106 } 107 log.verbose('cache add', 'spec', spec) 108 if (!spec) return BB.reject(new Error(usage)) 109 log.silly('cache add', 'parsed spec', spec) 110 return finished(pacote.tarball.stream(spec, npmConfig({where})).resume()) 111} 112 113cache.verify = verify 114function verify () { 115 const cache = path.join(npm.config.get('cache'), '_cacache') 116 let prefix = cache 117 if (prefix.indexOf(process.env.HOME) === 0) { 118 prefix = '~' + prefix.substr(process.env.HOME.length) 119 } 120 return cacache.verify(cache).then((stats) => { 121 output(`Cache verified and compressed (${prefix}):`) 122 output(`Content verified: ${stats.verifiedContent} (${stats.keptSize} bytes)`) 123 stats.badContentCount && output(`Corrupted content removed: ${stats.badContentCount}`) 124 stats.reclaimedCount && output(`Content garbage-collected: ${stats.reclaimedCount} (${stats.reclaimedSize} bytes)`) 125 stats.missingContent && output(`Missing content: ${stats.missingContent}`) 126 output(`Index entries: ${stats.totalEntries}`) 127 output(`Finished in ${stats.runTime.total / 1000}s`) 128 }) 129} 130 131cache.unpack = unpack 132function unpack (pkg, ver, unpackTarget, dmode, fmode, uid, gid) { 133 return unbuild([unpackTarget], true).then(() => { 134 const opts = npmConfig({dmode, fmode, uid, gid, offline: true}) 135 return pacote.extract(npa.resolve(pkg, ver), unpackTarget, opts) 136 }) 137} 138