Projekt

Obecné

Profil

Stáhnout (4.31 KB) Statistiky
| Větev: | Revize:
1
'use strict';
2

    
3
const path = require('path');
4
const niceTry = require('nice-try');
5
const resolveCommand = require('./util/resolveCommand');
6
const escape = require('./util/escape');
7
const readShebang = require('./util/readShebang');
8
const semver = require('semver');
9

    
10
const isWin = process.platform === 'win32';
11
const isExecutableRegExp = /\.(?:com|exe)$/i;
12
const isCmdShimRegExp = /node_modules[\\/].bin[\\/][^\\/]+\.cmd$/i;
13

    
14
// `options.shell` is supported in Node ^4.8.0, ^5.7.0 and >= 6.0.0
15
const supportsShellOption = niceTry(() => semver.satisfies(process.version, '^4.8.0 || ^5.7.0 || >= 6.0.0', true)) || false;
16

    
17
function detectShebang(parsed) {
18
    parsed.file = resolveCommand(parsed);
19

    
20
    const shebang = parsed.file && readShebang(parsed.file);
21

    
22
    if (shebang) {
23
        parsed.args.unshift(parsed.file);
24
        parsed.command = shebang;
25

    
26
        return resolveCommand(parsed);
27
    }
28

    
29
    return parsed.file;
30
}
31

    
32
function parseNonShell(parsed) {
33
    if (!isWin) {
34
        return parsed;
35
    }
36

    
37
    // Detect & add support for shebangs
38
    const commandFile = detectShebang(parsed);
39

    
40
    // We don't need a shell if the command filename is an executable
41
    const needsShell = !isExecutableRegExp.test(commandFile);
42

    
43
    // If a shell is required, use cmd.exe and take care of escaping everything correctly
44
    // Note that `forceShell` is an hidden option used only in tests
45
    if (parsed.options.forceShell || needsShell) {
46
        // Need to double escape meta chars if the command is a cmd-shim located in `node_modules/.bin/`
47
        // The cmd-shim simply calls execute the package bin file with NodeJS, proxying any argument
48
        // Because the escape of metachars with ^ gets interpreted when the cmd.exe is first called,
49
        // we need to double escape them
50
        const needsDoubleEscapeMetaChars = isCmdShimRegExp.test(commandFile);
51

    
52
        // Normalize posix paths into OS compatible paths (e.g.: foo/bar -> foo\bar)
53
        // This is necessary otherwise it will always fail with ENOENT in those cases
54
        parsed.command = path.normalize(parsed.command);
55

    
56
        // Escape command & arguments
57
        parsed.command = escape.command(parsed.command);
58
        parsed.args = parsed.args.map((arg) => escape.argument(arg, needsDoubleEscapeMetaChars));
59

    
60
        const shellCommand = [parsed.command].concat(parsed.args).join(' ');
61

    
62
        parsed.args = ['/d', '/s', '/c', `"${shellCommand}"`];
63
        parsed.command = process.env.comspec || 'cmd.exe';
64
        parsed.options.windowsVerbatimArguments = true; // Tell node's spawn that the arguments are already escaped
65
    }
66

    
67
    return parsed;
68
}
69

    
70
function parseShell(parsed) {
71
    // If node supports the shell option, there's no need to mimic its behavior
72
    if (supportsShellOption) {
73
        return parsed;
74
    }
75

    
76
    // Mimic node shell option
77
    // See https://github.com/nodejs/node/blob/b9f6a2dc059a1062776133f3d4fd848c4da7d150/lib/child_process.js#L335
78
    const shellCommand = [parsed.command].concat(parsed.args).join(' ');
79

    
80
    if (isWin) {
81
        parsed.command = typeof parsed.options.shell === 'string' ? parsed.options.shell : process.env.comspec || 'cmd.exe';
82
        parsed.args = ['/d', '/s', '/c', `"${shellCommand}"`];
83
        parsed.options.windowsVerbatimArguments = true; // Tell node's spawn that the arguments are already escaped
84
    } else {
85
        if (typeof parsed.options.shell === 'string') {
86
            parsed.command = parsed.options.shell;
87
        } else if (process.platform === 'android') {
88
            parsed.command = '/system/bin/sh';
89
        } else {
90
            parsed.command = '/bin/sh';
91
        }
92

    
93
        parsed.args = ['-c', shellCommand];
94
    }
95

    
96
    return parsed;
97
}
98

    
99
function parse(command, args, options) {
100
    // Normalize arguments, similar to nodejs
101
    if (args && !Array.isArray(args)) {
102
        options = args;
103
        args = null;
104
    }
105

    
106
    args = args ? args.slice(0) : []; // Clone array to avoid changing the original
107
    options = Object.assign({}, options); // Clone object to avoid changing the original
108

    
109
    // Build our parsed object
110
    const parsed = {
111
        command,
112
        args,
113
        options,
114
        file: undefined,
115
        original: {
116
            command,
117
            args,
118
        },
119
    };
120

    
121
    // Delegate further parsing to shell or non-shell
122
    return options.shell ? parseShell(parsed) : parseNonShell(parsed);
123
}
124

    
125
module.exports = parse;
(2-2/2)