• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2
3const path = require('path');
4const resolveCommand = require('./util/resolveCommand');
5const escape = require('./util/escape');
6const readShebang = require('./util/readShebang');
7
8const isWin = process.platform === 'win32';
9const isExecutableRegExp = /\.(?:com|exe)$/i;
10const isCmdShimRegExp = /node_modules[\\/].bin[\\/][^\\/]+\.cmd$/i;
11
12function 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
27function 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
65function 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
91module.exports = parse;
92