126 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			126 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 
								 | 
							
								'use strict';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const path = require('path');
							 | 
						||
| 
								 | 
							
								const niceTry = require('nice-try');
							 | 
						||
| 
								 | 
							
								const resolveCommand = require('./util/resolveCommand');
							 | 
						||
| 
								 | 
							
								const escape = require('./util/escape');
							 | 
						||
| 
								 | 
							
								const readShebang = require('./util/readShebang');
							 | 
						||
| 
								 | 
							
								const semver = require('semver');
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const isWin = process.platform === 'win32';
							 | 
						||
| 
								 | 
							
								const isExecutableRegExp = /\.(?:com|exe)$/i;
							 | 
						||
| 
								 | 
							
								const isCmdShimRegExp = /node_modules[\\/].bin[\\/][^\\/]+\.cmd$/i;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// `options.shell` is supported in Node ^4.8.0, ^5.7.0 and >= 6.0.0
							 | 
						||
| 
								 | 
							
								const supportsShellOption = niceTry(() => semver.satisfies(process.version, '^4.8.0 || ^5.7.0 || >= 6.0.0', true)) || false;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function detectShebang(parsed) {
							 | 
						||
| 
								 | 
							
								    parsed.file = resolveCommand(parsed);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    const shebang = parsed.file && readShebang(parsed.file);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (shebang) {
							 | 
						||
| 
								 | 
							
								        parsed.args.unshift(parsed.file);
							 | 
						||
| 
								 | 
							
								        parsed.command = shebang;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return resolveCommand(parsed);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return parsed.file;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function parseNonShell(parsed) {
							 | 
						||
| 
								 | 
							
								    if (!isWin) {
							 | 
						||
| 
								 | 
							
								        return parsed;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Detect & add support for shebangs
							 | 
						||
| 
								 | 
							
								    const commandFile = detectShebang(parsed);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // We don't need a shell if the command filename is an executable
							 | 
						||
| 
								 | 
							
								    const needsShell = !isExecutableRegExp.test(commandFile);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // If a shell is required, use cmd.exe and take care of escaping everything correctly
							 | 
						||
| 
								 | 
							
								    // Note that `forceShell` is an hidden option used only in tests
							 | 
						||
| 
								 | 
							
								    if (parsed.options.forceShell || needsShell) {
							 | 
						||
| 
								 | 
							
								        // Need to double escape meta chars if the command is a cmd-shim located in `node_modules/.bin/`
							 | 
						||
| 
								 | 
							
								        // The cmd-shim simply calls execute the package bin file with NodeJS, proxying any argument
							 | 
						||
| 
								 | 
							
								        // Because the escape of metachars with ^ gets interpreted when the cmd.exe is first called,
							 | 
						||
| 
								 | 
							
								        // we need to double escape them
							 | 
						||
| 
								 | 
							
								        const needsDoubleEscapeMetaChars = isCmdShimRegExp.test(commandFile);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        // Normalize posix paths into OS compatible paths (e.g.: foo/bar -> foo\bar)
							 | 
						||
| 
								 | 
							
								        // This is necessary otherwise it will always fail with ENOENT in those cases
							 | 
						||
| 
								 | 
							
								        parsed.command = path.normalize(parsed.command);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        // Escape command & arguments
							 | 
						||
| 
								 | 
							
								        parsed.command = escape.command(parsed.command);
							 | 
						||
| 
								 | 
							
								        parsed.args = parsed.args.map((arg) => escape.argument(arg, needsDoubleEscapeMetaChars));
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        const shellCommand = [parsed.command].concat(parsed.args).join(' ');
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        parsed.args = ['/d', '/s', '/c', `"${shellCommand}"`];
							 | 
						||
| 
								 | 
							
								        parsed.command = process.env.comspec || 'cmd.exe';
							 | 
						||
| 
								 | 
							
								        parsed.options.windowsVerbatimArguments = true; // Tell node's spawn that the arguments are already escaped
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return parsed;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function parseShell(parsed) {
							 | 
						||
| 
								 | 
							
								    // If node supports the shell option, there's no need to mimic its behavior
							 | 
						||
| 
								 | 
							
								    if (supportsShellOption) {
							 | 
						||
| 
								 | 
							
								        return parsed;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Mimic node shell option
							 | 
						||
| 
								 | 
							
								    // See https://github.com/nodejs/node/blob/b9f6a2dc059a1062776133f3d4fd848c4da7d150/lib/child_process.js#L335
							 | 
						||
| 
								 | 
							
								    const shellCommand = [parsed.command].concat(parsed.args).join(' ');
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (isWin) {
							 | 
						||
| 
								 | 
							
								        parsed.command = typeof parsed.options.shell === 'string' ? parsed.options.shell : process.env.comspec || 'cmd.exe';
							 | 
						||
| 
								 | 
							
								        parsed.args = ['/d', '/s', '/c', `"${shellCommand}"`];
							 | 
						||
| 
								 | 
							
								        parsed.options.windowsVerbatimArguments = true; // Tell node's spawn that the arguments are already escaped
							 | 
						||
| 
								 | 
							
								    } else {
							 | 
						||
| 
								 | 
							
								        if (typeof parsed.options.shell === 'string') {
							 | 
						||
| 
								 | 
							
								            parsed.command = parsed.options.shell;
							 | 
						||
| 
								 | 
							
								        } else if (process.platform === 'android') {
							 | 
						||
| 
								 | 
							
								            parsed.command = '/system/bin/sh';
							 | 
						||
| 
								 | 
							
								        } else {
							 | 
						||
| 
								 | 
							
								            parsed.command = '/bin/sh';
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        parsed.args = ['-c', shellCommand];
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return parsed;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function parse(command, args, options) {
							 | 
						||
| 
								 | 
							
								    // Normalize arguments, similar to nodejs
							 | 
						||
| 
								 | 
							
								    if (args && !Array.isArray(args)) {
							 | 
						||
| 
								 | 
							
								        options = args;
							 | 
						||
| 
								 | 
							
								        args = null;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    args = args ? args.slice(0) : []; // Clone array to avoid changing the original
							 | 
						||
| 
								 | 
							
								    options = Object.assign({}, options); // Clone object to avoid changing the original
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Build our parsed object
							 | 
						||
| 
								 | 
							
								    const parsed = {
							 | 
						||
| 
								 | 
							
								        command,
							 | 
						||
| 
								 | 
							
								        args,
							 | 
						||
| 
								 | 
							
								        options,
							 | 
						||
| 
								 | 
							
								        file: undefined,
							 | 
						||
| 
								 | 
							
								        original: {
							 | 
						||
| 
								 | 
							
								            command,
							 | 
						||
| 
								 | 
							
								            args,
							 | 
						||
| 
								 | 
							
								        },
							 | 
						||
| 
								 | 
							
								    };
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Delegate further parsing to shell or non-shell
							 | 
						||
| 
								 | 
							
								    return options.shell ? parseShell(parsed) : parseNonShell(parsed);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								module.exports = parse;
							 |