diff options
Diffstat (limited to 'deps/npm/node_modules/pacote/lib')
12 files changed, 344 insertions, 147 deletions
diff --git a/deps/npm/node_modules/pacote/lib/extract-stream.js b/deps/npm/node_modules/pacote/lib/extract-stream.js index b3c720b07f..b7694876ba 100644 --- a/deps/npm/node_modules/pacote/lib/extract-stream.js +++ b/deps/npm/node_modules/pacote/lib/extract-stream.js @@ -1,5 +1,6 @@ 'use strict' +const PassThrough = require('stream').PassThrough const path = require('path') const tar = require('tar') @@ -10,7 +11,29 @@ function computeMode (fileMode, optMode, umask) { return (fileMode | optMode) & ~(umask || 0) } -function extractStream (dest, opts) { +function pkgJsonTransform (spec, opts) { + return entry => { + if (entry.path === 'package.json') { + const transformed = new PassThrough() + let str = '' + entry.on('end', () => transformed.end(str.replace( + /}\s*$/, + `\n,"_resolved": ${ + JSON.stringify(opts.resolved || '') + }\n,"_integrity": ${ + JSON.stringify(opts.integrity || '') + }\n,"_from": ${ + JSON.stringify(spec.toString()) + }\n}` + ))) + entry.on('error', e => transformed.emit('error')) + entry.on('data', d => { str += d }) + return transformed + } + } +} + +function extractStream (spec, dest, opts) { opts = opts || {} const sawIgnores = new Set() return tar.x({ @@ -20,6 +43,7 @@ function extractStream (dest, opts) { onwarn: msg => opts.log && opts.log.warn('tar', msg), uid: opts.uid, gid: opts.gid, + transform: opts.resolved && pkgJsonTransform(spec, opts), onentry (entry) { if (entry.type.toLowerCase() === 'file') { entry.mode = computeMode(entry.mode, opts.fmode, opts.umask) diff --git a/deps/npm/node_modules/pacote/lib/fetchers/file.js b/deps/npm/node_modules/pacote/lib/fetchers/file.js index 48bec0c2d8..5688cd1bdd 100644 --- a/deps/npm/node_modules/pacote/lib/fetchers/file.js +++ b/deps/npm/node_modules/pacote/lib/fetchers/file.js @@ -52,13 +52,13 @@ Fetcher.impl(fetchFile, { } else { let integrity const cacheWriter = !opts.cache - ? BB.resolve(null) - : (pipe( - fs.createReadStream(src), - cacache.put.stream(opts.cache, `pacote:tarball:${src}`, { - integrity: opts.integrity - }).on('integrity', d => { integrity = d }) - )) + ? BB.resolve(null) + : (pipe( + fs.createReadStream(src), + cacache.put.stream(opts.cache, `pacote:tarball:${src}`, { + integrity: opts.integrity + }).on('integrity', d => { integrity = d }) + )) return cacheWriter.then(() => { if (integrity) { stream.emit('integrity', integrity) } return pipe(fs.createReadStream(src), stream) diff --git a/deps/npm/node_modules/pacote/lib/fetchers/git.js b/deps/npm/node_modules/pacote/lib/fetchers/git.js index 11b5695255..6ce966ae5d 100644 --- a/deps/npm/node_modules/pacote/lib/fetchers/git.js +++ b/deps/npm/node_modules/pacote/lib/fetchers/git.js @@ -135,16 +135,16 @@ function resolve (url, spec, name, opts) { const isSemver = !!spec.gitRange return git.revs(url, opts).then(remoteRefs => { return isSemver - ? pickManifest({ - versions: remoteRefs.versions, - 'dist-tags': remoteRefs['dist-tags'], - name: name - }, spec.gitRange, opts) - : remoteRefs - ? BB.resolve( - remoteRefs.refs[spec.gitCommittish] || remoteRefs.refs[remoteRefs.shas[spec.gitCommittish]] - ) - : null + ? pickManifest({ + versions: remoteRefs.versions, + 'dist-tags': remoteRefs['dist-tags'], + name: name + }, spec.gitRange, opts) + : remoteRefs + ? BB.resolve( + remoteRefs.refs[spec.gitCommittish] || remoteRefs.refs[remoteRefs.shas[spec.gitCommittish]] + ) + : null }) } diff --git a/deps/npm/node_modules/pacote/lib/fetchers/registry/fetch.js b/deps/npm/node_modules/pacote/lib/fetchers/registry/fetch.js index a947ccea55..3a2a4a5a77 100644 --- a/deps/npm/node_modules/pacote/lib/fetchers/registry/fetch.js +++ b/deps/npm/node_modules/pacote/lib/fetchers/registry/fetch.js @@ -68,12 +68,12 @@ function logRequest (uri, res, startTime, opts) { function getCacheMode (opts) { return opts.offline - ? 'only-if-cached' - : opts.preferOffline - ? 'force-cache' - : opts.preferOnline - ? 'no-cache' - : 'default' + ? 'only-if-cached' + : opts.preferOffline + ? 'force-cache' + : opts.preferOnline + ? 'no-cache' + : 'default' } function getHeaders (uri, registry, opts) { diff --git a/deps/npm/node_modules/pacote/lib/fetchers/registry/manifest.js b/deps/npm/node_modules/pacote/lib/fetchers/registry/manifest.js index 4488ddb4c7..4e5a8010e2 100644 --- a/deps/npm/node_modules/pacote/lib/fetchers/registry/manifest.js +++ b/deps/npm/node_modules/pacote/lib/fetchers/registry/manifest.js @@ -28,8 +28,8 @@ function manifest (spec, opts) { function metadataUrl (registry, name) { const normalized = registry.slice(-1) !== '/' - ? registry + '/' - : registry + ? registry + '/' + : registry return url.resolve(normalized, name) } diff --git a/deps/npm/node_modules/pacote/lib/fetchers/registry/tarball.js b/deps/npm/node_modules/pacote/lib/fetchers/registry/tarball.js index 2c63872e7f..89158d3929 100644 --- a/deps/npm/node_modules/pacote/lib/fetchers/registry/tarball.js +++ b/deps/npm/node_modules/pacote/lib/fetchers/registry/tarball.js @@ -7,22 +7,40 @@ const manifest = require('./manifest') const optCheck = require('../../util/opt-check') const PassThrough = require('stream').PassThrough const pickRegistry = require('./pick-registry') -const pipe = BB.promisify(require('mississippi').pipe) const ssri = require('ssri') const url = require('url') module.exports = tarball function tarball (spec, opts) { opts = optCheck(opts) + const registry = pickRegistry(spec, opts) const stream = new PassThrough() - manifest(spec, opts).then(manifest => { - stream.emit('manifest', manifest) - return pipe( - fromManifest(manifest, spec, opts).on( - 'integrity', i => stream.emit('integrity', i) - ), - stream + let mani + if ( + opts.resolved && + // spec.type === 'version' && + opts.resolved.indexOf(registry) === 0 + ) { + // fakeChild is a shortcut to avoid looking up a manifest! + mani = BB.resolve({ + name: spec.name, + version: spec.fetchSpec, + _integrity: opts.integrity, + _resolved: opts.resolved, + _fakeChild: true + }) + } else { + // We can't trust opts.resolved if it's going to a separate host. + mani = manifest(spec, opts) + } + + mani.then(mani => { + !mani._fakeChild && stream.emit('manifest', mani) + const fetchStream = fromManifest(mani, spec, opts).on( + 'integrity', i => stream.emit('integrity', i) ) + fetchStream.on('error', err => stream.emit('error', err)) + fetchStream.pipe(stream) }).catch(err => stream.emit('error', err)) return stream } @@ -33,45 +51,43 @@ function fromManifest (manifest, spec, opts) { opts.scope = spec.scope || opts.scope const stream = new PassThrough() const registry = pickRegistry(spec, opts) - const uri = getTarballUrl(registry, manifest) + const uri = getTarballUrl(spec, registry, manifest, opts) fetch(uri, registry, Object.assign({ headers: { 'pacote-req-type': 'tarball', - 'pacote-pkg-id': `registry:${ - spec.type === 'remote' - ? spec - : `${manifest.name}@${manifest.version}` - }` + 'pacote-pkg-id': `registry:${manifest.name}@${uri}` }, integrity: manifest._integrity, algorithms: [ manifest._integrity - ? ssri.parse(manifest._integrity).pickAlgorithm() - : 'sha1' + ? ssri.parse(manifest._integrity).pickAlgorithm() + : 'sha1' ], spec - }, opts)).then(res => { - const hash = res.headers.get('x-local-cache-hash') - if (hash) { - stream.emit('integrity', decodeURIComponent(hash)) - } - res.body.on('error', err => stream.emit('error', err)) - res.body.pipe(stream) - }).catch(err => stream.emit('error', err)) + }, opts)) + .then(res => { + const hash = res.headers.get('x-local-cache-hash') + if (hash) { + stream.emit('integrity', decodeURIComponent(hash)) + } + res.body.on('error', err => stream.emit('error', err)) + res.body.pipe(stream) + }) + .catch(err => stream.emit('error', err)) return stream } -function getTarballUrl (registry, manifest) { +function getTarballUrl (spec, registry, mani, opts) { + const reg = url.parse(registry) + const tarball = url.parse(mani._resolved) // https://github.com/npm/npm/pull/9471 // - // TL;DR: Some alternative registries host tarballs on http and packuments on - // https, and vice-versa. There's also a case where people who can't use SSL - // to access the npm registry, for example, might use - // `--registry=http://registry.npmjs.org/`. In this case, we need to rewrite - // `tarball` to match the protocol. + // TL;DR: Some alternative registries host tarballs on http and packuments + // on https, and vice-versa. There's also a case where people who can't use + // SSL to access the npm registry, for example, might use + // `--registry=http://registry.npmjs.org/`. In this case, we need to + // rewrite `tarball` to match the protocol. // - const reg = url.parse(registry) - const tarball = url.parse(manifest._resolved) if (reg.hostname === tarball.hostname && reg.protocol !== tarball.protocol) { tarball.protocol = reg.protocol // Ports might be same host different protocol! diff --git a/deps/npm/node_modules/pacote/lib/finalize-manifest.js b/deps/npm/node_modules/pacote/lib/finalize-manifest.js index 86b273b215..a5ec7a7348 100644 --- a/deps/npm/node_modules/pacote/lib/finalize-manifest.js +++ b/deps/npm/node_modules/pacote/lib/finalize-manifest.js @@ -38,8 +38,8 @@ function finalizeManifest (pkg, spec, opts) { opts = optCheck(opts) const cachedManifest = (opts.cache && key && !opts.preferOnline && !opts.fullMetadata) - ? cacache.get.info(opts.cache, key, opts) - : BB.resolve(null) + ? cacache.get.info(opts.cache, key, opts) + : BB.resolve(null) return cachedManifest.then(cached => { if (cached && cached.metadata.manifest) { @@ -47,8 +47,8 @@ function finalizeManifest (pkg, spec, opts) { } else { return tarballedProps(pkg, spec, opts).then(props => { return pkg && pkg.name - ? new Manifest(pkg, props, opts.fullMetadata) - : new Manifest(props, null, opts.fullMetadata) + ? new Manifest(pkg, props, opts.fullMetadata) + : new Manifest(props, null, opts.fullMetadata) }).then(manifest => { const cacheKey = key || finalKey(manifest, spec) if (!opts.cache || !cacheKey) { @@ -169,8 +169,8 @@ function tarballedProps (pkg, spec, opts) { // to add to bin if (paths && paths.length) { const dirBin = mani - ? (mani && mani.directories && mani.directories.bin) - : (pkg && pkg.directories && pkg.directories.bin) + ? (mani && mani.directories && mani.directories.bin) + : (pkg && pkg.directories && pkg.directories.bin) if (dirBin) { extraProps.bin = {} paths.forEach(filePath => { diff --git a/deps/npm/node_modules/pacote/lib/util/git.js b/deps/npm/node_modules/pacote/lib/util/git.js index a6162ceeba..b854208ef9 100644 --- a/deps/npm/node_modules/pacote/lib/util/git.js +++ b/deps/npm/node_modules/pacote/lib/util/git.js @@ -12,8 +12,10 @@ const optCheck = require('./opt-check') const osenv = require('osenv') const path = require('path') const pinflight = require('promise-inflight') +const promiseRetry = require('promise-retry') const uniqueFilename = require('unique-filename') const which = BB.promisify(require('which')) +const semver = require('semver') const GOOD_ENV_VARS = new Set([ 'GIT_ASKPASS', @@ -25,6 +27,23 @@ const GOOD_ENV_VARS = new Set([ 'GIT_SSL_NO_VERIFY' ]) +const GIT_TRANSIENT_ERRORS = [ + 'remote error: Internal Server Error', + 'The remote end hung up unexpectedly', + 'Connection timed out', + 'Operation timed out', + 'Failed to connect to .* Timed out', + 'Connection reset by peer', + 'SSL_ERROR_SYSCALL', + 'The requested URL returned error: 503' +].join('|') + +const GIT_TRANSIENT_ERROR_RE = new RegExp(GIT_TRANSIENT_ERRORS) + +function shouldRetry (error) { + return GIT_TRANSIENT_ERROR_RE.test(error) +} + const GIT_ = 'GIT_' let GITENV function gitEnv () { @@ -113,57 +132,51 @@ function revs (repo, opts) { return pinflight(`ls-remote:${repo}`, () => { return spawnGit(['ls-remote', '-h', '-t', repo], { env: gitEnv() - }, opts).then(child => { - let stdout = '' - let stderr = '' - child.stdout.on('data', d => { stdout += d }) - child.stderr.on('data', d => { stderr += d }) - return finished(child).catch(err => { - err.message = `Error while executing:\n${GITPATH} ls-remote -h -t ${repo}\n\n${stderr}\n${err.message}` - throw err - }).then(() => { - return stdout.split('\n').reduce((revs, line) => { - const split = line.split(/\s+/, 2) - if (split.length < 2) { return revs } - const sha = split[0].trim() - const ref = split[1].trim().match(/(?:refs\/[^/]+\/)?(.*)/)[1] - if (!ref) { return revs } // ??? - if (ref.endsWith(CARET_BRACES)) { return revs } // refs/tags/x^{} crap - const type = refType(line) - const doc = {sha, ref, type} - - revs.refs[ref] = doc - // We can check out shallow clones on specific SHAs if we have a ref - if (revs.shas[sha]) { - revs.shas[sha].push(ref) - } else { - revs.shas[sha] = [ref] - } + }, opts).then((stdout) => { + return stdout.split('\n').reduce((revs, line) => { + const split = line.split(/\s+/, 2) + if (split.length < 2) { return revs } + const sha = split[0].trim() + const ref = split[1].trim().match(/(?:refs\/[^/]+\/)?(.*)/)[1] + if (!ref) { return revs } // ??? + if (ref.endsWith(CARET_BRACES)) { return revs } // refs/tags/x^{} crap + const type = refType(line) + const doc = {sha, ref, type} - if (type === 'tag') { - const match = ref.match(/v?(\d+\.\d+\.\d+)$/) - if (match) { - revs.versions[match[1]] = doc - } - } + revs.refs[ref] = doc + // We can check out shallow clones on specific SHAs if we have a ref + if (revs.shas[sha]) { + revs.shas[sha].push(ref) + } else { + revs.shas[sha] = [ref] + } - return revs - }, {versions: {}, 'dist-tags': {}, refs: {}, shas: {}}) - }).then(revs => { - if (revs.refs.HEAD) { - const HEAD = revs.refs.HEAD - Object.keys(revs.versions).forEach(v => { - if (v.sha === HEAD.sha) { - revs['dist-tags'].HEAD = v - if (!revs.refs.latest) { - revs['dist-tags'].latest = revs.refs.HEAD - } - } - }) + if (type === 'tag') { + const match = ref.match(/v?(\d+\.\d+\.\d+(?:[-+].+)?)$/) + if (match && semver.valid(match[1], true)) { + revs.versions[semver.clean(match[1], true)] = doc + } } - REVS.set(repo, revs) + return revs - }) + }, {versions: {}, 'dist-tags': {}, refs: {}, shas: {}}) + }, err => { + err.message = `Error while executing:\n${GITPATH} ls-remote -h -t ${repo}\n\n${err.stderr}\n${err.message}` + throw err + }).then(revs => { + if (revs.refs.HEAD) { + const HEAD = revs.refs.HEAD + Object.keys(revs.versions).forEach(v => { + if (v.sha === HEAD.sha) { + revs['dist-tags'].HEAD = v + if (!revs.refs.latest) { + revs['dist-tags'].latest = revs.refs.HEAD + } + } + }) + } + REVS.set(repo, revs) + return revs }) }) } @@ -172,7 +185,18 @@ module.exports._exec = execGit function execGit (gitArgs, gitOpts, opts) { opts = optCheck(opts) return checkGit().then(gitPath => { - return execFileAsync(gitPath, gitArgs, mkOpts(gitOpts, opts)) + return promiseRetry((retry, number) => { + if (number !== 1) { + opts.log.silly('pacote', 'Retrying git command: ' + gitArgs.join(' ') + ' attempt # ' + number) + } + return execFileAsync(gitPath, gitArgs, mkOpts(gitOpts, opts)).catch((err) => { + if (shouldRetry(err)) { + retry(err) + } else { + throw err + } + }) + }, opts.retry) }) } @@ -180,7 +204,28 @@ module.exports._spawn = spawnGit function spawnGit (gitArgs, gitOpts, opts) { opts = optCheck(opts) return checkGit().then(gitPath => { - return cp.spawn(gitPath, gitArgs, mkOpts(gitOpts, opts)) + return promiseRetry((retry, number) => { + if (number !== 1) { + opts.log.silly('pacote', 'Retrying git command: ' + gitArgs.join(' ') + ' attempt # ' + number) + } + const child = cp.spawn(gitPath, gitArgs, mkOpts(gitOpts, opts)) + + let stdout = '' + let stderr = '' + child.stdout.on('data', d => { stdout += d }) + child.stderr.on('data', d => { stderr += d }) + + return finished(child).catch(err => { + if (shouldRetry(stderr)) { + retry(err) + } else { + err.stderr = stderr + throw err + } + }).then(() => { + return stdout + }) + }, opts.retry) }) } @@ -213,10 +258,10 @@ const REFS_HEADS = 'refs/heads/' const HEAD = 'HEAD' function refType (ref) { return ref.indexOf(REFS_TAGS) !== -1 - ? 'tag' - : ref.indexOf(REFS_HEADS) !== -1 - ? 'branch' - : ref.endsWith(HEAD) - ? 'head' - : 'other' + ? 'tag' + : ref.indexOf(REFS_HEADS) !== -1 + ? 'branch' + : ref.endsWith(HEAD) + ? 'head' + : 'other' } diff --git a/deps/npm/node_modules/pacote/lib/util/gunzip-maybe.js b/deps/npm/node_modules/pacote/lib/util/gunzip-maybe.js deleted file mode 100644 index 055de2921a..0000000000 --- a/deps/npm/node_modules/pacote/lib/util/gunzip-maybe.js +++ /dev/null @@ -1,24 +0,0 @@ -'use strict' - -const duplex = require('mississippi').duplex -const through = require('mississippi').through -const zlib = require('zlib') - -function hasGzipHeader (c) { - return c[0] === 0x1F && c[1] === 0x8B && c[2] === 0x08 -} - -module.exports = gunzip -function gunzip () { - const stream = duplex() - const peeker = through((chunk, enc, cb) => { - const newStream = hasGzipHeader(chunk) - ? zlib.createGunzip() - : through() - stream.setReadable(newStream) - stream.setWritable(newStream) - stream.write(chunk) - }) - stream.setWritable(peeker) - return stream -} diff --git a/deps/npm/node_modules/pacote/lib/util/opt-check.js b/deps/npm/node_modules/pacote/lib/util/opt-check.js index 711f46c91a..d13a69e4dc 100644 --- a/deps/npm/node_modules/pacote/lib/util/opt-check.js +++ b/deps/npm/node_modules/pacote/lib/util/opt-check.js @@ -25,6 +25,7 @@ function PacoteOptions (opts) { this.proxy = opts.proxy this.noProxy = opts.noProxy this.registry = opts.registry || 'https://registry.npmjs.org' + this.resolved = opts.resolved this.retry = opts.retry // for npm-registry-client this.scope = opts.scope this.userAgent = opts.userAgent || `${pkg.name}@${pkg.version}/node@${process.version}+${process.arch} (${process.platform})` @@ -44,8 +45,8 @@ function PacoteOptions (opts) { this.fullMetadata = opts.fullMetadata this.alwaysAuth = opts.alwaysAuth this.includeDeprecated = opts.includeDeprecated == null - ? true - : opts.includeDeprecated + ? true + : opts.includeDeprecated this.dirPacker = opts.dirPacker || null diff --git a/deps/npm/node_modules/pacote/lib/util/pack-dir.js b/deps/npm/node_modules/pacote/lib/util/pack-dir.js index 7625f4faf8..62776692e1 100644 --- a/deps/npm/node_modules/pacote/lib/util/pack-dir.js +++ b/deps/npm/node_modules/pacote/lib/util/pack-dir.js @@ -14,8 +14,8 @@ function packDir (manifest, label, dir, target, opts) { opts = optCheck(opts) const packer = opts.dirPacker - ? BB.resolve(opts.dirPacker(manifest, dir)) - : mkPacker(dir) + ? BB.resolve(opts.dirPacker(manifest, dir)) + : mkPacker(dir) if (!opts.cache) { return packer.then(packer => pipe(packer, target)) diff --git a/deps/npm/node_modules/pacote/lib/with-tarball-stream.js b/deps/npm/node_modules/pacote/lib/with-tarball-stream.js new file mode 100644 index 0000000000..653a4a688a --- /dev/null +++ b/deps/npm/node_modules/pacote/lib/with-tarball-stream.js @@ -0,0 +1,135 @@ +'use strict' + +const BB = require('bluebird') + +const cacache = require('cacache') +const fetch = require('./fetch.js') +const fs = require('fs') +const npa = require('npm-package-arg') +const optCheck = require('./util/opt-check.js') +const path = require('path') +const ssri = require('ssri') +const retry = require('promise-retry') + +const statAsync = BB.promisify(fs.stat) + +const RETRIABLE_ERRORS = new Set(['ENOENT', 'EINTEGRITY', 'Z_DATA_ERROR']) + +module.exports = withTarballStream +function withTarballStream (spec, opts, streamHandler) { + opts = optCheck(opts) + spec = npa(spec, opts.where) + + // First, we check for a file: resolved shortcut + const tryFile = ( + !opts.preferOnline && + opts.integrity && + opts.resolved && + opts.resolved.startsWith('file:') + ) + ? BB.try(() => { + // NOTE - this is a special shortcut! Packages installed as files do not + // have a `resolved` field -- this specific case only occurs when you have, + // say, a git dependency or a registry dependency that you've packaged into + // a local file, and put that file: spec in the `resolved` field. + opts.log.silly('pacote', `trying ${spec} by local file: ${opts.resolved}`) + const file = path.resolve(opts.where || '.', opts.resolved.substr(5)) + return statAsync(file) + .then(() => { + const verifier = ssri.integrityStream({integrity: opts.integrity}) + const stream = fs.createReadStream(file) + .on('error', err => verifier.emit('error', err)) + .pipe(verifier) + return streamHandler(stream) + }) + .catch(err => { + if (err.code === 'EINTEGRITY') { + opts.log.warn('pacote', `EINTEGRITY while extracting ${spec} from ${file}.You will have to recreate the file.`) + opts.log.verbose('pacote', `EINTEGRITY for ${spec}: ${err.message}`) + } + throw err + }) + }) + : BB.reject(Object.assign(new Error('no file!'), {code: 'ENOENT'})) + + const tryDigest = tryFile + .catch(err => { + if ( + opts.preferOnline || + !opts.cache || + !opts.integrity || + !RETRIABLE_ERRORS.has(err.code) + ) { + throw err + } else { + opts.log.silly('tarball', `trying ${spec} by hash: ${opts.integrity}`) + const stream = cacache.get.stream.byDigest( + opts.cache, opts.integrity, opts + ) + stream.once('error', err => stream.on('newListener', (ev, l) => { + if (ev === 'error') { l(err) } + })) + return streamHandler(stream) + .catch(err => { + if (err.code === 'EINTEGRITY' || err.code === 'Z_DATA_ERROR') { + opts.log.warn('tarball', `cached data for ${spec} (${opts.integrity}) seems to be corrupted. Refreshing cache.`) + return cleanUpCached(opts.cache, opts.integrity, opts) + .then(() => { throw err }) + } else { + throw err + } + }) + } + }) + + const trySpec = tryDigest + .catch(err => { + if (!RETRIABLE_ERRORS.has(err.code)) { + // If it's not one of our retriable errors, bail out and give up. + throw err + } else { + opts.log.silly( + 'tarball', + `no local data for ${spec}. Extracting by manifest.` + ) + return BB.resolve(retry((tryAgain, attemptNum) => { + const tardata = fetch.tarball(spec, opts) + if (!opts.resolved) { + tardata.on('manifest', m => { + opts.resolved = m._resolved + }) + tardata.on('integrity', i => { + opts.integrity = i + }) + } + return BB.try(() => streamHandler(tardata)) + .catch(err => { + // Retry once if we have a cache, to clear up any weird conditions. + // Don't retry network errors, though -- make-fetch-happen has already + // taken care of making sure we're all set on that front. + if (opts.cache && err.code && !err.code.match(/^E\d{3}$/)) { + if (err.code === 'EINTEGRITY' || err.code === 'Z_DATA_ERROR') { + opts.log.warn('tarball', `tarball data for ${spec} (${opts.integrity}) seems to be corrupted. Trying one more time.`) + } + return cleanUpCached(opts.cache, err.sri, opts) + .then(() => tryAgain(err)) + } else { + throw err + } + }) + }, {retries: 1})) + } + }) + + return trySpec + .catch(err => { + if (err.code === 'EINTEGRITY') { + err.message = `Verification failed while extracting ${spec}:\n${err.message}` + } + throw err + }) +} + +function cleanUpCached (cachePath, integrity, opts) { + return cacache.rm.content(cachePath, integrity, opts) +} |