You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							125 lines
						
					
					
						
							4.3 KiB
						
					
					
				
			
		
		
	
	
							125 lines
						
					
					
						
							4.3 KiB
						
					
					
				'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;
 | 
						|
 |