UNPKG

3.06 kBJavaScriptView Raw
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;