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