UNPKG

3.57 kBJavaScriptView Raw
1var common = require('./common');
2var fs = require('fs');
3var path = require('path');
4
5common.register('which', _which, {
6 allowGlobbing: false,
7 cmdOptions: {
8 'a': 'all',
9 },
10});
11
12// XP's system default value for `PATHEXT` system variable, just in case it's not
13// set on Windows.
14var XP_DEFAULT_PATHEXT = '.com;.exe;.bat;.cmd;.vbs;.vbe;.js;.jse;.wsf;.wsh';
15
16// For earlier versions of NodeJS that doesn't have a list of constants (< v6)
17var FILE_EXECUTABLE_MODE = 1;
18
19function isWindowsPlatform() {
20 return process.platform === 'win32';
21}
22
23// Cross-platform method for splitting environment `PATH` variables
24function splitPath(p) {
25 return p ? p.split(path.delimiter) : [];
26}
27
28// Tests are running all cases for this func but it stays uncovered by codecov due to unknown reason
29/* istanbul ignore next */
30function isExecutable(pathName) {
31 try {
32 // TODO(node-support): replace with fs.constants.X_OK once remove support for node < v6
33 fs.accessSync(pathName, FILE_EXECUTABLE_MODE);
34 } catch (err) {
35 return false;
36 }
37 return true;
38}
39
40function checkPath(pathName) {
41 return fs.existsSync(pathName) && !common.statFollowLinks(pathName).isDirectory()
42 && (isWindowsPlatform() || isExecutable(pathName));
43}
44
45//@
46//@ ### which(command)
47//@
48//@ Examples:
49//@
50//@ ```javascript
51//@ var nodeExec = which('node');
52//@ ```
53//@
54//@ Searches for `command` in the system's `PATH`. On Windows, this uses the
55//@ `PATHEXT` variable to append the extension if it's not already executable.
56//@ Returns string containing the absolute path to `command`.
57function _which(options, cmd) {
58 if (!cmd) common.error('must specify command');
59
60 var isWindows = isWindowsPlatform();
61 var pathArray = splitPath(process.env.PATH);
62
63 var queryMatches = [];
64
65 // No relative/absolute paths provided?
66 if (cmd.indexOf('/') === -1) {
67 // Assume that there are no extensions to append to queries (this is the
68 // case for unix)
69 var pathExtArray = [''];
70 if (isWindows) {
71 // In case the PATHEXT variable is somehow not set (e.g.
72 // child_process.spawn with an empty environment), use the XP default.
73 var pathExtEnv = process.env.PATHEXT || XP_DEFAULT_PATHEXT;
74 pathExtArray = splitPath(pathExtEnv.toUpperCase());
75 }
76
77 // Search for command in PATH
78 for (var k = 0; k < pathArray.length; k++) {
79 // already found it
80 if (queryMatches.length > 0 && !options.all) break;
81
82 var attempt = path.resolve(pathArray[k], cmd);
83
84 if (isWindows) {
85 attempt = attempt.toUpperCase();
86 }
87
88 var match = attempt.match(/\.[^<>:"/\|?*.]+$/);
89 if (match && pathExtArray.indexOf(match[0]) >= 0) { // this is Windows-only
90 // The user typed a query with the file extension, like
91 // `which('node.exe')`
92 if (checkPath(attempt)) {
93 queryMatches.push(attempt);
94 break;
95 }
96 } else { // All-platforms
97 // Cycle through the PATHEXT array, and check each extension
98 // Note: the array is always [''] on Unix
99 for (var i = 0; i < pathExtArray.length; i++) {
100 var ext = pathExtArray[i];
101 var newAttempt = attempt + ext;
102 if (checkPath(newAttempt)) {
103 queryMatches.push(newAttempt);
104 break;
105 }
106 }
107 }
108 }
109 } else if (checkPath(cmd)) { // a valid absolute or relative path
110 queryMatches.push(path.resolve(cmd));
111 }
112
113 if (queryMatches.length > 0) {
114 return options.all ? queryMatches : queryMatches[0];
115 }
116 return options.all ? [] : null;
117}
118module.exports = _which;