| 'use strict'; |
| |
| var resolveCommand = require('./util/resolveCommand'); |
| var hasEmptyArgumentBug = require('./util/hasEmptyArgumentBug'); |
| var escapeArgument = require('./util/escapeArgument'); |
| var escapeCommand = require('./util/escapeCommand'); |
| var readShebang = require('./util/readShebang'); |
| |
| var isWin = process.platform === 'win32'; |
| var skipShellRegExp = /\.(?:com|exe)$/i; |
| |
| // Supported in Node >= 6 and >= 4.8 |
| var supportsShellOption = parseInt(process.version.substr(1).split('.')[0], 10) >= 6 || |
| parseInt(process.version.substr(1).split('.')[0], 10) === 4 && parseInt(process.version.substr(1).split('.')[1], 10) >= 8; |
| |
| function parseNonShell(parsed) { |
| var shebang; |
| var needsShell; |
| var applyQuotes; |
| |
| if (!isWin) { |
| return parsed; |
| } |
| |
| // Detect & add support for shebangs |
| parsed.file = resolveCommand(parsed.command); |
| parsed.file = parsed.file || resolveCommand(parsed.command, true); |
| shebang = parsed.file && readShebang(parsed.file); |
| |
| if (shebang) { |
| parsed.args.unshift(parsed.file); |
| parsed.command = shebang; |
| needsShell = hasEmptyArgumentBug || !skipShellRegExp.test(resolveCommand(shebang) || resolveCommand(shebang, true)); |
| } else { |
| needsShell = hasEmptyArgumentBug || !skipShellRegExp.test(parsed.file); |
| } |
| |
| // If a shell is required, use cmd.exe and take care of escaping everything correctly |
| if (needsShell) { |
| // Escape command & arguments |
| applyQuotes = (parsed.command !== 'echo'); // Do not quote arguments for the special "echo" command |
| parsed.command = escapeCommand(parsed.command); |
| parsed.args = parsed.args.map(function (arg) { |
| return escapeArgument(arg, applyQuotes); |
| }); |
| |
| // Make use of cmd.exe |
| parsed.args = ['/d', '/s', '/c', '"' + parsed.command + (parsed.args.length ? ' ' + parsed.args.join(' ') : '') + '"']; |
| 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) { |
| var shellCommand; |
| |
| // 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 |
| 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) { |
| var parsed; |
| |
| // 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 = options || {}; |
| |
| // Build our parsed object |
| parsed = { |
| command: command, |
| args: args, |
| options: options, |
| file: undefined, |
| original: command, |
| }; |
| |
| // Delegate further parsing to shell or non-shell |
| return options.shell ? parseShell(parsed) : parseNonShell(parsed); |
| } |
| |
| module.exports = parse; |