1'use strict' 2 3const Minipass = require('minipass') 4const path = require('path') 5const tar = require('tar') 6 7module.exports = extractStream 8module.exports._computeMode = computeMode 9 10class Transformer extends Minipass { 11 constructor (spec, opts) { 12 super() 13 this.spec = spec 14 this.opts = opts 15 this.str = '' 16 } 17 write (data) { 18 this.str += data 19 return true 20 } 21 end () { 22 const replaced = this.str.replace( 23 /}\s*$/, 24 `\n,"_resolved": ${ 25 JSON.stringify(this.opts.resolved || '') 26 }\n,"_integrity": ${ 27 JSON.stringify(this.opts.integrity || '') 28 }\n,"_from": ${ 29 JSON.stringify(this.spec.toString()) 30 }\n}` 31 ) 32 super.write(replaced) 33 return super.end() 34 } 35} 36 37function computeMode (fileMode, optMode, umask) { 38 return (fileMode | optMode) & ~(umask || 0) 39} 40 41function pkgJsonTransform (spec, opts) { 42 return entry => { 43 if (entry.path === 'package.json') { 44 const transformed = new Transformer(spec, opts) 45 return transformed 46 } 47 } 48} 49 50function extractStream (spec, dest, opts) { 51 opts = opts || {} 52 const sawIgnores = new Set() 53 return tar.x({ 54 cwd: dest, 55 filter: (name, entry) => !entry.header.type.match(/^.*link$/i), 56 strip: 1, 57 onwarn: msg => opts.log && opts.log.warn('tar', msg), 58 uid: opts.uid, 59 gid: opts.gid, 60 umask: opts.umask, 61 transform: opts.resolved && pkgJsonTransform(spec, opts), 62 onentry (entry) { 63 if (entry.type.toLowerCase() === 'file') { 64 entry.mode = computeMode(entry.mode, opts.fmode, opts.umask) 65 } else if (entry.type.toLowerCase() === 'directory') { 66 entry.mode = computeMode(entry.mode, opts.dmode, opts.umask) 67 } else { 68 entry.mode = computeMode(entry.mode, 0, opts.umask) 69 } 70 71 // Note: This mirrors logic in the fs read operations that are 72 // employed during tarball creation, in the fstream-npm module. 73 // It is duplicated here to handle tarballs that are created 74 // using other means, such as system tar or git archive. 75 if (entry.type.toLowerCase() === 'file') { 76 const base = path.basename(entry.path) 77 if (base === '.npmignore') { 78 sawIgnores.add(entry.path) 79 } else if (base === '.gitignore') { 80 const npmignore = entry.path.replace(/\.gitignore$/, '.npmignore') 81 if (!sawIgnores.has(npmignore)) { 82 // Rename, may be clobbered later. 83 entry.path = npmignore 84 } 85 } 86 } 87 } 88 }) 89} 90