This repository has been archived by the owner on Jul 3, 2019. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 62
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
284 additions
and
283 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
'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) | ||
}) | ||
}) | ||
: 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 | ||
}) | ||
} | ||
tardata.once('error', err => tardata.on('newListener', (ev, l) => { | ||
if (ev === 'error') { l(err) } | ||
})) | ||
return 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.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) | ||
} |
Oops, something went wrong.