| [9af201e] | 1 | 'use strict';
|
|---|
| 2 |
|
|---|
| 3 | const path = require('path');
|
|---|
| 4 | const resolveCommand = require('./util/resolveCommand');
|
|---|
| 5 | const escape = require('./util/escape');
|
|---|
| 6 | const readShebang = require('./util/readShebang');
|
|---|
| 7 |
|
|---|
| 8 | const isWin = process.platform === 'win32';
|
|---|
| 9 | const isExecutableRegExp = /\.(?:com|exe)$/i;
|
|---|
| 10 | const isCmdShimRegExp = /node_modules[\\/].bin[\\/][^\\/]+\.cmd$/i;
|
|---|
| 11 |
|
|---|
| 12 | function detectShebang(parsed) {
|
|---|
| 13 | parsed.file = resolveCommand(parsed);
|
|---|
| 14 |
|
|---|
| 15 | const shebang = parsed.file && readShebang(parsed.file);
|
|---|
| 16 |
|
|---|
| 17 | if (shebang) {
|
|---|
| 18 | parsed.args.unshift(parsed.file);
|
|---|
| 19 | parsed.command = shebang;
|
|---|
| 20 |
|
|---|
| 21 | return resolveCommand(parsed);
|
|---|
| 22 | }
|
|---|
| 23 |
|
|---|
| 24 | return parsed.file;
|
|---|
| 25 | }
|
|---|
| 26 |
|
|---|
| 27 | function parseNonShell(parsed) {
|
|---|
| 28 | if (!isWin) {
|
|---|
| 29 | return parsed;
|
|---|
| 30 | }
|
|---|
| 31 |
|
|---|
| 32 | // Detect & add support for shebangs
|
|---|
| 33 | const commandFile = detectShebang(parsed);
|
|---|
| 34 |
|
|---|
| 35 | // We don't need a shell if the command filename is an executable
|
|---|
| 36 | const needsShell = !isExecutableRegExp.test(commandFile);
|
|---|
| 37 |
|
|---|
| 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);
|
|---|
| 46 |
|
|---|
| 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);
|
|---|
| 50 |
|
|---|
| 51 | // Escape command & arguments
|
|---|
| 52 | parsed.command = escape.command(parsed.command);
|
|---|
| 53 | parsed.args = parsed.args.map((arg) => escape.argument(arg, needsDoubleEscapeMetaChars));
|
|---|
| 54 |
|
|---|
| 55 | const shellCommand = [parsed.command].concat(parsed.args).join(' ');
|
|---|
| 56 |
|
|---|
| 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
|
|---|
| 60 | }
|
|---|
| 61 |
|
|---|
| 62 | return parsed;
|
|---|
| 63 | }
|
|---|
| 64 |
|
|---|
| 65 | function parse(command, args, options) {
|
|---|
| 66 | // Normalize arguments, similar to nodejs
|
|---|
| 67 | if (args && !Array.isArray(args)) {
|
|---|
| 68 | options = args;
|
|---|
| 69 | args = null;
|
|---|
| 70 | }
|
|---|
| 71 |
|
|---|
| 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
|
|---|
| 74 |
|
|---|
| 75 | // Build our parsed object
|
|---|
| 76 | const parsed = {
|
|---|
| 77 | command,
|
|---|
| 78 | args,
|
|---|
| 79 | options,
|
|---|
| 80 | file: undefined,
|
|---|
| 81 | original: {
|
|---|
| 82 | command,
|
|---|
| 83 | args,
|
|---|
| 84 | },
|
|---|
| 85 | };
|
|---|
| 86 |
|
|---|
| 87 | // Delegate further parsing to shell or non-shell
|
|---|
| 88 | return options.shell ? parsed : parseNonShell(parsed);
|
|---|
| 89 | }
|
|---|
| 90 |
|
|---|
| 91 | module.exports = parse;
|
|---|