1const { isexe, sync: isexeSync } = require('isexe') 2const { join, delimiter, sep, posix } = require('path') 3 4const isWindows = process.platform === 'win32' 5 6// used to check for slashed in commands passed in. always checks for the posix 7// seperator on all platforms, and checks for the current separator when not on 8// a posix platform. don't use the isWindows check for this since that is mocked 9// in tests but we still need the code to actually work when called. that is also 10// why it is ignored from coverage. 11/* istanbul ignore next */ 12const rSlash = new RegExp(`[${posix.sep}${sep === posix.sep ? '' : sep}]`.replace(/(\\)/g, '\\$1')) 13const rRel = new RegExp(`^\\.${rSlash.source}`) 14 15const getNotFoundError = (cmd) => 16 Object.assign(new Error(`not found: ${cmd}`), { code: 'ENOENT' }) 17 18const getPathInfo = (cmd, { 19 path: optPath = process.env.PATH, 20 pathExt: optPathExt = process.env.PATHEXT, 21 delimiter: optDelimiter = delimiter, 22}) => { 23 // If it has a slash, then we don't bother searching the pathenv. 24 // just check the file itself, and that's it. 25 const pathEnv = cmd.match(rSlash) ? [''] : [ 26 // windows always checks the cwd first 27 ...(isWindows ? [process.cwd()] : []), 28 ...(optPath || /* istanbul ignore next: very unusual */ '').split(optDelimiter), 29 ] 30 31 if (isWindows) { 32 const pathExtExe = optPathExt || 33 ['.EXE', '.CMD', '.BAT', '.COM'].join(optDelimiter) 34 const pathExt = pathExtExe.split(optDelimiter).flatMap((item) => [item, item.toLowerCase()]) 35 if (cmd.includes('.') && pathExt[0] !== '') { 36 pathExt.unshift('') 37 } 38 return { pathEnv, pathExt, pathExtExe } 39 } 40 41 return { pathEnv, pathExt: [''] } 42} 43 44const getPathPart = (raw, cmd) => { 45 const pathPart = /^".*"$/.test(raw) ? raw.slice(1, -1) : raw 46 const prefix = !pathPart && rRel.test(cmd) ? cmd.slice(0, 2) : '' 47 return prefix + join(pathPart, cmd) 48} 49 50const which = async (cmd, opt = {}) => { 51 const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt) 52 const found = [] 53 54 for (const envPart of pathEnv) { 55 const p = getPathPart(envPart, cmd) 56 57 for (const ext of pathExt) { 58 const withExt = p + ext 59 const is = await isexe(withExt, { pathExt: pathExtExe, ignoreErrors: true }) 60 if (is) { 61 if (!opt.all) { 62 return withExt 63 } 64 found.push(withExt) 65 } 66 } 67 } 68 69 if (opt.all && found.length) { 70 return found 71 } 72 73 if (opt.nothrow) { 74 return null 75 } 76 77 throw getNotFoundError(cmd) 78} 79 80const whichSync = (cmd, opt = {}) => { 81 const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt) 82 const found = [] 83 84 for (const pathEnvPart of pathEnv) { 85 const p = getPathPart(pathEnvPart, cmd) 86 87 for (const ext of pathExt) { 88 const withExt = p + ext 89 const is = isexeSync(withExt, { pathExt: pathExtExe, ignoreErrors: true }) 90 if (is) { 91 if (!opt.all) { 92 return withExt 93 } 94 found.push(withExt) 95 } 96 } 97 } 98 99 if (opt.all && found.length) { 100 return found 101 } 102 103 if (opt.nothrow) { 104 return null 105 } 106 107 throw getNotFoundError(cmd) 108} 109 110module.exports = which 111which.sync = whichSync 112