extract.js 3.0 KB

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