3 const path = require('path');
4 const resolveCommand = require('./util/resolveCommand');
5 const escape = require('./util/escape');
6 const readShebang = require('./util/readShebang');
8 const isWin = process.platform === 'win32';
9 const isExecutableRegExp = /\.(?:com|exe)$/i;
10 const isCmdShimRegExp = /node_modules[\\/].bin[\\/][^\\/]+\.cmd$/i;
12 function detectShebang(parsed) {
13 parsed.file = resolveCommand(parsed);
15 const shebang = parsed.file && readShebang(parsed.file);
18 parsed.args.unshift(parsed.file);
19 parsed.command = shebang;
21 return resolveCommand(parsed);
27 function parseNonShell(parsed) {
32 // Detect & add support for shebangs
33 const commandFile = detectShebang(parsed);
35 // We don't need a shell if the command filename is an executable
36 const needsShell = !isExecutableRegExp.test(commandFile);
38 // If a shell is required, use cmd.exe and take care of escaping everything correctly
39 // Note that `forceShell` is an hidden option used only in tests
40 if (parsed.options.forceShell || needsShell) {
41 // Need to double escape meta chars if the command is a cmd-shim located in `node_modules/.bin/`
42 // The cmd-shim simply calls execute the package bin file with NodeJS, proxying any argument
43 // Because the escape of metachars with ^ gets interpreted when the cmd.exe is first called,
44 // we need to double escape them
45 const needsDoubleEscapeMetaChars = isCmdShimRegExp.test(commandFile);
47 // Normalize posix paths into OS compatible paths (e.g.: foo/bar -> foo\bar)
48 // This is necessary otherwise it will always fail with ENOENT in those cases
49 parsed.command = path.normalize(parsed.command);
51 // Escape command & arguments
52 parsed.command = escape.command(parsed.command);
53 parsed.args = parsed.args.map((arg) => escape.argument(arg, needsDoubleEscapeMetaChars));
55 const shellCommand = [parsed.command].concat(parsed.args).join(' ');
57 parsed.args = ['/d', '/s', '/c', `"${shellCommand}"`];
58 parsed.command = process.env.comspec || 'cmd.exe';
59 parsed.options.windowsVerbatimArguments = true; // Tell node's spawn that the arguments are already escaped
65 function parse(command, args, options) {
66 // Normalize arguments, similar to nodejs
67 if (args && !Array.isArray(args)) {
72 args = args ? args.slice(0) : []; // Clone array to avoid changing the original
73 options = Object.assign({}, options); // Clone object to avoid changing the original
75 // Build our parsed object
87 // Delegate further parsing to shell or non-shell
88 return options.shell ? parsed : parseNonShell(parsed);
91 module.exports = parse;