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