index.js 3.1 KB

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