codekingpro/portable-devtools
114k
1const { resolve, dirname } = require('path')
2const { lstat } = require('fs/promises')
3const throwNonEnoent = er => {
4 if (er.code !== 'ENOENT') {
5 throw er
6 }
7}
8
9const cmdShim = require('cmd-shim')
10const readCmdShim = require('read-cmd-shim')
11
12const fixBin = require('./fix-bin.js')
13
14// even in --force mode, we never create a shim over a shim we've
15// already created. you can have multiple packages in a tree trying
16// to contend for the same bin, which creates a race condition and
17// nondeterminism.
18const seen = new Set()
19
20const failEEXIST = ({ to, from }) =>
21 Promise.reject(Object.assign(new Error('EEXIST: file already exists'), {
22 path: to,
23 dest: from,
24 code: 'EEXIST',
25 }))
26
27const handleReadCmdShimError = ({ er, from, to }) =>
28 er.code === 'ENOENT' ? null
29 : er.code === 'ENOTASHIM' ? failEEXIST({ from, to })
30 : Promise.reject(er)
31
32const SKIP = Symbol('skip - missing or already installed')
33const shimBin = ({ path, to, from, absFrom, force }) => {
34 const shims = [
35 to,
36 to + '.cmd',
37 to + '.ps1',
38 ]
39
40 for (const shim of shims) {
41 if (seen.has(shim)) {
42 return true
43 }
44 seen.add(shim)
45 }
46
47 return Promise.all([
48 ...shims,
49 absFrom,
50 ].map(f => lstat(f).catch(throwNonEnoent))).then((stats) => {
51 const [, , , stFrom] = stats
52 if (!stFrom) {
53 return SKIP
54 }
55
56 if (force) {
57 return false
58 }
59
60 return Promise.all(shims.map((s, i) => [s, stats[i]]).map(([s, st]) => {
61 if (!st) {
62 return false
63 }
64 return readCmdShim(s)
65 .then(target => {
66 target = resolve(dirname(to), target)
67 if (target.indexOf(resolve(path)) !== 0) {
68 return failEEXIST({ from, to, path })
69 }
70 return false
71 }, er => handleReadCmdShimError({ er, from, to }))
72 }))
73 })
74 .then(skip => skip !== SKIP && doShim(absFrom, to))
75}
76
77const doShim = (absFrom, to) =>
78 cmdShim(absFrom, to).then(() => fixBin(absFrom))
79
80const resetSeen = () => {
81 for (const p of seen) {
82 seen.delete(p)
83 }
84}
85
86module.exports = Object.assign(shimBin, { resetSeen })
87 