diff --git a/extract.js b/extract.js index 008d194..8eac4f0 100644 --- a/extract.js +++ b/extract.js @@ -1,7 +1,7 @@ 'use strict' var cache = require('./lib/cache') -var extractStream = require('./lib/util/extract-stream') +var extractStream = require('./lib/extract-stream') var pipe = require('mississippi').pipe var optCheck = require('./lib/util/opt-check') var rps = require('realize-package-specifier') @@ -13,23 +13,36 @@ function extract (spec, dest, opts, cb) { opts = null } opts = optCheck(opts) - var xtractor = extractStream(dest, opts) - var caStream = cache.get.stream.byDigest(opts.cache, opts.digest, opts) if (opts.digest) { - opts.log.silly('extract', 'trying from digest:', opts.digest) + opts.log.silly('extract', 'trying ', spec, ' digest:', opts.digest) + extractByDigest(dest, opts, function (err) { + if (err && err === 'ENOENT') { + opts.log.silly('extract', 'digest for', spec, 'not present. Using manifest.') + return extractByManifest(spec, dest, opts, cb) + } else { + return cb(err) + } + }) + } else { + opts.log.silly('extract', 'no digest provided for ', spec, '- extracting by manifest') + extractByManifest(spec, dest, opts, cb) } - caStream.on('error', function (err) { - if (err && err.code !== 'ENOENT') { return cb(err) } - rps(spec, function (err, res) { - if (err) { return cb(err) } - var tarball = require('./lib/handlers/' + res.type + '/tarball') - pipe(tarball(res, opts), xtractor, function (err) { - opts.log.silly('extract', 'extraction finished for', spec) - cb(err) - }) +} + +function extractByDigest (dest, opts, cb) { + var xtractor = extractStream(dest, opts) + var cached = cache.get.stream.byDigest(opts.cache, opts.digest, opts) + pipe(cached, xtractor, cb) +} + +function extractByManifest (spec, dest, opts, cb) { + var xtractor = extractStream(dest, opts) + rps(spec, function (err, res) { + if (err) { return cb(err) } + var tarball = require('./lib/handlers/' + res.type + '/tarball') + pipe(tarball(res, opts), xtractor, function (err) { + opts.log.silly('extract', 'extraction finished for', spec) + cb(err) }) - }).on('end', function () { - opts.log.silly('extract', spec, 'extracted by digest') - cb(null) - }).pipe(xtractor) + }) } diff --git a/lib/util/extract-stream.js b/lib/extract-stream.js similarity index 97% rename from lib/util/extract-stream.js rename to lib/extract-stream.js index bde642e..a7f0457 100644 --- a/lib/util/extract-stream.js +++ b/lib/extract-stream.js @@ -1,6 +1,6 @@ 'use strict' -var gunzip = require('./gunzip-maybe') +var gunzip = require('./util/gunzip-maybe') var path = require('path') var pipeline = require('mississippi').pipeline var tar = require('tar-fs') diff --git a/lib/finalize-manifest.js b/lib/finalize-manifest.js new file mode 100644 index 0000000..a5ebe64 --- /dev/null +++ b/lib/finalize-manifest.js @@ -0,0 +1,133 @@ +'use strict' + +var checksumStream = require('./util/checksum-stream') +var dezalgo = require('dezalgo') +var finished = require('mississippi').finished +var gunzip = require('./util/gunzip-maybe') +var minimatch = require('minimatch') +var normalize = require('normalize-package-data') +var optCheck = require('./util/opt-check') +var path = require('path') +var pipe = require('mississippi').pipe +var pipeline = require('mississippi').pipeline +var tar = require('tar-stream') +var through = require('mississippi').through + +module.exports = finalizeManifest +function finalizeManifest (pkg, spec, opts, cb) { + tarballedProps(pkg, spec, opts, function (err, props) { + if (err) { return cb(err) } + // normalize should not add any fields, and once + // makeManifest completes, it should never be modified. + cb(null, new Manifest(pkg, props)) + }) +} + +module.exports.Manifest = Manifest +function Manifest (pkg, fromTarball) { + fromTarball = fromTarball || {} + this.name = pkg.name + this.version = pkg.version + this.dependencies = pkg.dependencies || {} + this.optionalDependencies = pkg.optionalDependencies || {} + this.devDependencies = pkg.devDependencies || {} + var bundled = ( + pkg.bundledDependencies || + pkg.bundleDependencies || + false + ) + this.bundleDependencies = bundled + this.peerDependencies = pkg.peerDependencies || {} + + // This one depends entirely on each handler. + this._resolved = pkg._resolved + + // We sometimes need to grab these from the tarball. + this._shasum = pkg._shasum || fromTarball._shasum + this._shrinkwrap = pkg._shrinkwrap || fromTarball._shrinkwrap || null + this.bin = pkg.bin || fromTarball.bin || null + + this._id = null // filled in by normalize-package-data, but unnecessary + + Object.preventExtensions(this) + normalize(this) + Object.freeze(this) +} + +// Some things aren't filled in by standard manifest fetching. +// If this function needs to do its work, it will grab the +// package tarball, extract it, and take whatever it needs +// from the stream. +function tarballedProps (pkg, spec, opts, cb) { + cb = dezalgo(cb) + var extraProps = {} + var needsShrinkwrap = ( + pkg._hasShrinkwrap !== false && + !pkg._shrinkwrap + ) + var needsBin = !!( + !pkg.bin && + pkg.directories && + pkg.directories.bin + ) + var needsShasum = !pkg._shasum + if (!needsShrinkwrap && !needsBin && !needsShasum) { + opts.log.silly('finalize-manifest', 'Skipping tarball extraction -- nothing needed.') + return cb(null, extraProps) + } else { + opts = optCheck(opts) + opts.memoize = false + var tarball = require('./handlers/' + spec.type + '/tarball') + var tarData = tarball.fromManifest(pkg, spec, opts) + var shaStream = null + var extractorStream = null + + if (needsShrinkwrap || needsBin) { + opts.log.silly('finalize-manifest', 'parsing tarball for', spec.name) + var dirBin = pkg.directories && pkg.directories.bin + pkg.bin = pkg.bin || {} + var dataStream = tar.extract() + extractorStream = pipeline(gunzip(), dataStream) + dataStream.on('entry', function doEntry (header, fileStream, next) { + var filePath = header.name.replace(/[^/]+\//, '') + if (needsShrinkwrap && filePath === 'npm-shrinkwrap.json') { + var srData = '' + fileStream.on('data', function (d) { srData += d }) + + return finished(fileStream, function (err) { + if (err) { return dataStream.emit('error', err) } + try { + extraProps._shrinkwrap = JSON.parse(srData) + next() + } catch (e) { + dataStream.emit('error', e) + } + }) + } else if (needsBin && minimatch(filePath, dirBin + '/**')) { + var relative = path.relative(dirBin, filePath) + if (relative && relative[0] !== '.') { + extraProps.bin[path.basename(relative)] = path.join(dirBin, relative) + } + } + // Drain and get next one + fileStream.on('data', function () {}) + next() + }) + } else { + extractorStream = through() + } + if (needsShasum) { + shaStream = checksumStream(null, opts.hashAlgorithm) + shaStream.on('digest', function (d) { + extraProps._shasum = d + }) + } else { + shaStream = through() + } + // Drain the end stream + extractorStream.on('data', function () {}) + return pipe(tarData, shaStream, extractorStream, function (err) { + cb(err, extraProps) + }) + } +} diff --git a/lib/registry/manifest.js b/lib/registry/manifest.js index 7ddb7fe..94eb484 100644 --- a/lib/registry/manifest.js +++ b/lib/registry/manifest.js @@ -1,12 +1,10 @@ 'use strict' -var extractShrinkwrap = require('../util/extract-shrinkwrap') var optCheck = require('../util/opt-check') var pickManifest = require('./pick-manifest') var pickRegistry = require('./pick-registry') var url = require('url') var request = require('./request') -var tarball = require('./tarball') module.exports = manifest function manifest (spec, opts, cb) { @@ -28,13 +26,21 @@ function manifest (spec, opts, cb) { defaultTag: opts.defaultTag }, function (err, manifest) { if (err) { return cb(err) } - opts.log.silly( - 'registry.manifest', - spec.name + '@' + spec.spec, - 'resolved to', - manifest.name + '@' + manifest.version + // Done here instead of ./finalize-manifest because these fields + // have to be filled in differently depending on type. + manifest._shasum = ( + manifest.dist && manifest.dist.shasum + ) || manifest._shasum + manifest._resolved = ( + manifest.dist && manifest.dist.tarball + ) || url.resolve( + registry, + '/' + manifest.name + + '/-/' + manifest.name + + '-' + manifest.version + + '.tgz' ) - ensureShrinkwrap(manifest, registry, opts, cb) + cb(null, manifest) }) }) } @@ -45,31 +51,3 @@ function metadataUrl (registry, name) { : registry return url.resolve(normalized, name) } - -function ensureShrinkwrap (manifest, registry, opts, cb) { - if (manifest._hasShrinkwrap === false || manifest._shrinkwrap || !manifest.dist) { - opts.log.silly('registry.manifest', 'shrinkwrap extraction not needed') - cb(null, manifest) - } else { - opts.log.silly('registry.manifest', 'extracting shrinkwrap') - opts.memoize = false - var shrinkwrap - var tarstream = tarball.fromManifest(manifest, registry, opts) - extractShrinkwrap(tarstream, opts, function (err, sr) { - if (err) { return cb(err) } - shrinkwrap = sr - }) - tarstream.on('data', function () {}) - tarstream.on('error', cb) - tarstream.on('end', function () { - manifest._hasShrinkwrap = !!shrinkwrap - manifest._shrinkwrap = shrinkwrap - if (shrinkwrap) { - opts.log.silly('registry.manifest', 'shrinkwrap found') - } else { - opts.log.silly('registry.manifest', 'no shrinkwrap') - } - cb(null, manifest) - }) - } -} diff --git a/lib/registry/request.js b/lib/registry/request.js index ce76cdf..8b15df9 100644 --- a/lib/registry/request.js +++ b/lib/registry/request.js @@ -68,19 +68,22 @@ function getStream (uri, registry, opts) { } else { opts.log.silly('registry.get', 'trying to grab from cache') var cs = cache.get.stream(opts.cache, key, opts) + cs.once('data', function () { + opts.log.silly('registry.get', 'got data for', key, 'from cache.') + }) cs.on('metadata', function (meta) { if (isStale(meta, opts)) { opts.log.silly( 'registry.get', 'cache data for', key, 'stale. Checking registry.' ) - cs.pause() switchToRegistry(null, cs, meta) } else { opts.log.silly( 'registry.get', 'cached data for', key, 'valid. Reading from cache.' ) + cs.pipe(stream) } }) cs.once('error', function (err) { @@ -91,7 +94,6 @@ function getStream (uri, registry, opts) { } switchToRegistry(err, null) }) - cs.pipe(stream) stream.on('error', function (e) { cs.emit('error', e) }) stream.on('end', function () { cs.end() }) } @@ -104,8 +106,8 @@ function getStream (uri, registry, opts) { key ) if (fallback) { - opts.log.silly('registry.get', 'resuming stale cache stream for', key) - fallback.resume() + opts.log.silly('registry.get', 'reading stale cache stream for', key) + fallback.pipe(stream) } else { if (!err) { err = new Error('no offline data available') @@ -115,6 +117,7 @@ function getStream (uri, registry, opts) { } } else { if (fallback) { + fallback.pause() stream.emit('reset') } registryStream( @@ -123,17 +126,16 @@ function getStream (uri, registry, opts) { stream.emit('reset') }).on('cached', function () { opts.log.silly('registry.get', 'Got a 304. Switching back to cache.') - fallback.resume() + fallback.pipe(stream) }).once('error', function (err) { opts.log.silly('registry.get', 'request error: ', err) if (!fallback) { stream.emit('error', err) } else { opts.log.silly('registry.get', 'using stale', key, 'from cache.') - fallback.resume() + stream.emit('reset') + fallback.pipe(stream) } - }).on('end', function () { - stream.end() }).pipe(stream) } } @@ -189,16 +191,19 @@ function registryStream (key, uri, registry, meta, opts) { res, decoder, to(function (chunk, enc, cb) { - cacheStream.write(chunk, enc, function () { - stream.write(chunk, enc, cb) - }) + cacheStream.write(chunk, enc) + stream.write(chunk, enc, cb) }, function (cb) { + opts.log.silly('registry.get', 'request for', key, 'done.') cacheStream.end(function () { opts.log.silly('registry.get', 'wrapped up cacheStream') stream.end(cb) }) }), - function () {} + function (err) { + if (err) { return stream.emit('error', err) } + opts.log.silly('registry.get', 'pipeline for', key, 'done') + } ) } else { opts.log.silly('registry.get', 'no cache. streaming straight out') diff --git a/lib/registry/tarball.js b/lib/registry/tarball.js index 48437bf..633ba44 100644 --- a/lib/registry/tarball.js +++ b/lib/registry/tarball.js @@ -10,7 +10,6 @@ var through = require('mississippi').through module.exports = tarball function tarball (spec, opts) { opts = optCheck(opts) - var registry = pickRegistry(spec, opts) opts.log.verbose( 'registry.tarball', 'looking up registry-based metadata for ', spec @@ -23,7 +22,7 @@ function tarball (spec, opts) { 'registry metadata found. Downloading ', manifest.name + '@' + manifest.version ) pipe( - fromManifest(manifest, registry), + fromManifest(manifest, spec, opts), stream ) }) @@ -31,9 +30,10 @@ function tarball (spec, opts) { } module.exports.fromManifest = fromManifest -function fromManifest (manifest, registry, opts) { +function fromManifest (manifest, spec, opts) { opts = optCheck(opts) - var uri = manifest.dist && manifest.dist.tarball - opts.digest = manifest.dist && manifest.dist.shasum + var registry = pickRegistry(spec, opts) + var uri = manifest._resolved + opts.digest = manifest._shasum return request.stream(uri, registry, opts) } diff --git a/lib/util/checksum-stream.js b/lib/util/checksum-stream.js new file mode 100644 index 0000000..94d0d6e --- /dev/null +++ b/lib/util/checksum-stream.js @@ -0,0 +1,26 @@ +'use strict' + +var crypto = require('crypto') +var through = require('mississippi').through + +module.exports = checksumStream +function checksumStream (digest, algorithm) { + var hash = crypto.createHash(algorithm || 'sha1') + var stream = through(function (chunk, enc, cb) { + hash.update(chunk, enc) + cb(null, chunk, enc) + }, function (cb) { + var streamDigest = hash.digest('hex') + if (digest && streamDigest !== digest) { + var err = new Error('checksum failed') + err.code = 'EBADCHECKSUM' + err.expected = digest + err.found = streamDigest + return cb(err) + } else { + stream.emit('digest', streamDigest) + cb() + } + }) + return stream +} diff --git a/lib/util/extract-shrinkwrap.js b/lib/util/extract-shrinkwrap.js deleted file mode 100644 index 2b7d50a..0000000 --- a/lib/util/extract-shrinkwrap.js +++ /dev/null @@ -1,68 +0,0 @@ -'use strict' - -var finished = require('mississippi').finished -var gunzip = require('./gunzip-maybe') -var pipe = require('mississippi').pipe -var pipeline = require('mississippi').pipeline -var through = require('mississippi').through - -// `tar-stream` is significantly faster than `node-tar`, but there's -// a risk that `tar-stream` can cause some incompatibilities. -// -// It's worth keeping an eye out for issues caused by this, and -// swap out the module for node-tar if they rear their head. -var tar = require('tar-stream') - -// Picks out just the `npm-shrinkwrap.json` from a package -// tarball (coming through `pkgStream`), and stops parsing the -// tarball as soon as the file is found. -module.exports = extractShrinkwrap -function extractShrinkwrap (pkgStream, opts, cb) { - var extract = tar.extract() - - // The extra `through` is to compensate for misbehaving `pkgStream`s. - // For example, `request` streams are notoriously unreliable. - // This is a bit of defensive programming, not a fix for - // a specific known example of an issue. - var unzipped = pipeline(through(), gunzip(), extract) - - var shrinkwrap = null // we'll pop the data in here if found. - extract.on('entry', function onEntry (header, fileStream, next) { - if (header.name === 'package/npm-shrinkwrap.json') { - opts.log.silly('extract-shrinkwrap', 'found shrinkwrap') - // got a shrinkwrap! Now we don't need to look for entries anymore. - extract.removeListener('entry', onEntry) - - // Grab all the file data off the entry fileStream. - var data = '' - fileStream.on('data', function (d) { data += d }) - - finished(fileStream, function (err) { - if (err) { return extract.emit('error', err) } - try { - shrinkwrap = JSON.parse(data) - } catch (e) { - extract.emit('error', e) - } - // By destroying `unzipped`, this *should* stop `tar-stream` - // from continuing to waste resources on tarball parsing. - unzipped.unpipe() - cb(null, shrinkwrap) - }) - } else { - // Not a shrinkwrap. Autodrain this entry, and move on to the next. - fileStream.resume() - next() - } - }) - - // Any other streams that `pkgStream` is being piped to should - // remain unaffected by this, although there might be confusion - // around backpressure issues. - pipe(pkgStream, unzipped, function (err) { - // If we already successfully emitted a shrinkwrap, - // we literally don't care about any errors. - // Issues with `pkgStream` can be handled elsewhere if needed. - if (!shrinkwrap) { cb(err) } - }) -} diff --git a/manifest.js b/manifest.js index 0743ee6..e46a2dd 100644 --- a/manifest.js +++ b/manifest.js @@ -1,5 +1,6 @@ 'use strict' +var finalizeManifest = require('./lib/finalize-manifest') var optCheck = require('./lib/util/opt-check') var rps = require('realize-package-specifier') @@ -17,7 +18,8 @@ function manifest (spec, opts, cb) { if (err) { return cb(err) } var fetcher = handlers[res.type] || (handlers[res.type] = require('./lib/handlers/' + res.type + '/manifest')) fetcher(res, opts, function (err, mani) { - cb(err, mani) + if (err) { return cb(err) } + finalizeManifest(mani, res, opts, cb) }) }) } diff --git a/package.json b/package.json index 6459483..190cf4e 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "cacache": "^5.0.0", "dezalgo": "^1.0.3", "inflight": "^1.0.6", + "minimatch": "^3.0.3", "mississippi": "^1.2.0", "npm-registry-client": "^7.4.1", "realize-package-specifier": "^3.0.3", diff --git a/test/extract-stream.js b/test/extract-stream.js new file mode 100644 index 0000000..07183aa --- /dev/null +++ b/test/extract-stream.js @@ -0,0 +1,23 @@ +'use strict' + +var fs = require('fs') +var npmlog = require('npmlog') +var pipe = require('mississippi').pipe +var test = require('tap').test + +require('./util/test-dir')(__filename) + +var extractStream = require('../lib/extract-stream') + +npmlog.level = process.env.LOGLEVEL || 'silent' +var OPTS = { + log: npmlog +} + +test('basic extraction') +test('excludes symlinks') +test('renames .gitignore to .npmignore if not present') +test('accepts gid and uid opts') +test('accepts dmode/fmode/umask opts') +test('automatically handles gzipped tarballs') +test('strips first item in path, even if not `package/`') diff --git a/test/finalize-manifest.js b/test/finalize-manifest.js new file mode 100644 index 0000000..bd07551 --- /dev/null +++ b/test/finalize-manifest.js @@ -0,0 +1,14 @@ +'use strict' + +var test = require('tap').test + +require('./util/test-dir')(__filename) + +var finalizeManifest = require('../lib/finalize-manifest') + +test('returns a manifest with the right fields') +test('defaults all field to expected types + values') +test('manifest returned is immutable + inextensible') +test('fills in shrinkwrap if missing') +test('fills in shasum if missing') +test('fills in `bin` if `directories.bin` present') diff --git a/test/registry.manifest.cache.js b/test/registry.manifest.cache.js index 9472a99..f796eb9 100644 --- a/test/registry.manifest.cache.js +++ b/test/registry.manifest.cache.js @@ -7,18 +7,33 @@ var testDir = require('./util/test-dir') var tnock = require('./util/tnock') var CACHE = testDir(__filename) +var Manifest = require('../lib/finalize-manifest').Manifest var manifest = require('../manifest') -var PKG = { +// This is what the server sends +var BASE = { name: 'foo', - version: '1.2.3' + version: '1.2.3', + _hasShrinkwrap: false, + dist: { + shasum: 'deadbeef', + tarball: 'https://foo.bar/x.tgz' + } } +// This is what's returned by finalize-manifest +var PKG = new Manifest({ + name: 'foo', + version: '1.2.3', + _hasShrinkwrap: false, + _shasum: BASE.dist.shasum, + _resolved: BASE.dist.tarball +}) var META = { 'dist-tags': { latest: '1.2.3' }, versions: { - '1.2.3': PKG + '1.2.3': BASE } } diff --git a/test/registry.manifest.js b/test/registry.manifest.js index 6eda0db..cac27b0 100644 --- a/test/registry.manifest.js +++ b/test/registry.manifest.js @@ -4,11 +4,19 @@ var npmlog = require('npmlog') var test = require('tap').test var tnock = require('./util/tnock') +var Manifest = require('../lib/finalize-manifest').Manifest var manifest = require('../manifest') -var PKG = { +var BASE = { name: 'foo', - version: '1.2.3' + version: '1.2.3', + _hasShrinkwrap: false, + _shasum: 'deadbeef', + _resolved: 'https://foo.bar/x.tgz', + dist: { + shasum: 'deadbeef', + tarball: 'https://foo.bar/x.tgz' + } } var META = { @@ -35,7 +43,7 @@ var META = { name: 'foo', version: '1.2.1' }, - '1.2.3': PKG, + '1.2.3': BASE, '1.2.4': { name: 'foo', version: '1.2.4' @@ -46,6 +54,7 @@ var META = { } } } +var PKG = new Manifest(BASE) npmlog.level = process.env.LOGLEVEL || 'silent' var OPTS = { @@ -110,7 +119,7 @@ test('fetches manifest from registry by range', function (t) { manifest('foo@^1.2.0', OPTS, function (err, pkg) { if (err) { throw err } // Not 1.2.4 because 1.2.3 is `latest` - t.deepEqual(pkg, META.versions['1.2.3'], 'picked right manifest') + t.deepEqual(pkg, new Manifest(META.versions['1.2.3']), 'picked right manifest') t.end() }) }) @@ -121,7 +130,7 @@ test('fetches manifest from scoped registry by range', function (t) { srv.get('/@usr%2ffoo').reply(200, META) manifest('@usr/foo@^1.2.0', OPTS, function (err, pkg) { if (err) { throw err } - t.deepEqual(pkg, META.versions['1.2.3'], 'got scoped manifest from version') + t.deepEqual(pkg, new Manifest(META.versions['1.2.3']), 'got scoped manifest from version') t.end() }) }) @@ -241,13 +250,21 @@ test('package requests are case-sensitive', function (t) { t.plan(2) var srv = tnock(t, OPTS.registry) - var CASEDPKG = { + var CASEDBASE = { name: 'Foo', - version: '1.2.3' + version: '1.2.3', + _hasShrinkwrap: false, + _shasum: 'deadbeef', + _resolved: 'https://foo.bar/x.tgz', + dist: { + shasum: 'deadbeef', + tarball: 'https://foo.bar/x.tgz' + } } + var CASEDPKG = new Manifest(CASEDBASE) srv.get('/Foo').reply(200, { versions: { - '1.2.3': CASEDPKG + '1.2.3': CASEDBASE } }) manifest('Foo@1.2.3', OPTS, function (err, pkg) { diff --git a/test/registry.manifest.shrinkwrap.js b/test/registry.manifest.shrinkwrap.js index c7a03b7..57a23b7 100644 --- a/test/registry.manifest.shrinkwrap.js +++ b/test/registry.manifest.shrinkwrap.js @@ -21,10 +21,7 @@ var OPTS = { var PKG = { name: 'foo', - version: '1.2.3', - dist: { - tarball: OPTS.registry + '/foo/-/foo-1.2.3.tgz' - } + version: '1.2.3' } var SHRINKWRAP = { @@ -63,7 +60,6 @@ test('fetches shrinkwrap data if missing + required', function (t) { manifest('foo@1.2.3', OPTS, function (err, pkg) { if (err) { throw err } t.ok(pkg, 'got a package manifest') - t.equal(pkg._hasShrinkwrap, true, '_hasShrinkwrap set') t.deepEqual(pkg._shrinkwrap, SHRINKWRAP, 'got a shrinkwrap') t.end() }) diff --git a/test/util.extract-shrinkwrap.js b/test/util.extract-shrinkwrap.js deleted file mode 100644 index 1a358ac..0000000 --- a/test/util.extract-shrinkwrap.js +++ /dev/null @@ -1,75 +0,0 @@ -'use strict' - -var fs = require('fs') -var npmlog = require('npmlog') -var pipe = require('mississippi').pipe -var test = require('tap').test - -require('./util/test-dir')(__filename) - -var extractShrinkwrap = require('../lib/util/extract-shrinkwrap') - -var SHRINKWRAP = { - 'name': 'test', - 'version': '1.0.0', - 'dependencies': { - 'eggplant': { - 'version': '1.0.2', - 'from': 'eggplant@latest', - 'resolved': 'https://registry.npmjs.org/eggplant/-/eggplant-1.0.2.tgz' - } - } -} - -var HAS_SHRINKWRAP = '../../fixtures/has-shrinkwrap.tgz' -var NO_SHRINKWRAP = '../../fixtures/no-shrinkwrap.tgz' -var DEST = './copy.tgz' - -test('basic shrinkwrap extraction', function (t) { - var input = fs.createReadStream(HAS_SHRINKWRAP) - var output = fs.createWriteStream(DEST) - - t.plan(3) - extractShrinkwrap(input, {log: npmlog}, function (err, sr) { - if (err) { throw err } - t.ok(sr, 'got a shrinkwrap back!') - t.deepEqual(sr, SHRINKWRAP, 'shrinkwrap data correct') - }) - pipe(input, output, function (err) { - if (err) { throw err } - fs.readFile(HAS_SHRINKWRAP, 'utf8', function (err, src) { - if (err) { throw err } - fs.readFile(DEST, 'utf8', function (err, dest) { - if (err) { throw err } - t.equal(src, dest, 'data fully copied') - }) - }) - }) -}) - -test('no shrinkwrap in tarball', function (t) { - var input = fs.createReadStream(NO_SHRINKWRAP) - var output = fs.createWriteStream(DEST) - - t.plan(2) - extractShrinkwrap(input, {log: npmlog}, function (err, sr) { - if (err) { throw err } - t.notOk(sr, 'no shrinkwrap returned') - }) - pipe(input, output, function (err) { - if (err) { throw err } - fs.readFile(NO_SHRINKWRAP, 'utf8', function (err, src) { - if (err) { throw err } - fs.readFile(DEST, 'utf8', function (err, dest) { - if (err) { throw err } - t.equal(src, dest, 'data fully copied') - }) - }) - }) -}) - -test('stops parsing tarball after shrinkwrap found') -test('source stream continues after shrinkwrap found') -test('source stream errors trigger extract error') -test('only calls cb once if stream error after shrinkwrap found') -test('works fine when teeing a `request` stream')