codekingpro/portable-devtools
115k
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 