which.js 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. const isWindows = process.platform === 'win32' ||
  2. process.env.OSTYPE === 'cygwin' ||
  3. process.env.OSTYPE === 'msys'
  4. const path = require('path')
  5. const COLON = isWindows ? ';' : ':'
  6. const isexe = require('isexe')
  7. const getNotFoundError = (cmd) =>
  8. Object.assign(new Error(`not found: ${cmd}`), { code: 'ENOENT' })
  9. const getPathInfo = (cmd, opt) => {
  10. const colon = opt.colon || COLON
  11. // If it has a slash, then we don't bother searching the pathenv.
  12. // just check the file itself, and that's it.
  13. const pathEnv = cmd.match(/\//) || isWindows && cmd.match(/\\/) ? ['']
  14. : (
  15. [
  16. // windows always checks the cwd first
  17. ...(isWindows ? [process.cwd()] : []),
  18. ...(opt.path || process.env.PATH ||
  19. /* istanbul ignore next: very unusual */ '').split(colon),
  20. ]
  21. )
  22. const pathExtExe = isWindows
  23. ? opt.pathExt || process.env.PATHEXT || '.EXE;.CMD;.BAT;.COM'
  24. : ''
  25. const pathExt = isWindows ? pathExtExe.split(colon) : ['']
  26. if (isWindows) {
  27. if (cmd.indexOf('.') !== -1 && pathExt[0] !== '')
  28. pathExt.unshift('')
  29. }
  30. return {
  31. pathEnv,
  32. pathExt,
  33. pathExtExe,
  34. }
  35. }
  36. const which = (cmd, opt, cb) => {
  37. if (typeof opt === 'function') {
  38. cb = opt
  39. opt = {}
  40. }
  41. if (!opt)
  42. opt = {}
  43. const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt)
  44. const found = []
  45. const step = i => new Promise((resolve, reject) => {
  46. if (i === pathEnv.length)
  47. return opt.all && found.length ? resolve(found)
  48. : reject(getNotFoundError(cmd))
  49. const ppRaw = pathEnv[i]
  50. const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw
  51. const pCmd = path.join(pathPart, cmd)
  52. const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd
  53. : pCmd
  54. resolve(subStep(p, i, 0))
  55. })
  56. const subStep = (p, i, ii) => new Promise((resolve, reject) => {
  57. if (ii === pathExt.length)
  58. return resolve(step(i + 1))
  59. const ext = pathExt[ii]
  60. isexe(p + ext, { pathExt: pathExtExe }, (er, is) => {
  61. if (!er && is) {
  62. if (opt.all)
  63. found.push(p + ext)
  64. else
  65. return resolve(p + ext)
  66. }
  67. return resolve(subStep(p, i, ii + 1))
  68. })
  69. })
  70. return cb ? step(0).then(res => cb(null, res), cb) : step(0)
  71. }
  72. const whichSync = (cmd, opt) => {
  73. opt = opt || {}
  74. const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt)
  75. const found = []
  76. for (let i = 0; i < pathEnv.length; i ++) {
  77. const ppRaw = pathEnv[i]
  78. const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw
  79. const pCmd = path.join(pathPart, cmd)
  80. const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd
  81. : pCmd
  82. for (let j = 0; j < pathExt.length; j ++) {
  83. const cur = p + pathExt[j]
  84. try {
  85. const is = isexe.sync(cur, { pathExt: pathExtExe })
  86. if (is) {
  87. if (opt.all)
  88. found.push(cur)
  89. else
  90. return cur
  91. }
  92. } catch (ex) {}
  93. }
  94. }
  95. if (opt.all && found.length)
  96. return found
  97. if (opt.nothrow)
  98. return null
  99. throw getNotFoundError(cmd)
  100. }
  101. module.exports = which
  102. which.sync = whichSync