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 = ({ path, 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