1const Fetcher = require('./fetcher.js') 2const fsm = require('fs-minipass') 3const cacache = require('cacache') 4const _tarballFromResolved = Symbol.for('pacote.Fetcher._tarballFromResolved') 5const _exeBins = Symbol('_exeBins') 6const { resolve } = require('path') 7const fs = require('fs') 8const _readPackageJson = Symbol.for('package.Fetcher._readPackageJson') 9 10class FileFetcher extends Fetcher { 11 constructor (spec, opts) { 12 super(spec, opts) 13 // just the fully resolved filename 14 this.resolved = this.spec.fetchSpec 15 } 16 17 get types () { 18 return ['file'] 19 } 20 21 manifest () { 22 if (this.package) { 23 return Promise.resolve(this.package) 24 } 25 26 // have to unpack the tarball for this. 27 return cacache.tmp.withTmp(this.cache, this.opts, dir => 28 this.extract(dir) 29 .then(() => this[_readPackageJson](dir + '/package.json')) 30 .then(mani => this.package = { 31 ...mani, 32 _integrity: this.integrity && String(this.integrity), 33 _resolved: this.resolved, 34 _from: this.from, 35 })) 36 } 37 38 [_exeBins] (pkg, dest) { 39 if (!pkg.bin) { 40 return Promise.resolve() 41 } 42 43 return Promise.all(Object.keys(pkg.bin).map(k => new Promise(res => { 44 const script = resolve(dest, pkg.bin[k]) 45 // Best effort. Ignore errors here, the only result is that 46 // a bin script is not executable. But if it's missing or 47 // something, we just leave it for a later stage to trip over 48 // when we can provide a more useful contextual error. 49 fs.stat(script, (er, st) => { 50 if (er) { 51 return res() 52 } 53 const mode = st.mode | 0o111 54 if (mode === st.mode) { 55 return res() 56 } 57 fs.chmod(script, mode, res) 58 }) 59 }))) 60 } 61 62 extract (dest) { 63 // if we've already loaded the manifest, then the super got it. 64 // but if not, read the unpacked manifest and chmod properly. 65 return super.extract(dest) 66 .then(result => this.package ? result 67 : this[_readPackageJson](dest + '/package.json').then(pkg => 68 this[_exeBins](pkg, dest)).then(() => result)) 69 } 70 71 [_tarballFromResolved] () { 72 // create a read stream and return it 73 return new fsm.ReadStream(this.resolved) 74 } 75 76 packument () { 77 // simulate based on manifest 78 return this.manifest().then(mani => ({ 79 name: mani.name, 80 'dist-tags': { 81 [this.defaultTag]: mani.version, 82 }, 83 versions: { 84 [mani.version]: { 85 ...mani, 86 dist: { 87 tarball: `file:${this.resolved}`, 88 integrity: this.integrity && String(this.integrity), 89 }, 90 }, 91 }, 92 })) 93 } 94} 95 96module.exports = FileFetcher 97