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