• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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