const isWindows = process.platform === 'win32' || process.env.OSTYPE === 'cygwin' || process.env.OSTYPE === 'msys'; const path = require('path'); const COLON = isWindows ? ';' : ':'; const isexe = require('isexe'); const getNotFoundError = (cmd) => Object.assign(new Error(`not found: ${cmd}`), { code: 'ENOENT' }); const getPathInfo = (cmd, opt) => { const colon = opt.colon || COLON; // If it has a slash, then we don't bother searching the pathenv. // just check the file itself, and that's it. const pathEnv = cmd.match(/\//) || (isWindows && cmd.match(/\\/)) ? [''] : [ // windows always checks the cwd first ...(isWindows ? [process.cwd()] : []), ...( opt.path || process.env.PATH || /* istanbul ignore next: very unusual */ '' ).split(colon), ]; const pathExtExe = isWindows ? opt.pathExt || process.env.PATHEXT || '.EXE;.CMD;.BAT;.COM' : ''; const pathExt = isWindows ? pathExtExe.split(colon) : ['']; if (isWindows) { if (cmd.indexOf('.') !== -1 && pathExt[0] !== '') pathExt.unshift(''); } return { pathEnv, pathExt, pathExtExe, }; }; const which = (cmd, opt, cb) => { if (typeof opt === 'function') { cb = opt; opt = {}; } if (!opt) opt = {}; const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt); const found = []; const step = (i) => new Promise((resolve, reject) => { if (i === pathEnv.length) return opt.all && found.length ? resolve(found) : reject(getNotFoundError(cmd)); const ppRaw = pathEnv[i]; const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw; const pCmd = path.join(pathPart, cmd); const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd; resolve(subStep(p, i, 0)); }); const subStep = (p, i, ii) => new Promise((resolve, reject) => { if (ii === pathExt.length) return resolve(step(i + 1)); const ext = pathExt[ii]; isexe(p + ext, { pathExt: pathExtExe }, (er, is) => { if (!er && is) { if (opt.all) found.push(p + ext); else return resolve(p + ext); } return resolve(subStep(p, i, ii + 1)); }); }); return cb ? step(0).then((res) => cb(null, res), cb) : step(0); }; const whichSync = (cmd, opt) => { opt = opt || {}; const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt); const found = []; for (let i = 0; i < pathEnv.length; i++) { const ppRaw = pathEnv[i]; const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw; const pCmd = path.join(pathPart, cmd); const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd; for (let j = 0; j < pathExt.length; j++) { const cur = p + pathExt[j]; try { const is = isexe.sync(cur, { pathExt: pathExtExe }); if (is) { if (opt.all) found.push(cur); else return cur; } } catch (ex) {} } } if (opt.all && found.length) return found; if (opt.nothrow) return null; throw getNotFoundError(cmd); }; module.exports = which; which.sync = whichSync;