'use strict'; var exec = require('child_process').exec; var execSync = require('child_process').execSync; var fs = require('fs'); var path = require('path'); var access = fs.access; var accessSync = fs.accessSync; var constants = fs.constants || fs; var isUsingWindows = process.platform == 'win32' var fileNotExists = function(commandName, callback){ access(commandName, constants.F_OK, function(err){ callback(!err); }); }; var fileNotExistsSync = function(commandName){ try{ accessSync(commandName, constants.F_OK); return false; }catch(e){ return true; } }; var localExecutable = function(commandName, callback){ access(commandName, constants.F_OK | constants.X_OK, function(err){ callback(null, !err); }); }; var localExecutableSync = function(commandName){ try{ accessSync(commandName, constants.F_OK | constants.X_OK); return true; }catch(e){ return false; } } var commandExistsUnix = function(commandName, cleanedCommandName, callback) { fileNotExists(commandName, function(isFile){ if(!isFile){ var child = exec('command -v ' + cleanedCommandName + ' 2>/dev/null' + ' && { echo >&1 ' + cleanedCommandName + '; exit 0; }', function (error, stdout, stderr) { callback(null, !!stdout); }); return; } localExecutable(commandName, callback); }); } var commandExistsWindows = function(commandName, cleanedCommandName, callback) { // Regex from Julio from: https://stackoverflow.com/questions/51494579/regex-windows-path-validator if (!(/^(?!(?:.*\s|.*\.|\W+)$)(?:[a-zA-Z]:)?(?:(?:[^<>:"\|\?\*\n])+(?:\/\/|\/|\\\\|\\)?)+$/m.test(commandName))) { callback(null, false); return; } var child = exec('where ' + cleanedCommandName, function (error) { if (error !== null){ callback(null, false); } else { callback(null, true); } } ) } var commandExistsUnixSync = function(commandName, cleanedCommandName) { if(fileNotExistsSync(commandName)){ try { var stdout = execSync('command -v ' + cleanedCommandName + ' 2>/dev/null' + ' && { echo >&1 ' + cleanedCommandName + '; exit 0; }' ); return !!stdout; } catch (error) { return false; } } return localExecutableSync(commandName); } var commandExistsWindowsSync = function(commandName, cleanedCommandName, callback) { // Regex from Julio from: https://stackoverflow.com/questions/51494579/regex-windows-path-validator if (!(/^(?!(?:.*\s|.*\.|\W+)$)(?:[a-zA-Z]:)?(?:(?:[^<>:"\|\?\*\n])+(?:\/\/|\/|\\\\|\\)?)+$/m.test(commandName))) { return false; } try { var stdout = execSync('where ' + cleanedCommandName, {stdio: []}); return !!stdout; } catch (error) { return false; } } var cleanInput = function(s) { if (/[^A-Za-z0-9_\/:=-]/.test(s)) { s = "'"+s.replace(/'/g,"'\\''")+"'"; s = s.replace(/^(?:'')+/g, '') // unduplicate single-quote at the beginning .replace(/\\'''/g, "\\'" ); // remove non-escaped single-quote if there are enclosed between 2 escaped } return s; } if (isUsingWindows) { cleanInput = function(s) { var isPathName = /[\\]/.test(s); if (isPathName) { var dirname = '"' + path.dirname(s) + '"'; var basename = '"' + path.basename(s) + '"'; return dirname + ':' + basename; } return '"' + s + '"'; } } module.exports = function commandExists(commandName, callback) { var cleanedCommandName = cleanInput(commandName); if (!callback && typeof Promise !== 'undefined') { return new Promise(function(resolve, reject){ commandExists(commandName, function(error, output) { if (output) { resolve(commandName); } else { reject(error); } }); }); } if (isUsingWindows) { commandExistsWindows(commandName, cleanedCommandName, callback); } else { commandExistsUnix(commandName, cleanedCommandName, callback); } }; module.exports.sync = function(commandName) { var cleanedCommandName = cleanInput(commandName); if (isUsingWindows) { return commandExistsWindowsSync(commandName, cleanedCommandName); } else { return commandExistsUnixSync(commandName, cleanedCommandName); } };