1'use strict' 2 3const BB = require('bluebird') 4 5const extractStream = require('./lib/extract-stream.js') 6const fs = require('fs') 7const mkdirp = BB.promisify(require('mkdirp')) 8const npa = require('npm-package-arg') 9const optCheck = require('./lib/util/opt-check.js') 10const path = require('path') 11const rimraf = BB.promisify(require('rimraf')) 12const withTarballStream = require('./lib/with-tarball-stream.js') 13const inferOwner = require('infer-owner') 14const chown = BB.promisify(require('chownr')) 15 16const truncateAsync = BB.promisify(fs.truncate) 17const readFileAsync = BB.promisify(fs.readFile) 18const appendFileAsync = BB.promisify(fs.appendFile) 19 20// you used to call me on my... 21const selfOwner = process.getuid ? { 22 uid: process.getuid(), 23 gid: process.getgid() 24} : { 25 uid: undefined, 26 gid: undefined 27} 28 29module.exports = extract 30function extract (spec, dest, opts) { 31 opts = optCheck(opts) 32 spec = npa(spec, opts.where) 33 if (spec.type === 'git' && !opts.cache) { 34 throw new TypeError('Extracting git packages requires a cache folder') 35 } 36 if (typeof dest !== 'string') { 37 throw new TypeError('Extract requires a destination') 38 } 39 const startTime = Date.now() 40 return inferOwner(dest).then(({ uid, gid }) => { 41 opts = opts.concat({ uid, gid }) 42 return withTarballStream(spec, opts, stream => { 43 return tryExtract(spec, stream, dest, opts) 44 }) 45 .then(() => { 46 if (!opts.resolved) { 47 const pjson = path.join(dest, 'package.json') 48 return readFileAsync(pjson, 'utf8') 49 .then(str => truncateAsync(pjson) 50 .then(() => appendFileAsync(pjson, str.replace( 51 /}\s*$/, 52 `\n,"_resolved": ${ 53 JSON.stringify(opts.resolved || '') 54 }\n,"_integrity": ${ 55 JSON.stringify(opts.integrity || '') 56 }\n,"_from": ${ 57 JSON.stringify(spec.toString()) 58 }\n}` 59 )))) 60 } 61 }) 62 .then(() => opts.log.silly( 63 'extract', 64 `${spec} extracted to ${dest} (${Date.now() - startTime}ms)` 65 )) 66 }) 67} 68 69function tryExtract (spec, tarStream, dest, opts) { 70 return new BB((resolve, reject) => { 71 tarStream.on('error', reject) 72 73 rimraf(dest) 74 .then(() => mkdirp(dest)) 75 .then((made) => { 76 // respect the current ownership of unpack targets 77 // but don't try to chown if we're not root. 78 if (selfOwner.uid === 0 && 79 typeof selfOwner.gid === 'number' && 80 selfOwner.uid !== opts.uid && selfOwner.gid !== opts.gid) { 81 return chown(made || dest, opts.uid, opts.gid) 82 } 83 }) 84 .then(() => { 85 const xtractor = extractStream(spec, dest, opts) 86 xtractor.on('error', reject) 87 xtractor.on('close', resolve) 88 tarStream.pipe(xtractor) 89 }) 90 .catch(reject) 91 }) 92 .catch(err => { 93 if (err.code === 'EINTEGRITY') { 94 err.message = `Verification failed while extracting ${spec}:\n${err.message}` 95 } 96 97 throw err 98 }) 99} 100