From 7f48c55c69a7c573edbf46eaa3bdc0f5b4542058 Mon Sep 17 00:00:00 2001 From: Gar Date: Wed, 18 May 2022 13:25:57 -0700 Subject: [PATCH 1/4] deps: cacache@16.1.0 * allow external integrity/size source --- node_modules/cacache/lib/content/read.js | 163 +++++----- node_modules/cacache/lib/content/rm.js | 18 +- node_modules/cacache/lib/content/write.js | 62 ++-- node_modules/cacache/lib/entry-index.js | 195 ++++++------ node_modules/cacache/lib/get.js | 140 ++++----- node_modules/cacache/lib/put.js | 35 +-- node_modules/cacache/lib/util/fix-owner.js | 75 +++-- node_modules/cacache/lib/util/move-file.js | 83 +++-- node_modules/cacache/lib/util/tmp.js | 12 +- node_modules/cacache/lib/verify.js | 348 +++++++++------------ node_modules/cacache/package.json | 10 +- package-lock.json | 14 +- package.json | 2 +- 13 files changed, 528 insertions(+), 629 deletions(-) diff --git a/node_modules/cacache/lib/content/read.js b/node_modules/cacache/lib/content/read.js index 0aeb454725bd0..f5128fe185d0f 100644 --- a/node_modules/cacache/lib/content/read.js +++ b/node_modules/cacache/lib/content/read.js @@ -1,42 +1,35 @@ 'use strict' -const util = require('util') - -const fs = require('fs') +const fs = require('@npmcli/fs') const fsm = require('fs-minipass') const ssri = require('ssri') const contentPath = require('./path') const Pipeline = require('minipass-pipeline') -const lstat = util.promisify(fs.lstat) -const readFile = util.promisify(fs.readFile) -const copyFile = util.promisify(fs.copyFile) - module.exports = read const MAX_SINGLE_READ_SIZE = 64 * 1024 * 1024 -function read (cache, integrity, opts = {}) { +async function read (cache, integrity, opts = {}) { const { size } = opts - return withContentSri(cache, integrity, (cpath, sri) => { + const { stat, cpath, sri } = await withContentSri(cache, integrity, async (cpath, sri) => { // get size - return lstat(cpath).then(stat => ({ stat, cpath, sri })) - }).then(({ stat, cpath, sri }) => { - if (typeof size === 'number' && stat.size !== size) { - throw sizeError(size, stat.size) - } + const stat = await fs.lstat(cpath) + return { stat, cpath, sri } + }) + if (typeof size === 'number' && stat.size !== size) { + throw sizeError(size, stat.size) + } - if (stat.size > MAX_SINGLE_READ_SIZE) { - return readPipeline(cpath, stat.size, sri, new Pipeline()).concat() - } + if (stat.size > MAX_SINGLE_READ_SIZE) { + return readPipeline(cpath, stat.size, sri, new Pipeline()).concat() + } - return readFile(cpath, null).then((data) => { - if (!ssri.checkData(data, sri)) { - throw integrityError(sri, cpath) - } + const data = await fs.readFile(cpath, { encoding: null }) + if (!ssri.checkData(data, sri)) { + throw integrityError(sri, cpath) + } - return data - }) - }) + return data } const readPipeline = (cpath, size, sri, stream) => { @@ -58,7 +51,7 @@ module.exports.sync = readSync function readSync (cache, integrity, opts = {}) { const { size } = opts return withContentSriSync(cache, integrity, (cpath, sri) => { - const data = fs.readFileSync(cpath) + const data = fs.readFileSync(cpath, { encoding: null }) if (typeof size === 'number' && size !== data.length) { throw sizeError(size, data.length) } @@ -77,16 +70,19 @@ module.exports.readStream = readStream function readStream (cache, integrity, opts = {}) { const { size } = opts const stream = new Pipeline() - withContentSri(cache, integrity, (cpath, sri) => { - // just lstat to ensure it exists - return lstat(cpath).then((stat) => ({ stat, cpath, sri })) - }).then(({ stat, cpath, sri }) => { + // Set all this up to run on the stream and then just return the stream + Promise.resolve().then(async () => { + const { stat, cpath, sri } = await withContentSri(cache, integrity, async (cpath, sri) => { + // just lstat to ensure it exists + const stat = await fs.lstat(cpath) + return { stat, cpath, sri } + }) if (typeof size === 'number' && size !== stat.size) { return stream.emit('error', sizeError(size, stat.size)) } readPipeline(cpath, stat.size, sri, stream) - }, er => stream.emit('error', er)) + }).catch(err => stream.emit('error', err)) return stream } @@ -96,7 +92,7 @@ module.exports.copy.sync = copySync function copy (cache, integrity, dest) { return withContentSri(cache, integrity, (cpath, sri) => { - return copyFile(cpath, dest) + return fs.copyFile(cpath, dest) }) } @@ -108,14 +104,17 @@ function copySync (cache, integrity, dest) { module.exports.hasContent = hasContent -function hasContent (cache, integrity) { +async function hasContent (cache, integrity) { if (!integrity) { - return Promise.resolve(false) + return false } - return withContentSri(cache, integrity, (cpath, sri) => { - return lstat(cpath).then((stat) => ({ size: stat.size, sri, stat })) - }).catch((err) => { + try { + return await withContentSri(cache, integrity, async (cpath, sri) => { + const stat = await fs.lstat(cpath) + return { size: stat.size, sri, stat } + }) + } catch (err) { if (err.code === 'ENOENT') { return false } @@ -128,7 +127,7 @@ function hasContent (cache, integrity) { return false } } - }) + } } module.exports.hasContent.sync = hasContentSync @@ -159,61 +158,47 @@ function hasContentSync (cache, integrity) { }) } -function withContentSri (cache, integrity, fn) { - const tryFn = () => { - const sri = ssri.parse(integrity) - // If `integrity` has multiple entries, pick the first digest - // with available local data. - const algo = sri.pickAlgorithm() - const digests = sri[algo] - - if (digests.length <= 1) { - const cpath = contentPath(cache, digests[0]) - return fn(cpath, digests[0]) - } else { - // Can't use race here because a generic error can happen before - // a ENOENT error, and can happen before a valid result - return Promise - .all(digests.map((meta) => { - return withContentSri(cache, meta, fn) - .catch((err) => { - if (err.code === 'ENOENT') { - return Object.assign( - new Error('No matching content found for ' + sri.toString()), - { code: 'ENOENT' } - ) - } - return err - }) - })) - .then((results) => { - // Return the first non error if it is found - const result = results.find((r) => !(r instanceof Error)) - if (result) { - return result - } - - // Throw the No matching content found error - const enoentError = results.find((r) => r.code === 'ENOENT') - if (enoentError) { - throw enoentError - } - - // Throw generic error - throw results.find((r) => r instanceof Error) - }) +async function withContentSri (cache, integrity, fn) { + const sri = ssri.parse(integrity) + // If `integrity` has multiple entries, pick the first digest + // with available local data. + const algo = sri.pickAlgorithm() + const digests = sri[algo] + + if (digests.length <= 1) { + const cpath = contentPath(cache, digests[0]) + return fn(cpath, digests[0]) + } else { + // Can't use race here because a generic error can happen before + // a ENOENT error, and can happen before a valid result + const results = await Promise.all(digests.map(async (meta) => { + try { + return await withContentSri(cache, meta, fn) + } catch (err) { + if (err.code === 'ENOENT') { + return Object.assign( + new Error('No matching content found for ' + sri.toString()), + { code: 'ENOENT' } + ) + } + return err + } + })) + // Return the first non error if it is found + const result = results.find((r) => !(r instanceof Error)) + if (result) { + return result } - } - return new Promise((resolve, reject) => { - try { - tryFn() - .then(resolve) - .catch(reject) - } catch (err) { - reject(err) + // Throw the No matching content found error + const enoentError = results.find((r) => r.code === 'ENOENT') + if (enoentError) { + throw enoentError } - }) + + // Throw generic error + throw results.find((r) => r instanceof Error) + } } function withContentSriSync (cache, integrity, fn) { diff --git a/node_modules/cacache/lib/content/rm.js b/node_modules/cacache/lib/content/rm.js index 50612364e9b48..f7333053b393f 100644 --- a/node_modules/cacache/lib/content/rm.js +++ b/node_modules/cacache/lib/content/rm.js @@ -8,13 +8,13 @@ const rimraf = util.promisify(require('rimraf')) module.exports = rm -function rm (cache, integrity) { - return hasContent(cache, integrity).then((content) => { - // ~pretty~ sure we can't end up with a content lacking sri, but be safe - if (content && content.sri) { - return rimraf(contentPath(cache, content.sri)).then(() => true) - } else { - return false - } - }) +async function rm (cache, integrity) { + const content = await hasContent(cache, integrity) + // ~pretty~ sure we can't end up with a content lacking sri, but be safe + if (content && content.sri) { + await rimraf(contentPath(cache, content.sri)) + return true + } else { + return false + } } diff --git a/node_modules/cacache/lib/content/write.js b/node_modules/cacache/lib/content/write.js index b0aa18c12ba78..62388dc81d0fd 100644 --- a/node_modules/cacache/lib/content/write.js +++ b/node_modules/cacache/lib/content/write.js @@ -1,10 +1,11 @@ 'use strict' +const events = require('events') const util = require('util') const contentPath = require('./path') const fixOwner = require('../util/fix-owner') -const fs = require('fs') +const fs = require('@npmcli/fs') const moveFile = require('../util/move-file') const Minipass = require('minipass') const Pipeline = require('minipass-pipeline') @@ -15,8 +16,6 @@ const ssri = require('ssri') const uniqueFilename = require('unique-filename') const fsm = require('fs-minipass') -const writeFile = util.promisify(fs.writeFile) - module.exports = write async function write (cache, data, opts = {}) { @@ -36,7 +35,7 @@ async function write (cache, data, opts = {}) { const tmp = await makeTmp(cache, opts) try { - await writeFile(tmp.target, data, { flag: 'wx' }) + await fs.writeFile(tmp.target, data, { flag: 'wx' }) await moveToDestination(tmp, cache, sri, opts) return { integrity: sri, size: data.length } } finally { @@ -115,7 +114,21 @@ async function handleContent (inputStream, cache, opts) { } } -function pipeToTmp (inputStream, cache, tmpTarget, opts) { +async function pipeToTmp (inputStream, cache, tmpTarget, opts) { + const outStream = new fsm.WriteStream(tmpTarget, { + flags: 'wx', + }) + + if (opts.integrityEmitter) { + // we need to create these all simultaneously since they can fire in any order + const [integrity, size] = await Promise.all([ + events.once(opts.integrityEmitter, 'integrity').then(res => res[0]), + events.once(opts.integrityEmitter, 'size').then(res => res[0]), + new Pipeline(inputStream, outStream).promise(), + ]) + return { integrity, size } + } + let integrity let size const hashStream = ssri.integrityStream({ @@ -130,43 +143,28 @@ function pipeToTmp (inputStream, cache, tmpTarget, opts) { size = s }) - const outStream = new fsm.WriteStream(tmpTarget, { - flags: 'wx', - }) - - // NB: this can throw if the hashStream has a problem with - // it, and the data is fully written. but pipeToTmp is only - // called in promisory contexts where that is handled. - const pipeline = new Pipeline( - inputStream, - hashStream, - outStream - ) - - return pipeline.promise().then(() => ({ integrity, size })) + const pipeline = new Pipeline(inputStream, hashStream, outStream) + await pipeline.promise() + return { integrity, size } } -function makeTmp (cache, opts) { +async function makeTmp (cache, opts) { const tmpTarget = uniqueFilename(path.join(cache, 'tmp'), opts.tmpPrefix) - return fixOwner.mkdirfix(cache, path.dirname(tmpTarget)).then(() => ({ + await fixOwner.mkdirfix(cache, path.dirname(tmpTarget)) + return { target: tmpTarget, moved: false, - })) + } } -function moveToDestination (tmp, cache, sri, opts) { +async function moveToDestination (tmp, cache, sri, opts) { const destination = contentPath(cache, sri) const destDir = path.dirname(destination) - return fixOwner - .mkdirfix(cache, destDir) - .then(() => { - return moveFile(tmp.target, destination) - }) - .then(() => { - tmp.moved = true - return fixOwner.chownr(cache, destination) - }) + await fixOwner.mkdirfix(cache, destDir) + await moveFile(tmp.target, destination) + tmp.moved = true + await fixOwner.chownr(cache, destination) } function sizeError (expected, found) { diff --git a/node_modules/cacache/lib/entry-index.js b/node_modules/cacache/lib/entry-index.js index 9d4485624acbc..cbfa619099f90 100644 --- a/node_modules/cacache/lib/entry-index.js +++ b/node_modules/cacache/lib/entry-index.js @@ -2,7 +2,7 @@ const util = require('util') const crypto = require('crypto') -const fs = require('fs') +const fs = require('@npmcli/fs') const Minipass = require('minipass') const path = require('path') const ssri = require('ssri') @@ -17,11 +17,6 @@ const _rimraf = require('rimraf') const rimraf = util.promisify(_rimraf) rimraf.sync = _rimraf.sync -const appendFile = util.promisify(fs.appendFile) -const readFile = util.promisify(fs.readFile) -const readdir = util.promisify(fs.readdir) -const writeFile = util.promisify(fs.writeFile) - module.exports.NotFoundError = class NotFoundError extends Error { constructor (cache, key) { super(`No cache entry for ${key} found in ${cache}`) @@ -85,7 +80,7 @@ async function compact (cache, key, matchFn, opts = {}) { } const write = async (tmp) => { - await writeFile(tmp.target, newIndex, { flag: 'wx' }) + await fs.writeFile(tmp.target, newIndex, { flag: 'wx' }) await fixOwner.mkdirfix(cache, path.dirname(bucket)) // we use @npmcli/move-file directly here because we // want to overwrite the existing file @@ -118,7 +113,7 @@ async function compact (cache, key, matchFn, opts = {}) { module.exports.insert = insert -function insert (cache, key, integrity, opts = {}) { +async function insert (cache, key, integrity, opts = {}) { const { metadata, size } = opts const bucket = bucketPath(cache, key) const entry = { @@ -128,36 +123,32 @@ function insert (cache, key, integrity, opts = {}) { size, metadata, } - return fixOwner - .mkdirfix(cache, path.dirname(bucket)) - .then(() => { - const stringified = JSON.stringify(entry) - // NOTE - Cleverness ahoy! - // - // This works because it's tremendously unlikely for an entry to corrupt - // another while still preserving the string length of the JSON in - // question. So, we just slap the length in there and verify it on read. - // - // Thanks to @isaacs for the whiteboarding session that ended up with - // this. - return appendFile(bucket, `\n${hashEntry(stringified)}\t${stringified}`) - }) - .then(() => fixOwner.chownr(cache, bucket)) - .catch((err) => { - if (err.code === 'ENOENT') { - return undefined - } + try { + await fixOwner.mkdirfix(cache, path.dirname(bucket)) + const stringified = JSON.stringify(entry) + // NOTE - Cleverness ahoy! + // + // This works because it's tremendously unlikely for an entry to corrupt + // another while still preserving the string length of the JSON in + // question. So, we just slap the length in there and verify it on read. + // + // Thanks to @isaacs for the whiteboarding session that ended up with + // this. + await fs.appendFile(bucket, `\n${hashEntry(stringified)}\t${stringified}`) + await fixOwner.chownr(cache, bucket) + } catch (err) { + if (err.code === 'ENOENT') { + return undefined + } - throw err - // There's a class of race conditions that happen when things get deleted - // during fixOwner, or between the two mkdirfix/chownr calls. - // - // It's perfectly fine to just not bother in those cases and lie - // that the index entry was written. Because it's a cache. - }) - .then(() => { - return formatEntry(cache, entry) - }) + throw err + // There's a class of race conditions that happen when things get deleted + // during fixOwner, or between the two mkdirfix/chownr calls. + // + // It's perfectly fine to just not bother in those cases and lie + // that the index entry was written. Because it's a cache. + } + return formatEntry(cache, entry) } module.exports.insert.sync = insertSync @@ -187,25 +178,24 @@ function insertSync (cache, key, integrity, opts = {}) { module.exports.find = find -function find (cache, key) { +async function find (cache, key) { const bucket = bucketPath(cache, key) - return bucketEntries(bucket) - .then((entries) => { - return entries.reduce((latest, next) => { - if (next && next.key === key) { - return formatEntry(cache, next) - } else { - return latest - } - }, null) - }) - .catch((err) => { - if (err.code === 'ENOENT') { - return null + try { + const entries = await bucketEntries(bucket) + return entries.reduce((latest, next) => { + if (next && next.key === key) { + return formatEntry(cache, next) } else { - throw err + return latest } - }) + }, null) + } catch (err) { + if (err.code === 'ENOENT') { + return null + } else { + throw err + } + } } module.exports.find.sync = findSync @@ -257,67 +247,64 @@ function lsStream (cache) { const indexDir = bucketDir(cache) const stream = new Minipass({ objectMode: true }) - readdirOrEmpty(indexDir).then(buckets => Promise.all( - buckets.map(bucket => { + // Set all this up to run on the stream and then just return the stream + Promise.resolve().then(async () => { + const buckets = await readdirOrEmpty(indexDir) + await Promise.all(buckets.map(async (bucket) => { const bucketPath = path.join(indexDir, bucket) - return readdirOrEmpty(bucketPath).then(subbuckets => Promise.all( - subbuckets.map(subbucket => { - const subbucketPath = path.join(bucketPath, subbucket) - - // "/cachename//./*" - return readdirOrEmpty(subbucketPath).then(entries => Promise.all( - entries.map(entry => { - const entryPath = path.join(subbucketPath, entry) - return bucketEntries(entryPath).then(entries => - // using a Map here prevents duplicate keys from - // showing up twice, I guess? - entries.reduce((acc, entry) => { - acc.set(entry.key, entry) - return acc - }, new Map()) - ).then(reduced => { - // reduced is a map of key => entry - for (const entry of reduced.values()) { - const formatted = formatEntry(cache, entry) - if (formatted) { - stream.write(formatted) - } - } - }).catch(err => { - if (err.code === 'ENOENT') { - return undefined - } - throw err - }) - }) - )) - }) - )) - }) - )) - .then( - () => stream.end(), - err => stream.emit('error', err) - ) + const subbuckets = await readdirOrEmpty(bucketPath) + await Promise.all(subbuckets.map(async (subbucket) => { + const subbucketPath = path.join(bucketPath, subbucket) + + // "/cachename//./*" + const subbucketEntries = await readdirOrEmpty(subbucketPath) + await Promise.all(subbucketEntries.map(async (entry) => { + const entryPath = path.join(subbucketPath, entry) + try { + const entries = await bucketEntries(entryPath) + // using a Map here prevents duplicate keys from showing up + // twice, I guess? + const reduced = entries.reduce((acc, entry) => { + acc.set(entry.key, entry) + return acc + }, new Map()) + // reduced is a map of key => entry + for (const entry of reduced.values()) { + const formatted = formatEntry(cache, entry) + if (formatted) { + stream.write(formatted) + } + } + } catch (err) { + if (err.code === 'ENOENT') { + return undefined + } + throw err + } + })) + })) + })) + stream.end() + }).catch(err => stream.emit('error', err)) return stream } module.exports.ls = ls -function ls (cache) { - return lsStream(cache).collect().then(entries => - entries.reduce((acc, xs) => { - acc[xs.key] = xs - return acc - }, {}) - ) +async function ls (cache) { + const entries = await lsStream(cache).collect() + return entries.reduce((acc, xs) => { + acc[xs.key] = xs + return acc + }, {}) } module.exports.bucketEntries = bucketEntries -function bucketEntries (bucket, filter) { - return readFile(bucket, 'utf8').then((data) => _bucketEntries(data, filter)) +async function bucketEntries (bucket, filter) { + const data = await fs.readFile(bucket, 'utf8') + return _bucketEntries(data, filter) } module.exports.bucketEntries.sync = bucketEntriesSync @@ -406,7 +393,7 @@ function formatEntry (cache, entry, keepAll) { } function readdirOrEmpty (dir) { - return readdir(dir).catch((err) => { + return fs.readdir(dir).catch((err) => { if (err.code === 'ENOENT' || err.code === 'ENOTDIR') { return [] } diff --git a/node_modules/cacache/lib/get.js b/node_modules/cacache/lib/get.js index 58f357b1da3d1..cc9d8f6796647 100644 --- a/node_modules/cacache/lib/get.js +++ b/node_modules/cacache/lib/get.js @@ -8,52 +8,48 @@ const index = require('./entry-index') const memo = require('./memoization') const read = require('./content/read') -function getData (cache, key, opts = {}) { +async function getData (cache, key, opts = {}) { const { integrity, memoize, size } = opts const memoized = memo.get(cache, key, opts) if (memoized && memoize !== false) { - return Promise.resolve({ + return { metadata: memoized.entry.metadata, data: memoized.data, integrity: memoized.entry.integrity, size: memoized.entry.size, - }) + } } - return index.find(cache, key, opts).then((entry) => { - if (!entry) { - throw new index.NotFoundError(cache, key) - } + const entry = await index.find(cache, key, opts) + if (!entry) { + throw new index.NotFoundError(cache, key) + } + const data = await read(cache, entry.integrity, { integrity, size }) + if (memoize) { + memo.put(cache, entry, data, opts) + } - return read(cache, entry.integrity, { integrity, size }).then((data) => { - if (memoize) { - memo.put(cache, entry, data, opts) - } - - return { - data, - metadata: entry.metadata, - size: entry.size, - integrity: entry.integrity, - } - }) - }) + return { + data, + metadata: entry.metadata, + size: entry.size, + integrity: entry.integrity, + } } module.exports = getData -function getDataByDigest (cache, key, opts = {}) { +async function getDataByDigest (cache, key, opts = {}) { const { integrity, memoize, size } = opts const memoized = memo.get.byDigest(cache, key, opts) if (memoized && memoize !== false) { - return Promise.resolve(memoized) + return memoized } - return read(cache, key, { integrity, size }).then((res) => { - if (memoize) { - memo.put.byDigest(cache, key, res, opts) - } - return res - }) + const res = await read(cache, key, { integrity, size }) + if (memoize) { + memo.put.byDigest(cache, key, res, opts) + } + return res } module.exports.byDigest = getDataByDigest @@ -131,36 +127,35 @@ function getStream (cache, key, opts = {}) { } const stream = new Pipeline() - index - .find(cache, key) - .then((entry) => { - if (!entry) { - throw new index.NotFoundError(cache, key) - } - - stream.emit('metadata', entry.metadata) - stream.emit('integrity', entry.integrity) - stream.emit('size', entry.size) - stream.on('newListener', function (ev, cb) { - ev === 'metadata' && cb(entry.metadata) - ev === 'integrity' && cb(entry.integrity) - ev === 'size' && cb(entry.size) - }) - - const src = read.readStream( - cache, - entry.integrity, - { ...opts, size: typeof size !== 'number' ? entry.size : size } - ) - - if (memoize) { - const memoStream = new Collect.PassThrough() - memoStream.on('collect', data => memo.put(cache, entry, data, opts)) - stream.unshift(memoStream) - } - stream.unshift(src) + // Set all this up to run on the stream and then just return the stream + Promise.resolve().then(async () => { + const entry = await index.find(cache, key) + if (!entry) { + throw new index.NotFoundError(cache, key) + } + + stream.emit('metadata', entry.metadata) + stream.emit('integrity', entry.integrity) + stream.emit('size', entry.size) + stream.on('newListener', function (ev, cb) { + ev === 'metadata' && cb(entry.metadata) + ev === 'integrity' && cb(entry.integrity) + ev === 'size' && cb(entry.size) }) - .catch((err) => stream.emit('error', err)) + + const src = read.readStream( + cache, + entry.integrity, + { ...opts, size: typeof size !== 'number' ? entry.size : size } + ) + + if (memoize) { + const memoStream = new Collect.PassThrough() + memoStream.on('collect', data => memo.put(cache, entry, data, opts)) + stream.unshift(memoStream) + } + stream.unshift(src) + }).catch((err) => stream.emit('error', err)) return stream } @@ -204,27 +199,26 @@ function info (cache, key, opts = {}) { } module.exports.info = info -function copy (cache, key, dest, opts = {}) { - return index.find(cache, key, opts).then((entry) => { - if (!entry) { - throw new index.NotFoundError(cache, key) - } - return read.copy(cache, entry.integrity, dest, opts) - .then(() => { - return { - metadata: entry.metadata, - size: entry.size, - integrity: entry.integrity, - } - }) - }) +async function copy (cache, key, dest, opts = {}) { + const entry = await index.find(cache, key, opts) + if (!entry) { + throw new index.NotFoundError(cache, key) + } + await read.copy(cache, entry.integrity, dest, opts) + return { + metadata: entry.metadata, + size: entry.size, + integrity: entry.integrity, + } } module.exports.copy = copy -function copyByDigest (cache, key, dest, opts = {}) { - return read.copy(cache, key, dest, opts).then(() => key) +async function copyByDigest (cache, key, dest, opts = {}) { + await read.copy(cache, key, dest, opts) + return key } + module.exports.copy.byDigest = copyByDigest module.exports.hasContent = read.hasContent diff --git a/node_modules/cacache/lib/put.js b/node_modules/cacache/lib/put.js index eed51874f9f64..9fc932d5f6dec 100644 --- a/node_modules/cacache/lib/put.js +++ b/node_modules/cacache/lib/put.js @@ -14,20 +14,16 @@ const putOpts = (opts) => ({ module.exports = putData -function putData (cache, key, data, opts = {}) { +async function putData (cache, key, data, opts = {}) { const { memoize } = opts opts = putOpts(opts) - return write(cache, data, opts).then((res) => { - return index - .insert(cache, key, res.integrity, { ...opts, size: res.size }) - .then((entry) => { - if (memoize) { - memo.put(cache, entry, data, opts) - } + const res = await write(cache, data, opts) + const entry = await index.insert(cache, key, res.integrity, { ...opts, size: res.size }) + if (memoize) { + memo.put(cache, entry, data, opts) + } - return res.integrity - }) - }) + return res.integrity } module.exports.stream = putStream @@ -68,17 +64,14 @@ function putStream (cache, key, opts = {}) { // last but not least, we write the index and emit hash and size, // and memoize if we're doing that pipeline.push(new Flush({ - flush () { + async flush () { if (!error) { - return index - .insert(cache, key, integrity, { ...opts, size }) - .then((entry) => { - if (memoize && memoData) { - memo.put(cache, entry, memoData, opts) - } - pipeline.emit('integrity', integrity) - pipeline.emit('size', size) - }) + const entry = await index.insert(cache, key, integrity, { ...opts, size }) + if (memoize && memoData) { + memo.put(cache, entry, memoData, opts) + } + pipeline.emit('integrity', integrity) + pipeline.emit('size', size) } }, })) diff --git a/node_modules/cacache/lib/util/fix-owner.js b/node_modules/cacache/lib/util/fix-owner.js index bc14def4e405c..182fcb028f06c 100644 --- a/node_modules/cacache/lib/util/fix-owner.js +++ b/node_modules/cacache/lib/util/fix-owner.js @@ -33,40 +33,38 @@ const getSelf = () => { module.exports.chownr = fixOwner -function fixOwner (cache, filepath) { +async function fixOwner (cache, filepath) { if (!process.getuid) { // This platform doesn't need ownership fixing - return Promise.resolve() + return } getSelf() if (self.uid !== 0) { // almost certainly can't chown anyway - return Promise.resolve() + return } - return Promise.resolve(inferOwner(cache)).then((owner) => { - const { uid, gid } = owner + const { uid, gid } = await inferOwner(cache) - // No need to override if it's already what we used. - if (self.uid === uid && self.gid === gid) { - return - } + // No need to override if it's already what we used. + if (self.uid === uid && self.gid === gid) { + return + } - return inflight('fixOwner: fixing ownership on ' + filepath, () => - chownr( - filepath, - typeof uid === 'number' ? uid : self.uid, - typeof gid === 'number' ? gid : self.gid - ).catch((err) => { - if (err.code === 'ENOENT') { - return null - } - - throw err - }) - ) - }) + return inflight('fixOwner: fixing ownership on ' + filepath, () => + chownr( + filepath, + typeof uid === 'number' ? uid : self.uid, + typeof gid === 'number' ? gid : self.gid + ).catch((err) => { + if (err.code === 'ENOENT') { + return null + } + + throw err + }) + ) } module.exports.chownr.sync = fixOwnerSync @@ -105,26 +103,25 @@ function fixOwnerSync (cache, filepath) { module.exports.mkdirfix = mkdirfix -function mkdirfix (cache, p, cb) { +async function mkdirfix (cache, p, cb) { // we have to infer the owner _before_ making the directory, even though // we aren't going to use the results, since the cache itself might not // exist yet. If we mkdirp it, then our current uid/gid will be assumed // to be correct if it creates the cache folder in the process. - return Promise.resolve(inferOwner(cache)).then(() => { - return mkdirp(p) - .then((made) => { - if (made) { - return fixOwner(cache, made).then(() => made) - } - }) - .catch((err) => { - if (err.code === 'EEXIST') { - return fixOwner(cache, p).then(() => null) - } - - throw err - }) - }) + await inferOwner(cache) + try { + const made = await mkdirp(p) + if (made) { + await fixOwner(cache, made) + return made + } + } catch (err) { + if (err.code === 'EEXIST') { + await fixOwner(cache, p) + return null + } + throw err + } } module.exports.mkdirfix.sync = mkdirfixSync diff --git a/node_modules/cacache/lib/util/move-file.js b/node_modules/cacache/lib/util/move-file.js index 3739cea3df281..a0b40413cb56e 100644 --- a/node_modules/cacache/lib/util/move-file.js +++ b/node_modules/cacache/lib/util/move-file.js @@ -1,18 +1,13 @@ 'use strict' -const fs = require('fs') -const util = require('util') -const chmod = util.promisify(fs.chmod) -const unlink = util.promisify(fs.unlink) -const stat = util.promisify(fs.stat) +const fs = require('@npmcli/fs') const move = require('@npmcli/move-file') const pinflight = require('promise-inflight') module.exports = moveFile -function moveFile (src, dest) { - const isWindows = global.__CACACHE_TEST_FAKE_WINDOWS__ || - process.platform === 'win32' +async function moveFile (src, dest) { + const isWindows = process.platform === 'win32' // This isn't quite an fs.rename -- the assumption is that // if `dest` already exists, and we get certain errors while @@ -23,47 +18,39 @@ function moveFile (src, dest) { // content their own way. // // Note that, as the name suggests, this strictly only supports file moves. - return new Promise((resolve, reject) => { - fs.link(src, dest, (err) => { - if (err) { - if (isWindows && err.code === 'EPERM') { - // XXX This is a really weird way to handle this situation, as it - // results in the src file being deleted even though the dest - // might not exist. Since we pretty much always write files to - // deterministic locations based on content hash, this is likely - // ok (or at worst, just ends in a future cache miss). But it would - // be worth investigating at some time in the future if this is - // really what we want to do here. - return resolve() - } else if (err.code === 'EEXIST' || err.code === 'EBUSY') { - // file already exists, so whatever - return resolve() - } else { - return reject(err) + try { + await fs.link(src, dest) + } catch (err) { + if (isWindows && err.code === 'EPERM') { + // XXX This is a really weird way to handle this situation, as it + // results in the src file being deleted even though the dest + // might not exist. Since we pretty much always write files to + // deterministic locations based on content hash, this is likely + // ok (or at worst, just ends in a future cache miss). But it would + // be worth investigating at some time in the future if this is + // really what we want to do here. + } else if (err.code === 'EEXIST' || err.code === 'EBUSY') { + // file already exists, so whatever + } else { + throw err + } + } + try { + await Promise.all([ + fs.unlink(src), + !isWindows && fs.chmod(dest, '0444'), + ]) + } catch (e) { + return pinflight('cacache-move-file:' + dest, async () => { + await fs.stat(dest).catch((err) => { + if (err.code !== 'ENOENT') { + // Something else is wrong here. Bail bail bail + throw err } - } else { - return resolve() - } - }) - }) - .then(() => { - // content should never change for any reason, so make it read-only - return Promise.all([ - unlink(src), - !isWindows && chmod(dest, '0444'), - ]) - }) - .catch(() => { - return pinflight('cacache-move-file:' + dest, () => { - return stat(dest).catch((err) => { - if (err.code !== 'ENOENT') { - // Something else is wrong here. Bail bail bail - throw err - } - // file doesn't already exist! let's try a rename -> copy fallback - // only delete if it successfully copies - return move(src, dest) - }) }) + // file doesn't already exist! let's try a rename -> copy fallback + // only delete if it successfully copies + return move(src, dest) }) + } } diff --git a/node_modules/cacache/lib/util/tmp.js b/node_modules/cacache/lib/util/tmp.js index 0a5a50eba3061..b4437cfcbeed6 100644 --- a/node_modules/cacache/lib/util/tmp.js +++ b/node_modules/cacache/lib/util/tmp.js @@ -7,15 +7,13 @@ const path = require('path') module.exports.mkdir = mktmpdir -function mktmpdir (cache, opts = {}) { +async function mktmpdir (cache, opts = {}) { const { tmpPrefix } = opts const tmpDir = path.join(cache, 'tmp') - return fs.mkdir(tmpDir, { recursive: true, owner: 'inherit' }) - .then(() => { - // do not use path.join(), it drops the trailing / if tmpPrefix is unset - const target = `${tmpDir}${path.sep}${tmpPrefix || ''}` - return fs.mkdtemp(target, { owner: 'inherit' }) - }) + await fs.mkdir(tmpDir, { recursive: true, owner: 'inherit' }) + // do not use path.join(), it drops the trailing / if tmpPrefix is unset + const target = `${tmpDir}${path.sep}${tmpPrefix || ''}` + return fs.mkdtemp(target, { owner: 'inherit' }) } module.exports.withTmp = withTmp diff --git a/node_modules/cacache/lib/verify.js b/node_modules/cacache/lib/verify.js index a39fb6ce1d1dc..52692a01d192f 100644 --- a/node_modules/cacache/lib/verify.js +++ b/node_modules/cacache/lib/verify.js @@ -5,7 +5,7 @@ const util = require('util') const pMap = require('p-map') const contentPath = require('./content/path') const fixOwner = require('./util/fix-owner') -const fs = require('fs') +const fs = require('@npmcli/fs') const fsm = require('fs-minipass') const glob = util.promisify(require('glob')) const index = require('./entry-index') @@ -18,11 +18,6 @@ const globify = pattern => pattern.split('\\').join('/') const hasOwnProperty = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key) -const stat = util.promisify(fs.stat) -const truncate = util.promisify(fs.truncate) -const writeFile = util.promisify(fs.writeFile) -const readFile = util.promisify(fs.readFile) - const verifyOpts = (opts) => ({ concurrency: 20, log: { silly () {} }, @@ -31,7 +26,7 @@ const verifyOpts = (opts) => ({ module.exports = verify -function verify (cache, opts) { +async function verify (cache, opts) { opts = verifyOpts(opts) opts.log.silly('verify', 'verifying cache at', cache) @@ -45,56 +40,47 @@ function verify (cache, opts) { markEndTime, ] - return steps - .reduce((promise, step, i) => { - const label = step.name - const start = new Date() - return promise.then((stats) => { - return step(cache, opts).then((s) => { - s && - Object.keys(s).forEach((k) => { - stats[k] = s[k] - }) - const end = new Date() - if (!stats.runTime) { - stats.runTime = {} - } - - stats.runTime[label] = end - start - return Promise.resolve(stats) - }) + const stats = {} + for (const step of steps) { + const label = step.name + const start = new Date() + const s = await step(cache, opts) + if (s) { + Object.keys(s).forEach((k) => { + stats[k] = s[k] }) - }, Promise.resolve({})) - .then((stats) => { - stats.runTime.total = stats.endTime - stats.startTime - opts.log.silly( - 'verify', - 'verification finished for', - cache, - 'in', - `${stats.runTime.total}ms` - ) - return stats - }) + } + const end = new Date() + if (!stats.runTime) { + stats.runTime = {} + } + stats.runTime[label] = end - start + } + stats.runTime.total = stats.endTime - stats.startTime + opts.log.silly( + 'verify', + 'verification finished for', + cache, + 'in', + `${stats.runTime.total}ms` + ) + return stats } -function markStartTime (cache, opts) { - return Promise.resolve({ startTime: new Date() }) +async function markStartTime (cache, opts) { + return { startTime: new Date() } } -function markEndTime (cache, opts) { - return Promise.resolve({ endTime: new Date() }) +async function markEndTime (cache, opts) { + return { endTime: new Date() } } -function fixPerms (cache, opts) { +async function fixPerms (cache, opts) { opts.log.silly('verify', 'fixing cache permissions') - return fixOwner - .mkdirfix(cache, cache) - .then(() => { - // TODO - fix file permissions too - return fixOwner.chownr(cache, cache) - }) - .then(() => null) + await fixOwner.mkdirfix(cache, cache) + // TODO - fix file permissions too + await fixOwner.chownr(cache, cache) + return null } // Implements a naive mark-and-sweep tracing garbage collector. @@ -106,7 +92,7 @@ function fixPerms (cache, opts) { // 4. If content is live, verify its checksum and delete it if it fails // 5. If content is not marked as live, rimraf it. // -function garbageCollect (cache, opts) { +async function garbageCollect (cache, opts) { opts.log.silly('verify', 'garbage collecting content') const indexStream = index.lsStream(cache) const liveContent = new Set() @@ -117,156 +103,135 @@ function garbageCollect (cache, opts) { liveContent.add(entry.integrity.toString()) }) - return new Promise((resolve, reject) => { + await new Promise((resolve, reject) => { indexStream.on('end', resolve).on('error', reject) - }).then(() => { - const contentDir = contentPath.contentDir(cache) - return glob(globify(path.join(contentDir, '**')), { - follow: false, - nodir: true, - nosort: true, - }).then((files) => { - return Promise.resolve({ - verifiedContent: 0, - reclaimedCount: 0, - reclaimedSize: 0, - badContentCount: 0, - keptSize: 0, - }).then((stats) => - pMap( - files, - (f) => { - const split = f.split(/[/\\]/) - const digest = split.slice(split.length - 3).join('') - const algo = split[split.length - 4] - const integrity = ssri.fromHex(digest, algo) - if (liveContent.has(integrity.toString())) { - return verifyContent(f, integrity).then((info) => { - if (!info.valid) { - stats.reclaimedCount++ - stats.badContentCount++ - stats.reclaimedSize += info.size - } else { - stats.verifiedContent++ - stats.keptSize += info.size - } - return stats - }) - } else { - // No entries refer to this content. We can delete. - stats.reclaimedCount++ - return stat(f).then((s) => { - return rimraf(f).then(() => { - stats.reclaimedSize += s.size - return stats - }) - }) - } - }, - { concurrency: opts.concurrency } - ).then(() => stats) - ) - }) }) -} - -function verifyContent (filepath, sri) { - return stat(filepath) - .then((s) => { - const contentInfo = { - size: s.size, - valid: true, - } - return ssri - .checkStream(new fsm.ReadStream(filepath), sri) - .catch((err) => { - if (err.code !== 'EINTEGRITY') { - throw err - } - - return rimraf(filepath).then(() => { - contentInfo.valid = false - }) - }) - .then(() => contentInfo) - }) - .catch((err) => { - if (err.code === 'ENOENT') { - return { size: 0, valid: false } + const contentDir = contentPath.contentDir(cache) + const files = await glob(globify(path.join(contentDir, '**')), { + follow: false, + nodir: true, + nosort: true, + }) + const stats = { + verifiedContent: 0, + reclaimedCount: 0, + reclaimedSize: 0, + badContentCount: 0, + keptSize: 0, + } + await pMap( + files, + async (f) => { + const split = f.split(/[/\\]/) + const digest = split.slice(split.length - 3).join('') + const algo = split[split.length - 4] + const integrity = ssri.fromHex(digest, algo) + if (liveContent.has(integrity.toString())) { + const info = await verifyContent(f, integrity) + if (!info.valid) { + stats.reclaimedCount++ + stats.badContentCount++ + stats.reclaimedSize += info.size + } else { + stats.verifiedContent++ + stats.keptSize += info.size + } + } else { + // No entries refer to this content. We can delete. + stats.reclaimedCount++ + const s = await fs.stat(f) + await rimraf(f) + stats.reclaimedSize += s.size } + return stats + }, + { concurrency: opts.concurrency } + ) + return stats +} +async function verifyContent (filepath, sri) { + const contentInfo = {} + try { + const { size } = await fs.stat(filepath) + contentInfo.size = size + contentInfo.valid = true + await ssri.checkStream(new fsm.ReadStream(filepath), sri) + } catch (err) { + if (err.code === 'ENOENT') { + return { size: 0, valid: false } + } + if (err.code !== 'EINTEGRITY') { throw err - }) + } + + await rimraf(filepath) + contentInfo.valid = false + } + return contentInfo } -function rebuildIndex (cache, opts) { +async function rebuildIndex (cache, opts) { opts.log.silly('verify', 'rebuilding index') - return index.ls(cache).then((entries) => { - const stats = { - missingContent: 0, - rejectedEntries: 0, - totalEntries: 0, - } - const buckets = {} - for (const k in entries) { - /* istanbul ignore else */ - if (hasOwnProperty(entries, k)) { - const hashed = index.hashKey(k) - const entry = entries[k] - const excluded = opts.filter && !opts.filter(entry) - excluded && stats.rejectedEntries++ - if (buckets[hashed] && !excluded) { - buckets[hashed].push(entry) - } else if (buckets[hashed] && excluded) { - // skip - } else if (excluded) { - buckets[hashed] = [] - buckets[hashed]._path = index.bucketPath(cache, k) - } else { - buckets[hashed] = [entry] - buckets[hashed]._path = index.bucketPath(cache, k) - } + const entries = await index.ls(cache) + const stats = { + missingContent: 0, + rejectedEntries: 0, + totalEntries: 0, + } + const buckets = {} + for (const k in entries) { + /* istanbul ignore else */ + if (hasOwnProperty(entries, k)) { + const hashed = index.hashKey(k) + const entry = entries[k] + const excluded = opts.filter && !opts.filter(entry) + excluded && stats.rejectedEntries++ + if (buckets[hashed] && !excluded) { + buckets[hashed].push(entry) + } else if (buckets[hashed] && excluded) { + // skip + } else if (excluded) { + buckets[hashed] = [] + buckets[hashed]._path = index.bucketPath(cache, k) + } else { + buckets[hashed] = [entry] + buckets[hashed]._path = index.bucketPath(cache, k) } } - return pMap( - Object.keys(buckets), - (key) => { - return rebuildBucket(cache, buckets[key], stats, opts) - }, - { concurrency: opts.concurrency } - ).then(() => stats) - }) + } + await pMap( + Object.keys(buckets), + (key) => { + return rebuildBucket(cache, buckets[key], stats, opts) + }, + { concurrency: opts.concurrency } + ) + return stats } -function rebuildBucket (cache, bucket, stats, opts) { - return truncate(bucket._path).then(() => { - // This needs to be serialized because cacache explicitly - // lets very racy bucket conflicts clobber each other. - return bucket.reduce((promise, entry) => { - return promise.then(() => { - const content = contentPath(cache, entry.integrity) - return stat(content) - .then(() => { - return index - .insert(cache, entry.key, entry.integrity, { - metadata: entry.metadata, - size: entry.size, - }) - .then(() => { - stats.totalEntries++ - }) - }) - .catch((err) => { - if (err.code === 'ENOENT') { - stats.rejectedEntries++ - stats.missingContent++ - return - } - throw err - }) +async function rebuildBucket (cache, bucket, stats, opts) { + await fs.truncate(bucket._path) + // This needs to be serialized because cacache explicitly + // lets very racy bucket conflicts clobber each other. + for (const entry of bucket) { + const content = contentPath(cache, entry.integrity) + try { + await fs.stat(content) + await index.insert(cache, entry.key, entry.integrity, { + metadata: entry.metadata, + size: entry.size, }) - }, Promise.resolve()) - }) + stats.totalEntries++ + } catch (err) { + if (err.code === 'ENOENT') { + stats.rejectedEntries++ + stats.missingContent++ + } else { + throw err + } + } + } } function cleanTmp (cache, opts) { @@ -278,7 +243,7 @@ function writeVerifile (cache, opts) { const verifile = path.join(cache, '_lastverified') opts.log.silly('verify', 'writing verifile to ' + verifile) try { - return writeFile(verifile, '' + +new Date()) + return fs.writeFile(verifile, `${Date.now()}`) } finally { fixOwner.chownr.sync(cache, verifile) } @@ -286,8 +251,7 @@ function writeVerifile (cache, opts) { module.exports.lastRun = lastRun -function lastRun (cache) { - return readFile(path.join(cache, '_lastverified'), 'utf8').then( - (data) => new Date(+data) - ) +async function lastRun (cache) { + const data = await fs.readFile(path.join(cache, '_lastverified'), { encoding: 'utf8' }) + return new Date(+data) } diff --git a/node_modules/cacache/package.json b/node_modules/cacache/package.json index cd7e4de5e0cba..8e54901b4562a 100644 --- a/node_modules/cacache/package.json +++ b/node_modules/cacache/package.json @@ -1,6 +1,6 @@ { "name": "cacache", - "version": "16.0.7", + "version": "16.1.0", "cache-version": { "content": "2", "index": "5" @@ -69,20 +69,16 @@ }, "devDependencies": { "@npmcli/eslint-config": "^3.0.1", - "@npmcli/template-oss": "3.4.1", + "@npmcli/template-oss": "3.4.3", "tap": "^16.0.0" }, - "tap": { - "100": true, - "test-regex": "test/[^/]*.js" - }, "engines": { "node": "^12.13.0 || ^14.15.0 || >=16.0.0" }, "templateOSS": { "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.", "windowsCI": false, - "version": "3.4.1" + "version": "3.4.3" }, "author": "GitHub Inc." } diff --git a/package-lock.json b/package-lock.json index 3ef6ead05e562..83d078741d946 100644 --- a/package-lock.json +++ b/package-lock.json @@ -96,7 +96,7 @@ "@npmcli/run-script": "^3.0.1", "abbrev": "~1.1.1", "archy": "~1.0.0", - "cacache": "^16.0.7", + "cacache": "^16.1.0", "chalk": "^4.1.2", "chownr": "^2.0.0", "cli-columns": "^4.0.0", @@ -1577,9 +1577,9 @@ } }, "node_modules/cacache": { - "version": "16.0.7", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.0.7.tgz", - "integrity": "sha512-a4zfQpp5vm4Ipdvbj+ZrPonikRhm6WBEd4zT1Yc1DXsmAxrPgDwWBLF/u/wTVXSFPIgOJ1U3ghSa2Xm4s3h28w==", + "version": "16.1.0", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.0.tgz", + "integrity": "sha512-Pk4aQkwCW82A4jGKFvcGkQFqZcMspfP9YWq9Pr87/ldDvlWf718zeI6KWCdKt/jeihu6BytHRUicJPB1K2k8EQ==", "inBundle": true, "dependencies": { "@npmcli/fs": "^2.1.0", @@ -11224,9 +11224,9 @@ } }, "cacache": { - "version": "16.0.7", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.0.7.tgz", - "integrity": "sha512-a4zfQpp5vm4Ipdvbj+ZrPonikRhm6WBEd4zT1Yc1DXsmAxrPgDwWBLF/u/wTVXSFPIgOJ1U3ghSa2Xm4s3h28w==", + "version": "16.1.0", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.0.tgz", + "integrity": "sha512-Pk4aQkwCW82A4jGKFvcGkQFqZcMspfP9YWq9Pr87/ldDvlWf718zeI6KWCdKt/jeihu6BytHRUicJPB1K2k8EQ==", "requires": { "@npmcli/fs": "^2.1.0", "@npmcli/move-file": "^2.0.0", diff --git a/package.json b/package.json index e654f94102d9d..621be8335705a 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "@npmcli/run-script": "^3.0.1", "abbrev": "~1.1.1", "archy": "~1.0.0", - "cacache": "^16.0.7", + "cacache": "^16.1.0", "chalk": "^4.1.2", "chownr": "^2.0.0", "cli-columns": "^4.0.0", From 7dd925c6887868495829d57c3e22007530c9566f Mon Sep 17 00:00:00 2001 From: Gar Date: Wed, 18 May 2022 13:27:41 -0700 Subject: [PATCH 2/4] deps: make-fetch-happen@10.1.5 * cache integrity and size events so late listeners still get them * pass expected integrity to cacache * pass integrityEmitter to cacache to avoid a redundant integrity stream * remove in-memory buffering in favor of full time streaming --- .../make-fetch-happen/lib/cache/entry.js | 138 ++++++------------ .../make-fetch-happen/lib/pipeline.js | 41 ++++++ node_modules/make-fetch-happen/lib/remote.js | 13 +- node_modules/make-fetch-happen/package.json | 8 +- package-lock.json | 18 +-- package.json | 2 +- 6 files changed, 109 insertions(+), 111 deletions(-) create mode 100644 node_modules/make-fetch-happen/lib/pipeline.js diff --git a/node_modules/make-fetch-happen/lib/cache/entry.js b/node_modules/make-fetch-happen/lib/cache/entry.js index ae2ad8c7667f2..7a7572ba030c9 100644 --- a/node_modules/make-fetch-happen/lib/cache/entry.js +++ b/node_modules/make-fetch-happen/lib/cache/entry.js @@ -1,21 +1,16 @@ const { Request, Response } = require('minipass-fetch') const Minipass = require('minipass') -const MinipassCollect = require('minipass-collect') const MinipassFlush = require('minipass-flush') -const MinipassPipeline = require('minipass-pipeline') const cacache = require('cacache') const url = require('url') +const CachingMinipassPipeline = require('../pipeline.js') const CachePolicy = require('./policy.js') const cacheKey = require('./key.js') const remote = require('../remote.js') const hasOwnProperty = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop) -// maximum amount of data we will buffer into memory -// if we'll exceed this, we switch to streaming -const MAX_MEM_SIZE = 5 * 1024 * 1024 // 5MB - // allow list for request headers that will be written to the cache index // note: we will also store any request headers // that are named in a response's vary header @@ -256,13 +251,12 @@ class CacheEntry { } const size = this.response.headers.get('content-length') - const fitsInMemory = !!size && Number(size) < MAX_MEM_SIZE - const shouldBuffer = this.options.memoize !== false && fitsInMemory const cacheOpts = { algorithms: this.options.algorithms, metadata: getMetadata(this.request, this.response, this.options), size, - memoize: fitsInMemory && this.options.memoize, + integrity: this.options.integrity, + integrityEmitter: this.response.body.hasIntegrityEmitter && this.response.body, } let body = null @@ -275,52 +269,31 @@ class CacheEntry { cacheWriteReject = reject }) - body = new MinipassPipeline(new MinipassFlush({ + body = new CachingMinipassPipeline({ events: ['integrity', 'size'] }, new MinipassFlush({ flush () { return cacheWritePromise }, })) - - let abortStream, onResume - if (shouldBuffer) { - // if the result fits in memory, use a collect stream to gather - // the response and write it to cacache while also passing it through - // to the user - onResume = () => { - const collector = new MinipassCollect.PassThrough() - abortStream = collector - collector.on('collect', (data) => { - // TODO if the cache write fails, log a warning but return the response anyway - cacache.put(this.options.cachePath, this.key, data, cacheOpts) - .then(cacheWriteResolve, cacheWriteReject) - }) - body.unshift(collector) - body.unshift(this.response.body) - } - } else { - // if it does not fit in memory, create a tee stream and use - // that to pipe to both the cache and the user simultaneously - onResume = () => { - const tee = new Minipass() - const cacheStream = cacache.put.stream(this.options.cachePath, this.key, cacheOpts) - abortStream = cacheStream - tee.pipe(cacheStream) - // TODO if the cache write fails, log a warning but return the response anyway - cacheStream.promise().then(cacheWriteResolve, cacheWriteReject) - body.unshift(tee) - body.unshift(this.response.body) - } + // this is always true since if we aren't reusing the one from the remote fetch, we + // are using the one from cacache + body.hasIntegrityEmitter = true + + const onResume = () => { + const tee = new Minipass() + const cacheStream = cacache.put.stream(this.options.cachePath, this.key, cacheOpts) + // re-emit the integrity and size events on our new response body so they can be reused + cacheStream.on('integrity', i => body.emit('integrity', i)) + cacheStream.on('size', s => body.emit('size', s)) + // stick a flag on here so downstream users will know if they can expect integrity events + tee.pipe(cacheStream) + // TODO if the cache write fails, log a warning but return the response anyway + cacheStream.promise().then(cacheWriteResolve, cacheWriteReject) + body.unshift(tee) + body.unshift(this.response.body) } body.once('resume', onResume) body.once('end', () => body.removeListener('resume', onResume)) - this.response.body.on('error', (err) => { - // the abortStream will either be a MinipassCollect if we buffer - // or a cacache write stream, either way be sure to listen for - // errors from the actual response and avoid writing data that we - // know to be invalid to the cache - abortStream.destroy(err) - }) } else { await cacache.index.insert(this.options.cachePath, this.key, null, cacheOpts) } @@ -331,7 +304,7 @@ class CacheEntry { // the header anyway this.response.headers.set('x-local-cache', encodeURIComponent(this.options.cachePath)) this.response.headers.set('x-local-cache-key', encodeURIComponent(this.key)) - this.response.headers.set('x-local-cache-mode', shouldBuffer ? 'buffer' : 'stream') + this.response.headers.set('x-local-cache-mode', 'stream') this.response.headers.set('x-local-cache-status', status) this.response.headers.set('x-local-cache-time', new Date().toISOString()) const newResponse = new Response(body, { @@ -346,9 +319,6 @@ class CacheEntry { // use the cached data to create a response and return it async respond (method, options, status) { let response - const size = Number(this.response.headers.get('content-length')) - const fitsInMemory = !!size && size < MAX_MEM_SIZE - const shouldBuffer = this.options.memoize !== false && fitsInMemory if (method === 'HEAD' || [301, 308].includes(this.response.status)) { // if the request is a HEAD, or the response is a redirect, // then the metadata in the entry already includes everything @@ -358,66 +328,44 @@ class CacheEntry { // we're responding with a full cached response, so create a body // that reads from cacache and attach it to a new Response const body = new Minipass() - const removeOnResume = () => body.removeListener('resume', onResume) - let onResume - if (shouldBuffer) { - onResume = async () => { - removeOnResume() - try { - const content = await cacache.get.byDigest( + const headers = { ...this.policy.responseHeaders() } + const onResume = () => { + const cacheStream = cacache.get.stream.byDigest( + this.options.cachePath, this.entry.integrity, { memoize: this.options.memoize } + ) + cacheStream.on('error', async (err) => { + cacheStream.pause() + if (err.code === 'EINTEGRITY') { + await cacache.rm.content( this.options.cachePath, this.entry.integrity, { memoize: this.options.memoize } ) - body.end(content) - } catch (err) { - if (err.code === 'EINTEGRITY') { - await cacache.rm.content( - this.options.cachePath, this.entry.integrity, { memoize: this.options.memoize } - ) - } - if (err.code === 'ENOENT' || err.code === 'EINTEGRITY') { - await CacheEntry.invalidate(this.request, this.options) - } - body.emit('error', err) } - } - } else { - onResume = () => { - const cacheStream = cacache.get.stream.byDigest( - this.options.cachePath, this.entry.integrity, { memoize: this.options.memoize } - ) - cacheStream.on('error', async (err) => { - cacheStream.pause() - if (err.code === 'EINTEGRITY') { - await cacache.rm.content( - this.options.cachePath, this.entry.integrity, { memoize: this.options.memoize } - ) - } - if (err.code === 'ENOENT' || err.code === 'EINTEGRITY') { - await CacheEntry.invalidate(this.request, this.options) - } - body.emit('error', err) - cacheStream.resume() - }) - cacheStream.pipe(body) - } + if (err.code === 'ENOENT' || err.code === 'EINTEGRITY') { + await CacheEntry.invalidate(this.request, this.options) + } + body.emit('error', err) + cacheStream.resume() + }) + // emit the integrity and size events based on our metadata so we're consistent + body.emit('integrity', this.entry.integrity) + body.emit('size', Number(headers['content-length'])) + cacheStream.pipe(body) } body.once('resume', onResume) - body.once('end', removeOnResume) + body.once('end', () => body.removeListener('resume', onResume)) response = new Response(body, { url: this.entry.metadata.url, counter: options.counter, status: 200, - headers: { - ...this.policy.responseHeaders(), - }, + headers, }) } response.headers.set('x-local-cache', encodeURIComponent(this.options.cachePath)) response.headers.set('x-local-cache-hash', encodeURIComponent(this.entry.integrity)) response.headers.set('x-local-cache-key', encodeURIComponent(this.key)) - response.headers.set('x-local-cache-mode', shouldBuffer ? 'buffer' : 'stream') + response.headers.set('x-local-cache-mode', 'stream') response.headers.set('x-local-cache-status', status) response.headers.set('x-local-cache-time', new Date(this.entry.metadata.time).toUTCString()) return response diff --git a/node_modules/make-fetch-happen/lib/pipeline.js b/node_modules/make-fetch-happen/lib/pipeline.js new file mode 100644 index 0000000000000..b1d221b2d0ce3 --- /dev/null +++ b/node_modules/make-fetch-happen/lib/pipeline.js @@ -0,0 +1,41 @@ +'use strict' + +const MinipassPipeline = require('minipass-pipeline') + +class CachingMinipassPipeline extends MinipassPipeline { + #events = [] + #data = new Map() + + constructor (opts, ...streams) { + // CRITICAL: do NOT pass the streams to the call to super(), this will start + // the flow of data and potentially cause the events we need to catch to emit + // before we've finished our own setup. instead we call super() with no args, + // finish our setup, and then push the streams into ourselves to start the + // data flow + super() + this.#events = opts.events + + /* istanbul ignore next - coverage disabled because this is pointless to test here */ + if (streams.length) { + this.push(...streams) + } + } + + on (event, handler) { + if (this.#events.includes(event) && this.#data.has(event)) { + return handler(...this.#data.get(event)) + } + + return super.on(event, handler) + } + + emit (event, ...data) { + if (this.#events.includes(event)) { + this.#data.set(event, data) + } + + return super.emit(event, ...data) + } +} + +module.exports = CachingMinipassPipeline diff --git a/node_modules/make-fetch-happen/lib/remote.js b/node_modules/make-fetch-happen/lib/remote.js index a8b8d2a0198d4..763fc0d48802b 100644 --- a/node_modules/make-fetch-happen/lib/remote.js +++ b/node_modules/make-fetch-happen/lib/remote.js @@ -1,9 +1,9 @@ const Minipass = require('minipass') -const MinipassPipeline = require('minipass-pipeline') const fetch = require('minipass-fetch') const promiseRetry = require('promise-retry') const ssri = require('ssri') +const CachingMinipassPipeline = require('./pipeline.js') const getAgent = require('./agent.js') const pkg = require('../package.json') @@ -53,7 +53,16 @@ const remoteFetch = (request, options) => { // we got a 200 response and the user has specified an expected // integrity value, so wrap the response in an ssri stream to verify it const integrityStream = ssri.integrityStream({ integrity: _opts.integrity }) - res = new fetch.Response(new MinipassPipeline(res.body, integrityStream), res) + const pipeline = new CachingMinipassPipeline({ + events: ['integrity', 'size'], + }, res.body, integrityStream) + // we also propagate the integrity and size events out to the pipeline so we can use + // this new response body as an integrityEmitter for cacache + integrityStream.on('integrity', i => pipeline.emit('integrity', i)) + integrityStream.on('size', s => pipeline.emit('size', s)) + res = new fetch.Response(pipeline, res) + // set an explicit flag so we know if our response body will emit integrity and size + res.body.hasIntegrityEmitter = true } res.headers.set('x-fetch-attempts', attemptNum) diff --git a/node_modules/make-fetch-happen/package.json b/node_modules/make-fetch-happen/package.json index e1e8e975187b5..2e6153b99fa0d 100644 --- a/node_modules/make-fetch-happen/package.json +++ b/node_modules/make-fetch-happen/package.json @@ -1,6 +1,6 @@ { "name": "make-fetch-happen", - "version": "10.1.3", + "version": "10.1.5", "description": "Opinionated, caching, retrying fetch client", "main": "lib/index.js", "files": [ @@ -37,7 +37,7 @@ "license": "ISC", "dependencies": { "agentkeepalive": "^4.2.1", - "cacache": "^16.0.2", + "cacache": "^16.1.0", "http-cache-semantics": "^4.1.0", "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.0", @@ -55,7 +55,7 @@ }, "devDependencies": { "@npmcli/eslint-config": "^3.0.1", - "@npmcli/template-oss": "3.4.3", + "@npmcli/template-oss": "3.5.0", "mkdirp": "^1.0.4", "nock": "^13.2.4", "rimraf": "^3.0.2", @@ -73,6 +73,6 @@ }, "templateOSS": { "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.", - "version": "3.4.3" + "version": "3.5.0" } } diff --git a/package-lock.json b/package-lock.json index 83d078741d946..3154dc55452cc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -121,7 +121,7 @@ "libnpmsearch": "^5.0.2", "libnpmteam": "^4.0.2", "libnpmversion": "^3.0.1", - "make-fetch-happen": "^10.1.3", + "make-fetch-happen": "^10.1.5", "minipass": "^3.1.6", "minipass-pipeline": "^1.2.4", "mkdirp": "^1.0.4", @@ -4609,13 +4609,13 @@ "peer": true }, "node_modules/make-fetch-happen": { - "version": "10.1.3", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.1.3.tgz", - "integrity": "sha512-s/UjmGjUHn9m52cctFhN2ITObbT+axoUhgeir8xGrOlPbKDyJsdhQzb8PGncPQQ28uduHybFJ6Iumy2OZnreXw==", + "version": "10.1.5", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.1.5.tgz", + "integrity": "sha512-mucOj2H0Jn/ax7H9K9T1bf0p1nn/mBFa551Os7ed9xRfLEx20aZhZeLslmRYfAaAqXZUGipcs+m5KOKvOH0XKA==", "inBundle": true, "dependencies": { "agentkeepalive": "^4.2.1", - "cacache": "^16.0.2", + "cacache": "^16.1.0", "http-cache-semantics": "^4.1.0", "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.0", @@ -13269,12 +13269,12 @@ "peer": true }, "make-fetch-happen": { - "version": "10.1.3", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.1.3.tgz", - "integrity": "sha512-s/UjmGjUHn9m52cctFhN2ITObbT+axoUhgeir8xGrOlPbKDyJsdhQzb8PGncPQQ28uduHybFJ6Iumy2OZnreXw==", + "version": "10.1.5", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.1.5.tgz", + "integrity": "sha512-mucOj2H0Jn/ax7H9K9T1bf0p1nn/mBFa551Os7ed9xRfLEx20aZhZeLslmRYfAaAqXZUGipcs+m5KOKvOH0XKA==", "requires": { "agentkeepalive": "^4.2.1", - "cacache": "^16.0.2", + "cacache": "^16.1.0", "http-cache-semantics": "^4.1.0", "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.0", diff --git a/package.json b/package.json index 621be8335705a..a2fd510a77c89 100644 --- a/package.json +++ b/package.json @@ -90,7 +90,7 @@ "libnpmsearch": "^5.0.2", "libnpmteam": "^4.0.2", "libnpmversion": "^3.0.1", - "make-fetch-happen": "^10.1.3", + "make-fetch-happen": "^10.1.5", "minipass": "^3.1.6", "minipass-pipeline": "^1.2.4", "mkdirp": "^1.0.4", From 75ca2c592dc69177534a3b65edc189e84578e9f5 Mon Sep 17 00:00:00 2001 From: Gar Date: Wed, 18 May 2022 13:29:21 -0700 Subject: [PATCH 3/4] deps: pacote@13.4.1 * pass prefix and workspaces to npm-packlist * add verifySignatures to registry.manifest --- node_modules/pacote/lib/dir.js | 4 +- node_modules/pacote/lib/registry.js | 191 +++++++++++++++++----------- node_modules/pacote/package.json | 6 +- package-lock.json | 14 +- package.json | 2 +- 5 files changed, 128 insertions(+), 89 deletions(-) diff --git a/node_modules/pacote/lib/dir.js b/node_modules/pacote/lib/dir.js index 598b029f7ad48..502379810a006 100644 --- a/node_modules/pacote/lib/dir.js +++ b/node_modules/pacote/lib/dir.js @@ -63,10 +63,12 @@ class DirFetcher extends Fetcher { stream.resolved = this.resolved stream.integrity = this.integrity + const { prefix, workspaces } = this.opts + // run the prepare script, get the list of files, and tar it up // pipe to the stream, and proxy errors the chain. this[_prepareDir]() - .then(() => packlist({ path: this.resolved })) + .then(() => packlist({ path: this.resolved, prefix, workspaces })) .then(files => tar.c(tarCreateOptions(this.package), files) .on('error', er => stream.emit('error', er)).pipe(stream)) .catch(er => stream.emit('error', er)) diff --git a/node_modules/pacote/lib/registry.js b/node_modules/pacote/lib/registry.js index e8ca16f596d6f..2eb37bceccebc 100644 --- a/node_modules/pacote/lib/registry.js +++ b/node_modules/pacote/lib/registry.js @@ -7,6 +7,7 @@ const npa = require('npm-package-arg') const rpj = require('read-package-json-fast') const pickManifest = require('npm-pick-manifest') const ssri = require('ssri') +const crypto = require('crypto') // Corgis are cute. 🐕🐶 const corgiDoc = 'application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*' @@ -14,8 +15,6 @@ const fullDoc = 'application/json' const fetch = require('npm-registry-fetch') -// TODO: memoize reg requests, so we don't even have to check cache - const _headers = Symbol('_headers') class RegistryFetcher extends Fetcher { constructor (spec, opts) { @@ -39,28 +38,30 @@ class RegistryFetcher extends Fetcher { this.packumentUrl = removeTrailingSlashes(this.registry) + '/' + this.spec.escapedName + const parsed = new URL(this.registry) + const regKey = `//${parsed.host}${parsed.pathname}` + // unlike the nerf-darted auth keys, this one does *not* allow a mismatch + // of trailing slashes. It must match exactly. + if (this.opts[`${regKey}:_keys`]) { + this.registryKeys = this.opts[`${regKey}:_keys`] + } + // XXX pacote <=9 has some logic to ignore opts.resolved if // the resolved URL doesn't go to the same registry. // Consider reproducing that here, to throw away this.resolved // in that case. } - resolve () { - if (this.resolved) { - return Promise.resolve(this.resolved) - } - - // fetching the manifest sets resolved and (usually) integrity - return this.manifest().then(() => { - if (this.resolved) { - return this.resolved - } - + async resolve () { + // fetching the manifest sets resolved and (if present) integrity + await this.manifest() + if (!this.resolved) { throw Object.assign( new Error('Invalid package manifest: no `dist.tarball` field'), { package: this.spec.toString() } ) - }) + } + return this.resolved } [_headers] () { @@ -87,91 +88,127 @@ class RegistryFetcher extends Fetcher { // npm-registry-fetch the packument // set the appropriate header for corgis if fullMetadata isn't set // return the res.json() promise - const p = fetch(this.packumentUrl, { - ...this.opts, - headers: this[_headers](), - spec: this.spec, - // never check integrity for packuments themselves - integrity: null, - }).then(res => res.json().then(packument => { + try { + const res = await fetch(this.packumentUrl, { + ...this.opts, + headers: this[_headers](), + spec: this.spec, + // never check integrity for packuments themselves + integrity: null, + }) + const packument = await res.json() packument._cached = res.headers.has('x-local-cache') packument._contentLength = +res.headers.get('content-length') if (this.packumentCache) { this.packumentCache.set(this.packumentUrl, packument) } return packument - })).catch(er => { + } catch (err) { if (this.packumentCache) { this.packumentCache.delete(this.packumentUrl) } - if (er.code === 'E404' && !this.fullMetadata) { - // possible that corgis are not supported by this registry - this.fullMetadata = true - return this.packument() + if (err.code !== 'E404' || this.fullMetadata) { + throw err } - throw er - }) - if (this.packumentCache) { - this.packumentCache.set(this.packumentUrl, p) + // possible that corgis are not supported by this registry + this.fullMetadata = true + return this.packument() } - return p } - manifest () { + async manifest () { if (this.package) { - return Promise.resolve(this.package) + return this.package } - return this.packument() - .then(packument => pickManifest(packument, this.spec.fetchSpec, { - ...this.opts, - defaultTag: this.defaultTag, - before: this.before, - }) /* XXX add ETARGET and E403 revalidation of cached packuments here */) - .then(mani => { - // add _resolved and _integrity from dist object - const { dist } = mani - if (dist) { - this.resolved = mani._resolved = dist.tarball - mani._from = this.from - const distIntegrity = dist.integrity ? ssri.parse(dist.integrity) - : dist.shasum ? ssri.fromHex(dist.shasum, 'sha1', { ...this.opts }) - : null - if (distIntegrity) { - if (!this.integrity) { - this.integrity = distIntegrity - } else if (!this.integrity.match(distIntegrity)) { - // only bork if they have algos in common. - // otherwise we end up breaking if we have saved a sha512 - // previously for the tarball, but the manifest only - // provides a sha1, which is possible for older publishes. - // Otherwise, this is almost certainly a case of holding it - // wrong, and will result in weird or insecure behavior - // later on when building package tree. - for (const algo of Object.keys(this.integrity)) { - if (distIntegrity[algo]) { - throw Object.assign(new Error( - `Integrity checksum failed when using ${algo}: ` + - `wanted ${this.integrity} but got ${distIntegrity}.` - ), { code: 'EINTEGRITY' }) - } - } - // made it this far, the integrity is worthwhile. accept it. - // the setter here will take care of merging it into what we - // already had. - this.integrity = distIntegrity + const packument = await this.packument() + const mani = await pickManifest(packument, this.spec.fetchSpec, { + ...this.opts, + defaultTag: this.defaultTag, + before: this.before, + }) + /* XXX add ETARGET and E403 revalidation of cached packuments here */ + + // add _resolved and _integrity from dist object + const { dist } = mani + if (dist) { + this.resolved = mani._resolved = dist.tarball + mani._from = this.from + const distIntegrity = dist.integrity ? ssri.parse(dist.integrity) + : dist.shasum ? ssri.fromHex(dist.shasum, 'sha1', { ...this.opts }) + : null + if (distIntegrity) { + if (this.integrity && !this.integrity.match(distIntegrity)) { + // only bork if they have algos in common. + // otherwise we end up breaking if we have saved a sha512 + // previously for the tarball, but the manifest only + // provides a sha1, which is possible for older publishes. + // Otherwise, this is almost certainly a case of holding it + // wrong, and will result in weird or insecure behavior + // later on when building package tree. + for (const algo of Object.keys(this.integrity)) { + if (distIntegrity[algo]) { + throw Object.assign(new Error( + `Integrity checksum failed when using ${algo}: ` + + `wanted ${this.integrity} but got ${distIntegrity}.` + ), { code: 'EINTEGRITY' }) } } } - if (this.integrity) { - mani._integrity = String(this.integrity) - if (dist.signatures) { + // made it this far, the integrity is worthwhile. accept it. + // the setter here will take care of merging it into what we already + // had. + this.integrity = distIntegrity + } + } + if (this.integrity) { + mani._integrity = String(this.integrity) + if (dist.signatures) { + if (this.opts.verifySignatures) { + if (this.registryKeys) { + // validate and throw on error, then set _signatures + const message = `${mani._id}:${mani._integrity}` + for (const signature of dist.signatures) { + const publicKey = this.registryKeys.filter(key => (key.keyid === signature.keyid))[0] + if (!publicKey) { + throw Object.assign(new Error( + `${mani._id} has a signature with keyid: ${signature.keyid} ` + + 'but no corresponding public key can be found.' + ), { code: 'EMISSINGSIGNATUREKEY' }) + } + const validPublicKey = + !publicKey.expires || (Date.parse(publicKey.expires) > Date.now()) + if (!validPublicKey) { + throw Object.assign(new Error( + `${mani._id} has a signature with keyid: ${signature.keyid} ` + + `but the corresponding public key has expired ${publicKey.expires}` + ), { code: 'EEXPIREDSIGNATUREKEY' }) + } + const verifier = crypto.createVerify('SHA256') + verifier.write(message) + verifier.end() + const valid = verifier.verify( + publicKey.pemkey, + signature.sig, + 'base64' + ) + if (!valid) { + throw Object.assign(new Error( + 'Integrity checksum signature failed: ' + + `key ${publicKey.keyid} signature ${signature.sig}` + ), { code: 'EINTEGRITYSIGNATURE' }) + } + } mani._signatures = dist.signatures } + // if no keys, don't set _signatures + } else { + mani._signatures = dist.signatures } - this.package = rpj.normalize(mani) - return this.package - }) + } + } + this.package = rpj.normalize(mani) + return this.package } [_tarballFromResolved] () { diff --git a/node_modules/pacote/package.json b/node_modules/pacote/package.json index 2f4323c4414ed..f1a86938ff772 100644 --- a/node_modules/pacote/package.json +++ b/node_modules/pacote/package.json @@ -1,6 +1,6 @@ { "name": "pacote", - "version": "13.3.0", + "version": "13.4.1", "description": "JavaScript package downloader", "author": "GitHub Inc.", "bin": { @@ -26,7 +26,7 @@ }, "devDependencies": { "@npmcli/eslint-config": "^3.0.1", - "@npmcli/template-oss": "3.4.3", + "@npmcli/template-oss": "3.5.0", "hosted-git-info": "^5.0.0", "mutate-fs": "^2.1.1", "nock": "^13.2.4", @@ -74,7 +74,7 @@ }, "templateOSS": { "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.", - "version": "3.4.3", + "version": "3.5.0", "windowsCI": false } } diff --git a/package-lock.json b/package-lock.json index 3154dc55452cc..b5a784cb3130f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -138,7 +138,7 @@ "npm-user-validate": "^1.0.1", "npmlog": "^6.0.2", "opener": "^1.5.2", - "pacote": "^13.3.0", + "pacote": "^13.4.1", "parse-conflict-json": "^2.0.2", "proc-log": "^2.0.1", "qrcode-terminal": "^0.12.0", @@ -5547,9 +5547,9 @@ } }, "node_modules/pacote": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/pacote/-/pacote-13.3.0.tgz", - "integrity": "sha512-auhJAUlfC2TALo6I0s1vFoPvVFgWGx+uz/PnIojTTgkGwlK3Np8sGJ0ghfFhiuzJXTZoTycMLk8uLskdntPbDw==", + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-13.4.1.tgz", + "integrity": "sha512-FqlSWlD8n+ejCE17GF/lf0yasztMGFl4UFzYQk5njaK/qPPWfVDWnfQwqmqeXObWLSmIBew+O+CFD24vxkVDjg==", "inBundle": true, "dependencies": { "@npmcli/git": "^3.0.0", @@ -13877,9 +13877,9 @@ } }, "pacote": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/pacote/-/pacote-13.3.0.tgz", - "integrity": "sha512-auhJAUlfC2TALo6I0s1vFoPvVFgWGx+uz/PnIojTTgkGwlK3Np8sGJ0ghfFhiuzJXTZoTycMLk8uLskdntPbDw==", + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-13.4.1.tgz", + "integrity": "sha512-FqlSWlD8n+ejCE17GF/lf0yasztMGFl4UFzYQk5njaK/qPPWfVDWnfQwqmqeXObWLSmIBew+O+CFD24vxkVDjg==", "requires": { "@npmcli/git": "^3.0.0", "@npmcli/installed-package-contents": "^1.0.7", diff --git a/package.json b/package.json index a2fd510a77c89..1b0027ba5bb62 100644 --- a/package.json +++ b/package.json @@ -107,7 +107,7 @@ "npm-user-validate": "^1.0.1", "npmlog": "^6.0.2", "opener": "^1.5.2", - "pacote": "^13.3.0", + "pacote": "^13.4.1", "parse-conflict-json": "^2.0.2", "proc-log": "^2.0.1", "qrcode-terminal": "^0.12.0", From 5d365688c57555d5fa8cc15e79572036fcee3a1d Mon Sep 17 00:00:00 2001 From: Gar Date: Thu, 19 May 2022 11:04:13 -0700 Subject: [PATCH 4/4] deps: ssri@9.0.1 * store emitted events and re-emit them for late listeners --- node_modules/ssri/lib/index.js | 27 ++++++++++++++++++++++++++- node_modules/ssri/package.json | 6 +++--- package-lock.json | 14 +++++++------- package.json | 2 +- 4 files changed, 37 insertions(+), 12 deletions(-) diff --git a/node_modules/ssri/lib/index.js b/node_modules/ssri/lib/index.js index e2732fd072b12..1443137cbc708 100644 --- a/node_modules/ssri/lib/index.js +++ b/node_modules/ssri/lib/index.js @@ -30,6 +30,10 @@ const getOptString = options => !options || !options.length const _onEnd = Symbol('_onEnd') const _getOptions = Symbol('_getOptions') +const _emittedSize = Symbol('_emittedSize') +const _emittedIntegrity = Symbol('_emittedIntegrity') +const _emittedVerified = Symbol('_emittedVerified') + class IntegrityStream extends MiniPass { constructor (opts) { super() @@ -63,6 +67,22 @@ class IntegrityStream extends MiniPass { this.optString = getOptString(options) } + on (ev, handler) { + if (ev === 'size' && this[_emittedSize]) { + return handler(this[_emittedSize]) + } + + if (ev === 'integrity' && this[_emittedIntegrity]) { + return handler(this[_emittedIntegrity]) + } + + if (ev === 'verified' && this[_emittedVerified]) { + return handler(this[_emittedVerified]) + } + + return super.on(ev, handler) + } + emit (ev, data) { if (ev === 'end') { this[_onEnd]() @@ -103,9 +123,14 @@ class IntegrityStream extends MiniPass { err.sri = this.sri this.emit('error', err) } else { + this[_emittedSize] = this.size this.emit('size', this.size) + this[_emittedIntegrity] = newSri this.emit('integrity', newSri) - match && this.emit('verified', match) + if (match) { + this[_emittedVerified] = match + this.emit('verified', match) + } } } } diff --git a/node_modules/ssri/package.json b/node_modules/ssri/package.json index 84448afc3cf04..91c1f919788cd 100644 --- a/node_modules/ssri/package.json +++ b/node_modules/ssri/package.json @@ -1,6 +1,6 @@ { "name": "ssri", - "version": "9.0.0", + "version": "9.0.1", "description": "Standard Subresource Integrity library -- parses, serializes, generates, and verifies integrity metadata according to the SRI spec.", "main": "lib/index.js", "files": [ @@ -50,7 +50,7 @@ }, "devDependencies": { "@npmcli/eslint-config": "^3.0.1", - "@npmcli/template-oss": "3.2.2", + "@npmcli/template-oss": "3.5.0", "tap": "^16.0.1" }, "engines": { @@ -58,6 +58,6 @@ }, "templateOSS": { "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.", - "version": "3.2.2" + "version": "3.5.0" } } diff --git a/package-lock.json b/package-lock.json index b5a784cb3130f..a06dbc49ea5a9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -148,7 +148,7 @@ "readdir-scoped-modules": "^1.1.0", "rimraf": "^3.0.2", "semver": "^7.3.7", - "ssri": "^9.0.0", + "ssri": "^9.0.1", "tar": "^6.1.11", "text-table": "~0.2.0", "tiny-relative-date": "^1.3.0", @@ -6793,9 +6793,9 @@ } }, "node_modules/ssri": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.0.tgz", - "integrity": "sha512-Y1Z6J8UYnexKFN1R/hxUaYoY2LVdKEzziPmVAFKiKX8fiwvCJTVzn/xYE9TEWod5OVyNfIHHuVfIEuBClL/uJQ==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz", + "integrity": "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==", "inBundle": true, "dependencies": { "minipass": "^3.1.1" @@ -14720,9 +14720,9 @@ } }, "ssri": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.0.tgz", - "integrity": "sha512-Y1Z6J8UYnexKFN1R/hxUaYoY2LVdKEzziPmVAFKiKX8fiwvCJTVzn/xYE9TEWod5OVyNfIHHuVfIEuBClL/uJQ==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz", + "integrity": "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==", "requires": { "minipass": "^3.1.1" } diff --git a/package.json b/package.json index 1b0027ba5bb62..fef38f6efddcc 100644 --- a/package.json +++ b/package.json @@ -117,7 +117,7 @@ "readdir-scoped-modules": "^1.1.0", "rimraf": "^3.0.2", "semver": "^7.3.7", - "ssri": "^9.0.0", + "ssri": "^9.0.1", "tar": "^6.1.11", "text-table": "~0.2.0", "tiny-relative-date": "^1.3.0",