├── test ├── executable-script.cmd ├── executable-script.js ├── non-executable-script.js └── test.js ├── index.js ├── .travis.yml ├── .jshintrc ├── appveyor.yml ├── .gitignore ├── package.json ├── LICENSE ├── README.md └── lib └── command-exists.js /test/executable-script.cmd: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/executable-script.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/non-executable-script.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/command-exists'); 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "4" 4 | - "6" 5 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "curly": true, 3 | "eqeqeq": true, 4 | "immed": true, 5 | "latedef": true, 6 | "newcap": true, 7 | "noarg": true, 8 | "sub": true, 9 | "undef": true, 10 | "unused": true, 11 | "boss": true, 12 | "eqnull": true, 13 | "node": true 14 | } 15 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | init: 2 | # Get the latest stable version of Node.js 3 | - ps: Install-Product node $env:nodejs_version 4 | 5 | image: 6 | - Visual Studio 2017 7 | 8 | matrix: 9 | fast_finish: true 10 | 11 | environment: 12 | matrix: 13 | - nodejs_version: "4" 14 | - nodejs_version: "6" 15 | - nodejs_version: "7" 16 | - nodejs_version: "8" 17 | - nodejs_version: "9" 18 | 19 | install: 20 | # install modules 21 | - npm install 22 | 23 | # Post-install test scripts. 24 | test_script: 25 | # Output useful info for debugging. 26 | - node --version 27 | - npm --version 28 | - npm run test 29 | 30 | # Don't actually build. 31 | build: off -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Commenting this out is preferred by some people, see 24 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 25 | node_modules 26 | 27 | # Users Environment Variables 28 | .lock-wscript 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "command-exists", 3 | "version": "1.2.9", 4 | "description": "check whether a command line command exists in the current environment", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha test/test.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "http://github.com/mathisonian/command-exists" 12 | }, 13 | "keywords": [ 14 | "cli", 15 | "command", 16 | "exists" 17 | ], 18 | "author": "Matthew Conlen", 19 | "contributors": [ 20 | "Arthur Silber (https://arthursilber.de)" 21 | ], 22 | "license": "MIT", 23 | "bugs": { 24 | "url": "https://github.com/mathisonian/command-exists/issues" 25 | }, 26 | "homepage": "https://github.com/mathisonian/command-exists", 27 | "devDependencies": { 28 | "expect.js": "^0.3.1", 29 | "jshint": "^2.9.1", 30 | "mocha": "^2.5.3" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Matthew Conlen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | command-exists 2 | ============== 3 | 4 | node module to check if a command-line command exists 5 | 6 | 7 | 8 | ## installation 9 | 10 | ```bash 11 | npm install command-exists 12 | ``` 13 | 14 | ## usage 15 | 16 | ### async 17 | 18 | ```js 19 | var commandExists = require('command-exists'); 20 | 21 | commandExists('ls', function(err, commandExists) { 22 | 23 | if(commandExists) { 24 | // proceed confidently knowing this command is available 25 | } 26 | 27 | }); 28 | ``` 29 | ### promise 30 | ```js 31 | var commandExists = require('command-exists'); 32 | 33 | // invoked without a callback, it returns a promise 34 | commandExists('ls') 35 | .then(function(command){ 36 | // proceed 37 | }).catch(function(){ 38 | // command doesn't exist 39 | }); 40 | ``` 41 | 42 | ### sync 43 | ```js 44 | var commandExistsSync = require('command-exists').sync; 45 | // returns true/false; doesn't throw 46 | if (commandExistsSync('ls')) { 47 | // proceed 48 | } else { 49 | // ... 50 | } 51 | 52 | ``` 53 | 54 | 55 | ## changelog 56 | 57 | 58 | ### v1.2.9 59 | 60 | Fix issue with absolute paths on Windows ([#24](https://github.com/mathisonian/command-exists/pull/24)) 61 | 62 | ### v1.2.8 63 | 64 | Fix issue with paths on Windows ([#21](https://github.com/mathisonian/command-exists/pull/21)) 65 | 66 | ### v1.2.7 67 | 68 | Removes unnecessary printed output on windows. 69 | 70 | ### v1.2.6 71 | 72 | Small bugfixes. 73 | 74 | ### v1.2.5 75 | 76 | Fix windows bug introduced in 1.2.4. 77 | 78 | ### v1.2.4 79 | 80 | Fix potential security issue. 81 | 82 | ### v1.2.0 83 | 84 | Add support for promises 85 | 86 | ### v1.1.0 87 | 88 | Add synchronous version 89 | 90 | ### v1.0.2 91 | 92 | Support for windows 93 | -------------------------------------------------------------------------------- /lib/command-exists.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var exec = require('child_process').exec; 4 | var execSync = require('child_process').execSync; 5 | var fs = require('fs'); 6 | var path = require('path'); 7 | var access = fs.access; 8 | var accessSync = fs.accessSync; 9 | var constants = fs.constants || fs; 10 | 11 | var isUsingWindows = process.platform == 'win32' 12 | 13 | var fileNotExists = function(commandName, callback){ 14 | access(commandName, constants.F_OK, 15 | function(err){ 16 | callback(!err); 17 | }); 18 | }; 19 | 20 | var fileNotExistsSync = function(commandName){ 21 | try{ 22 | accessSync(commandName, constants.F_OK); 23 | return false; 24 | }catch(e){ 25 | return true; 26 | } 27 | }; 28 | 29 | var localExecutable = function(commandName, callback){ 30 | access(commandName, constants.F_OK | constants.X_OK, 31 | function(err){ 32 | callback(null, !err); 33 | }); 34 | }; 35 | 36 | var localExecutableSync = function(commandName){ 37 | try{ 38 | accessSync(commandName, constants.F_OK | constants.X_OK); 39 | return true; 40 | }catch(e){ 41 | return false; 42 | } 43 | } 44 | 45 | var commandExistsUnix = function(commandName, cleanedCommandName, callback) { 46 | 47 | fileNotExists(commandName, function(isFile){ 48 | 49 | if(!isFile){ 50 | var child = exec('command -v ' + cleanedCommandName + 51 | ' 2>/dev/null' + 52 | ' && { echo >&1 ' + cleanedCommandName + '; exit 0; }', 53 | function (error, stdout, stderr) { 54 | callback(null, !!stdout); 55 | }); 56 | return; 57 | } 58 | 59 | localExecutable(commandName, callback); 60 | }); 61 | 62 | } 63 | 64 | var commandExistsWindows = function(commandName, cleanedCommandName, callback) { 65 | // Regex from Julio from: https://stackoverflow.com/questions/51494579/regex-windows-path-validator 66 | if (!(/^(?!(?:.*\s|.*\.|\W+)$)(?:[a-zA-Z]:)?(?:(?:[^<>:"\|\?\*\n])+(?:\/\/|\/|\\\\|\\)?)+$/m.test(commandName))) { 67 | callback(null, false); 68 | return; 69 | } 70 | var child = exec('where ' + cleanedCommandName, 71 | function (error) { 72 | if (error !== null){ 73 | callback(null, false); 74 | } else { 75 | callback(null, true); 76 | } 77 | } 78 | ) 79 | } 80 | 81 | var commandExistsUnixSync = function(commandName, cleanedCommandName) { 82 | if(fileNotExistsSync(commandName)){ 83 | try { 84 | var stdout = execSync('command -v ' + cleanedCommandName + 85 | ' 2>/dev/null' + 86 | ' && { echo >&1 ' + cleanedCommandName + '; exit 0; }' 87 | ); 88 | return !!stdout; 89 | } catch (error) { 90 | return false; 91 | } 92 | } 93 | return localExecutableSync(commandName); 94 | } 95 | 96 | var commandExistsWindowsSync = function(commandName, cleanedCommandName, callback) { 97 | // Regex from Julio from: https://stackoverflow.com/questions/51494579/regex-windows-path-validator 98 | if (!(/^(?!(?:.*\s|.*\.|\W+)$)(?:[a-zA-Z]:)?(?:(?:[^<>:"\|\?\*\n])+(?:\/\/|\/|\\\\|\\)?)+$/m.test(commandName))) { 99 | return false; 100 | } 101 | try { 102 | var stdout = execSync('where ' + cleanedCommandName, {stdio: []}); 103 | return !!stdout; 104 | } catch (error) { 105 | return false; 106 | } 107 | } 108 | 109 | var cleanInput = function(s) { 110 | if (/[^A-Za-z0-9_\/:=-]/.test(s)) { 111 | s = "'"+s.replace(/'/g,"'\\''")+"'"; 112 | s = s.replace(/^(?:'')+/g, '') // unduplicate single-quote at the beginning 113 | .replace(/\\'''/g, "\\'" ); // remove non-escaped single-quote if there are enclosed between 2 escaped 114 | } 115 | return s; 116 | } 117 | 118 | if (isUsingWindows) { 119 | cleanInput = function(s) { 120 | var isPathName = /[\\]/.test(s); 121 | if (isPathName) { 122 | var dirname = '"' + path.dirname(s) + '"'; 123 | var basename = '"' + path.basename(s) + '"'; 124 | return dirname + ':' + basename; 125 | } 126 | return '"' + s + '"'; 127 | } 128 | } 129 | 130 | module.exports = function commandExists(commandName, callback) { 131 | var cleanedCommandName = cleanInput(commandName); 132 | if (!callback && typeof Promise !== 'undefined') { 133 | return new Promise(function(resolve, reject){ 134 | commandExists(commandName, function(error, output) { 135 | if (output) { 136 | resolve(commandName); 137 | } else { 138 | reject(error); 139 | } 140 | }); 141 | }); 142 | } 143 | if (isUsingWindows) { 144 | commandExistsWindows(commandName, cleanedCommandName, callback); 145 | } else { 146 | commandExistsUnix(commandName, cleanedCommandName, callback); 147 | } 148 | }; 149 | 150 | module.exports.sync = function(commandName) { 151 | var cleanedCommandName = cleanInput(commandName); 152 | if (isUsingWindows) { 153 | return commandExistsWindowsSync(commandName, cleanedCommandName); 154 | } else { 155 | return commandExistsUnixSync(commandName, cleanedCommandName); 156 | } 157 | }; 158 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var expect = require('expect.js'); 4 | var commandExists = require('..'); 5 | var commandExistsSync = commandExists.sync; 6 | var resolve = require('path').resolve; 7 | var isUsingWindows = process.platform == 'win32' 8 | 9 | describe('commandExists', function(){ 10 | describe('async - callback', function() { 11 | it('it should find a command named ls or xcopy', function(done){ 12 | var commandToUse = 'ls' 13 | if (isUsingWindows) { 14 | commandToUse = 'xcopy' 15 | } 16 | 17 | commandExists(commandToUse, function(err, exists) { 18 | expect(err).to.be(null); 19 | expect(exists).to.be(true); 20 | done(); 21 | }); 22 | }); 23 | 24 | it('it should not find a command named fdsafdsafdsafdsafdsa', function(done){ 25 | commandExists('fdsafdsafdsafdsafdsa', function(err, exists) { 26 | expect(err).to.be(null); 27 | expect(exists).to.be(false); 28 | done(); 29 | }); 30 | }); 31 | }); 32 | 33 | describe('async - promise', function() { 34 | it('it should find a command named ls or xcopy', function(done){ 35 | var commandToUse = 'ls' 36 | if (isUsingWindows) { 37 | commandToUse = 'xcopy' 38 | } 39 | 40 | commandExists(commandToUse) 41 | .then(function(command) { 42 | expect(command).to.be(commandToUse); 43 | done(); 44 | }); 45 | }); 46 | 47 | it('it should not find a command named fdsafdsafdsafdsafdsa', function(done){ 48 | commandExists('fdsafdsafdsafdsafdsa') 49 | .then(function() { 50 | // We should not execute this line. 51 | expect(true).to.be(false); 52 | }) 53 | .catch(function() { 54 | done(); 55 | }); 56 | }); 57 | }); 58 | 59 | describe('sync', function() { 60 | it('it should find a command named ls or xcopy', function(){ 61 | var commandToUse = 'ls' 62 | if (isUsingWindows) { 63 | commandToUse = 'xcopy' 64 | } 65 | expect(commandExistsSync(commandToUse)).to.be(true); 66 | }); 67 | 68 | it('it should not find a command named fdsafdsafdsafdsafdsa', function(){ 69 | expect(commandExistsSync('fdsafdsafdsafdsafdsa')).to.be(false); 70 | }); 71 | 72 | it('it should not find a command named ls or xcopy prefixed with some nonsense', function(){ 73 | var commandToUse = 'fdsafdsa ls' 74 | if (isUsingWindows) { 75 | commandToUse = 'fdsafdsaf xcopy' 76 | } 77 | expect(commandExistsSync(commandToUse)).to.be(false); 78 | }); 79 | 80 | it('it should not execute some nefarious code', function(){ 81 | expect(commandExistsSync('ls; touch /tmp/foo0')).to.be(false); 82 | }); 83 | 84 | it('it should not execute some nefarious code', function(){ 85 | expect(commandExistsSync('ls touch /tmp/foo0')).to.be(false); 86 | }); 87 | }); 88 | 89 | describe('local file', function() { 90 | it('it should report false if there is a non-executable file with that name', function(done) { 91 | var commandToUse = 'test/non-executable-script.js' 92 | commandExists(commandToUse) 93 | .then(function(command){ 94 | // We should not execute this line. 95 | expect(true).to.be(false); 96 | }).catch(function(err){ 97 | expect(err).to.be(null); 98 | done(); 99 | }); 100 | }); 101 | 102 | 103 | if (!isUsingWindows) { 104 | it('it should report true if there is an executable file with that name', function(done) { 105 | var commandToUse = 'test/executable-script.js' 106 | commandExists(commandToUse) 107 | .then(function(command){ 108 | // We should not execute this line. 109 | expect(command).to.be(commandToUse); 110 | done(); 111 | }); 112 | }); 113 | } 114 | 115 | if (isUsingWindows) { 116 | it('it should report true if there is an executable file with that name', function(done) { 117 | var commandToUse = 'test\\executable-script.cmd' 118 | commandExists(commandToUse) 119 | .then(function(command){ 120 | expect(command).to.be(commandToUse); 121 | done(); 122 | }); 123 | }); 124 | 125 | it('it should report false if there is a double quotation mark in the file path', function() { 126 | var commandToUse = 'test\\"executable-script.cmd' 127 | expect(commandExists.sync(commandToUse)).to.be(false); 128 | }); 129 | } 130 | }); 131 | 132 | describe('absolute path', function() { 133 | it('it should report true if there is a command with that name in absolute path', function(done) { 134 | var commandToUse = resolve('test/executable-script.js'); 135 | commandExists(commandToUse) 136 | .then(function(command){ 137 | expect(command).to.be(commandToUse); 138 | done(); 139 | }); 140 | }); 141 | 142 | it('it should report false if there is not a command with that name in absolute path', function() { 143 | var commandToUse = resolve('executable-script.js'); 144 | expect(commandExists.sync(commandToUse)).to.be(false); 145 | }); 146 | }); 147 | }); 148 | --------------------------------------------------------------------------------