utils/node_modules/@rushstack/node-core-library/lib/Executable.js
2024-02-07 01:33:07 -05:00

680 lines
31 KiB
JavaScript

"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __asyncValues = (this && this.__asyncValues) || function (o) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var m = o[Symbol.asyncIterator], i;
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Executable = exports.parseProcessListOutput = exports.parseProcessListOutputAsync = void 0;
const child_process = __importStar(require("child_process"));
const os = __importStar(require("os"));
const path = __importStar(require("path"));
const EnvironmentMap_1 = require("./EnvironmentMap");
const FileSystem_1 = require("./FileSystem");
const PosixModeBits_1 = require("./PosixModeBits");
const Text_1 = require("./Text");
const InternalError_1 = require("./InternalError");
async function parseProcessListOutputAsync(stream) {
var _a, e_1, _b, _c;
const processInfoById = new Map();
let seenHeaders = false;
try {
for (var _d = true, _e = __asyncValues(Text_1.Text.readLinesFromIterableAsync(stream, { ignoreEmptyLines: true })), _f; _f = await _e.next(), _a = _f.done, !_a;) {
_c = _f.value;
_d = false;
try {
const line = _c;
if (!seenHeaders) {
seenHeaders = true;
}
else {
parseProcessInfoEntry(line, processInfoById);
}
}
finally {
_d = true;
}
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (!_d && !_a && (_b = _e.return)) await _b.call(_e);
}
finally { if (e_1) throw e_1.error; }
}
return processInfoById;
}
exports.parseProcessListOutputAsync = parseProcessListOutputAsync;
// eslint-disable-next-line @rushstack/no-new-null
function parseProcessListOutput(output) {
const processInfoById = new Map();
let seenHeaders = false;
for (const line of Text_1.Text.readLinesFromIterable(output, { ignoreEmptyLines: true })) {
if (!seenHeaders) {
seenHeaders = true;
}
else {
parseProcessInfoEntry(line, processInfoById);
}
}
return processInfoById;
}
exports.parseProcessListOutput = parseProcessListOutput;
// win32 format:
// Name ParentProcessId ProcessId
// process name 1234 5678
// unix format:
// COMMAND PPID PID
// process name 51234 56784
const NAME_GROUP = 'name';
const PROCESS_ID_GROUP = 'pid';
const PARENT_PROCESS_ID_GROUP = 'ppid';
// eslint-disable-next-line @rushstack/security/no-unsafe-regexp
const PROCESS_LIST_ENTRY_REGEX = new RegExp(`^(?<${NAME_GROUP}>.+?)\\s+(?<${PARENT_PROCESS_ID_GROUP}>\\d+)\\s+(?<${PROCESS_ID_GROUP}>\\d+)\\s*$`);
function parseProcessInfoEntry(line, existingProcessInfoById) {
const match = line.match(PROCESS_LIST_ENTRY_REGEX);
if (!(match === null || match === void 0 ? void 0 : match.groups)) {
throw new InternalError_1.InternalError(`Invalid process list entry: ${line}`);
}
const processName = match.groups[NAME_GROUP];
const processId = parseInt(match.groups[PROCESS_ID_GROUP], 10);
const parentProcessId = parseInt(match.groups[PARENT_PROCESS_ID_GROUP], 10);
// Only care about the parent process if it is not the same as the current process.
let parentProcessInfo;
if (parentProcessId !== processId) {
parentProcessInfo = existingProcessInfoById.get(parentProcessId);
if (!parentProcessInfo) {
// Create a new placeholder entry for the parent with the information we have so far
parentProcessInfo = {
processName: '',
processId: parentProcessId,
parentProcessInfo: undefined,
childProcessInfos: []
};
existingProcessInfoById.set(parentProcessId, parentProcessInfo);
}
}
let processInfo = existingProcessInfoById.get(processId);
if (!processInfo) {
// Create a new entry
processInfo = {
processName,
processId,
parentProcessInfo,
childProcessInfos: []
};
existingProcessInfoById.set(processId, processInfo);
}
else {
// Update placeholder entry
processInfo.processName = processName;
processInfo.parentProcessInfo = parentProcessInfo;
}
// Add the process as a child of the parent process
parentProcessInfo === null || parentProcessInfo === void 0 ? void 0 : parentProcessInfo.childProcessInfos.push(processInfo);
}
function convertToProcessInfoByNameMap(processInfoById) {
const processInfoByNameMap = new Map();
for (const processInfo of processInfoById.values()) {
let processInfoNameEntries = processInfoByNameMap.get(processInfo.processName);
if (!processInfoNameEntries) {
processInfoNameEntries = [];
processInfoByNameMap.set(processInfo.processName, processInfoNameEntries);
}
processInfoNameEntries.push(processInfo);
}
return processInfoByNameMap;
}
const OS_PLATFORM = os.platform();
function getProcessListProcessOptions() {
let command;
let args;
if (OS_PLATFORM === 'win32') {
command = 'wmic.exe';
// Order of declared properties does not impact the order of the output
args = ['process', 'get', 'Name,ParentProcessId,ProcessId'];
}
else {
command = 'ps';
// -A: Select all processes
// -o: User-defined format
// Order of declared properties impacts the order of the output, so match
// the order of wmic.exe output
args = ['-Ao', 'comm,ppid,pid'];
}
return { path: command, args };
}
/**
* The Executable class provides a safe, portable, recommended solution for tools that need
* to launch child processes.
*
* @remarks
* The NodeJS child_process API provides a solution for launching child processes, however
* its design encourages reliance on the operating system shell for certain features.
* Invoking the OS shell is not safe, not portable, and generally not recommended:
*
* - Different shells have different behavior and command-line syntax, and which shell you
* will get with NodeJS is unpredictable. There is no universal shell guaranteed to be
* available on all platforms.
*
* - If a command parameter contains symbol characters, a shell may interpret them, which
* can introduce a security vulnerability
*
* - Each shell has different rules for escaping these symbols. On Windows, the default
* shell is incapable of escaping certain character sequences.
*
* The Executable API provides a pure JavaScript implementation of primitive shell-like
* functionality for searching the default PATH, appending default file extensions on Windows,
* and executing a file that may contain a POSIX shebang. This primitive functionality
* is sufficient (and recommended) for most tooling scenarios.
*
* If you need additional shell features such as wildcard globbing, environment variable
* expansion, piping, or built-in commands, then we recommend to use the `@microsoft/rushell`
* library instead. Rushell is a pure JavaScript shell with a standard syntax that is
* guaranteed to work consistently across all platforms.
*
* @public
*/
class Executable {
/**
* Synchronously create a child process and optionally capture its output.
*
* @remarks
* This function is similar to child_process.spawnSync(). The main differences are:
*
* - It does not invoke the OS shell unless the executable file is a shell script.
* - Command-line arguments containing special characters are more accurately passed
* through to the child process.
* - If the filename is missing a path, then the shell's default PATH will be searched.
* - If the filename is missing a file extension, then Windows default file extensions
* will be searched.
*
* @param filename - The name of the executable file. This string must not contain any
* command-line arguments. If the name contains any path delimiters, then the shell's
* default PATH will not be searched.
* @param args - The command-line arguments to be passed to the process.
* @param options - Additional options
* @returns the same data type as returned by the NodeJS child_process.spawnSync() API
*
* @privateRemarks
*
* NOTE: The NodeJS spawnSync() returns SpawnSyncReturns<string> or SpawnSyncReturns<Buffer>
* polymorphically based on the options.encoding parameter value. This is a fairly confusing
* design. In most cases, developers want string with the default encoding. If/when someone
* wants binary output or a non-default text encoding, we will introduce a separate API function
* with a name like "spawnWithBufferSync".
*/
static spawnSync(filename, args, options) {
if (!options) {
options = {};
}
const context = Executable._getExecutableContext(options);
const resolvedPath = Executable._tryResolve(filename, options, context);
if (!resolvedPath) {
throw new Error(`The executable file was not found: "${filename}"`);
}
const spawnOptions = {
cwd: context.currentWorkingDirectory,
env: context.environmentMap.toObject(),
input: options.input,
stdio: options.stdio,
timeout: options.timeoutMs,
maxBuffer: options.maxBuffer,
// Contrary to what the NodeJS typings imply, we must explicitly specify "utf8" here
// if we want the result to be SpawnSyncReturns<string> instead of SpawnSyncReturns<Buffer>.
encoding: 'utf8',
// NOTE: This is always false, because Rushell will be recommended instead of relying on the OS shell.
shell: false
};
const normalizedCommandLine = Executable._buildCommandLineFixup(resolvedPath, args, context);
return child_process.spawnSync(normalizedCommandLine.path, normalizedCommandLine.args, spawnOptions);
}
/**
* Start a child process.
*
* @remarks
* This function is similar to child_process.spawn(). The main differences are:
*
* - It does not invoke the OS shell unless the executable file is a shell script.
* - Command-line arguments containing special characters are more accurately passed
* through to the child process.
* - If the filename is missing a path, then the shell's default PATH will be searched.
* - If the filename is missing a file extension, then Windows default file extensions
* will be searched.
*
* This command is asynchronous, but it does not return a `Promise`. Instead it returns
* a Node.js `ChildProcess` supporting event notifications.
*
* @param filename - The name of the executable file. This string must not contain any
* command-line arguments. If the name contains any path delimiters, then the shell's
* default PATH will not be searched.
* @param args - The command-line arguments to be passed to the process.
* @param options - Additional options
* @returns the same data type as returned by the NodeJS child_process.spawnSync() API
*/
static spawn(filename, args, options) {
if (!options) {
options = {};
}
const context = Executable._getExecutableContext(options);
const resolvedPath = Executable._tryResolve(filename, options, context);
if (!resolvedPath) {
throw new Error(`The executable file was not found: "${filename}"`);
}
const spawnOptions = {
cwd: context.currentWorkingDirectory,
env: context.environmentMap.toObject(),
stdio: options.stdio,
// NOTE: This is always false, because Rushell will be recommended instead of relying on the OS shell.
shell: false
};
const normalizedCommandLine = Executable._buildCommandLineFixup(resolvedPath, args, context);
return child_process.spawn(normalizedCommandLine.path, normalizedCommandLine.args, spawnOptions);
}
static async waitForExitAsync(childProcess, options = {}) {
const { throwOnNonZeroExitCode = false, encoding } = options;
if (encoding && (!childProcess.stdout || !childProcess.stderr)) {
throw new Error('An encoding was specified, but stdout and/or stderr on the child process are not defined');
}
const collectedStdout = [];
const collectedStderr = [];
const useBufferEncoding = encoding === 'buffer';
function normalizeChunk(chunk) {
if (typeof chunk === 'string') {
return (useBufferEncoding ? Buffer.from(chunk) : chunk);
}
else {
return (useBufferEncoding ? chunk : chunk.toString(encoding));
}
}
let errorThrown = false;
const exitCode = await new Promise((resolve, reject) => {
if (encoding) {
childProcess.stdout.on('data', (chunk) => {
collectedStdout.push(normalizeChunk(chunk));
});
childProcess.stderr.on('data', (chunk) => {
collectedStderr.push(normalizeChunk(chunk));
});
}
childProcess.on('error', (error) => {
errorThrown = true;
reject(error);
});
childProcess.on('exit', (code) => {
if (errorThrown) {
// We've already rejected the promise
return;
}
if (code !== 0 && throwOnNonZeroExitCode) {
reject(new Error(`Process exited with code ${code}`));
}
else {
resolve(code);
}
});
});
const result = {
exitCode
};
if (encoding === 'buffer') {
result.stdout = Buffer.concat(collectedStdout);
result.stderr = Buffer.concat(collectedStderr);
}
else if (encoding) {
result.stdout = collectedStdout.join('');
result.stderr = collectedStderr.join('');
}
return result;
}
/* eslint-enable @rushstack/no-new-null */
/**
* Get the list of processes currently running on the system, keyed by the process ID.
*
* @remarks The underlying implementation depends on the operating system:
* - On Windows, this uses the `wmic.exe` utility.
* - On Unix, this uses the `ps` utility.
*/
static async getProcessInfoByIdAsync() {
const { path: command, args } = getProcessListProcessOptions();
const process = Executable.spawn(command, args, {
stdio: ['ignore', 'pipe', 'ignore']
});
if (process.stdout === null) {
throw new InternalError_1.InternalError('Child process did not provide stdout');
}
const [processInfoByIdMap] = await Promise.all([
parseProcessListOutputAsync(process.stdout),
// Don't collect output in the result since we process it directly
Executable.waitForExitAsync(process, { throwOnNonZeroExitCode: true })
]);
return processInfoByIdMap;
}
/**
* {@inheritDoc Executable.getProcessInfoByIdAsync}
*/
static getProcessInfoById() {
const { path: command, args } = getProcessListProcessOptions();
const processOutput = Executable.spawnSync(command, args);
if (processOutput.error) {
throw new Error(`Unable to list processes: ${command} failed with error ${processOutput.error}`);
}
if (processOutput.status !== 0) {
throw new Error(`Unable to list processes: ${command} exited with code ${processOutput.status}`);
}
return parseProcessListOutput(processOutput.output);
}
/**
* Get the list of processes currently running on the system, keyed by the process name. All processes
* with the same name will be grouped.
*
* @remarks The underlying implementation depends on the operating system:
* - On Windows, this uses the `wmic.exe` utility.
* - On Unix, this uses the `ps` utility.
*/
static async getProcessInfoByNameAsync() {
const processInfoById = await Executable.getProcessInfoByIdAsync();
return convertToProcessInfoByNameMap(processInfoById);
}
/**
* {@inheritDoc Executable.getProcessInfoByNameAsync}
*/
static getProcessInfoByName() {
const processInfoByIdMap = Executable.getProcessInfoById();
return convertToProcessInfoByNameMap(processInfoByIdMap);
}
// PROBLEM: Given an "args" array of strings that may contain special characters (e.g. spaces,
// backslashes, quotes), ensure that these strings pass through to the child process's ARGV array
// without anything getting corrupted along the way.
//
// On Unix you just pass the array to spawnSync(). But on Windows, this is a very complex problem:
// - The Win32 CreateProcess() API expects the args to be encoded as a single text string
// - The decoding of this string is up to the application (not the OS), and there are 3 different
// algorithms in common usage: the cmd.exe shell, the Microsoft CRT library init code, and
// the Win32 CommandLineToArgvW()
// - The encodings are counterintuitive and have lots of special cases
// - NodeJS spawnSync() tries do the encoding without knowing which decoder will be used
//
// See these articles for a full analysis:
// http://www.windowsinspired.com/understanding-the-command-line-string-and-arguments-received-by-a-windows-program/
// http://www.windowsinspired.com/how-a-windows-programs-splits-its-command-line-into-individual-arguments/
static _buildCommandLineFixup(resolvedPath, args, context) {
const fileExtension = path.extname(resolvedPath);
if (OS_PLATFORM === 'win32') {
// Do we need a custom handler for this file type?
switch (fileExtension.toUpperCase()) {
case '.EXE':
case '.COM':
// okay to execute directly
break;
case '.BAT':
case '.CMD': {
Executable._validateArgsForWindowsShell(args);
// These file types must be invoked via the Windows shell
let shellPath = context.environmentMap.get('COMSPEC');
if (!shellPath || !Executable._canExecute(shellPath, context)) {
shellPath = Executable.tryResolve('cmd.exe');
}
if (!shellPath) {
throw new Error(`Unable to execute "${path.basename(resolvedPath)}" ` +
`because CMD.exe was not found in the PATH`);
}
const shellArgs = [];
// /D: Disable execution of AutoRun commands when starting the new shell context
shellArgs.push('/d');
// /S: Disable Cmd.exe's parsing of double-quote characters inside the command-line
shellArgs.push('/s');
// /C: Execute the following command and then exit immediately
shellArgs.push('/c');
// If the path contains special charactrers (e.g. spaces), escape them so that
// they don't get interpreted by the shell
shellArgs.push(Executable._getEscapedForWindowsShell(resolvedPath));
shellArgs.push(...args);
return { path: shellPath, args: shellArgs };
}
default:
throw new Error(`Cannot execute "${path.basename(resolvedPath)}" because the file type is not supported`);
}
}
return {
path: resolvedPath,
args: args
};
}
/**
* Given a filename, this determines the absolute path of the executable file that would
* be executed by a shell:
*
* - If the filename is missing a path, then the shell's default PATH will be searched.
* - If the filename is missing a file extension, then Windows default file extensions
* will be searched.
*
* @remarks
*
* @param filename - The name of the executable file. This string must not contain any
* command-line arguments. If the name contains any path delimiters, then the shell's
* default PATH will not be searched.
* @param options - optional other parameters
* @returns the absolute path of the executable, or undefined if it was not found
*/
static tryResolve(filename, options) {
return Executable._tryResolve(filename, options || {}, Executable._getExecutableContext(options));
}
static _tryResolve(filename, options, context) {
// NOTE: Since "filename" cannot contain command-line arguments, the "/" here
// must be interpreted as a path delimiter
const hasPathSeparators = filename.indexOf('/') >= 0 || (OS_PLATFORM === 'win32' && filename.indexOf('\\') >= 0);
// Are there any path separators?
if (hasPathSeparators) {
// If so, then don't search the PATH. Just resolve relative to the current working directory
const resolvedPath = path.resolve(context.currentWorkingDirectory, filename);
return Executable._tryResolveFileExtension(resolvedPath, context);
}
else {
// Otherwise if it's a bare name, then try everything in the shell PATH
const pathsToSearch = Executable._getSearchFolders(context);
for (const pathToSearch of pathsToSearch) {
const resolvedPath = path.join(pathToSearch, filename);
const result = Executable._tryResolveFileExtension(resolvedPath, context);
if (result) {
return result;
}
}
// No match was found
return undefined;
}
}
static _tryResolveFileExtension(resolvedPath, context) {
if (Executable._canExecute(resolvedPath, context)) {
return resolvedPath;
}
// Try the default file extensions
for (const shellExtension of context.windowsExecutableExtensions) {
const resolvedNameWithExtension = resolvedPath + shellExtension;
if (Executable._canExecute(resolvedNameWithExtension, context)) {
return resolvedNameWithExtension;
}
}
return undefined;
}
static _buildEnvironmentMap(options) {
const environmentMap = new EnvironmentMap_1.EnvironmentMap();
if (options.environment !== undefined && options.environmentMap !== undefined) {
throw new Error('IExecutableResolveOptions.environment and IExecutableResolveOptions.environmentMap' +
' cannot both be specified');
}
if (options.environment !== undefined) {
environmentMap.mergeFromObject(options.environment);
}
else if (options.environmentMap !== undefined) {
environmentMap.mergeFrom(options.environmentMap);
}
else {
environmentMap.mergeFromObject(process.env);
}
return environmentMap;
}
/**
* This is used when searching the shell PATH for an executable, to determine
* whether a match should be skipped or not. If it returns true, this does not
* guarantee that the file can be successfully executed.
*/
static _canExecute(filePath, context) {
if (!FileSystem_1.FileSystem.exists(filePath)) {
return false;
}
if (OS_PLATFORM === 'win32') {
// NOTE: For Windows, we don't validate that the file extension appears in PATHEXT.
// That environment variable determines which extensions can be appended if the
// extension is missing, but it does not affect whether a file may be executed or not.
// Windows does have a (seldom used) ACL that can be used to deny execution permissions
// for a file, but NodeJS doesn't expose that API, so we don't bother checking it.
// However, Windows *does* require that the file has some kind of file extension
if (path.extname(filePath) === '') {
return false;
}
}
else {
// For Unix, check whether any of the POSIX execute bits are set
try {
// eslint-disable-next-line no-bitwise
if ((FileSystem_1.FileSystem.getPosixModeBits(filePath) & PosixModeBits_1.PosixModeBits.AllExecute) === 0) {
return false; // not executable
}
}
catch (error) {
// If we have trouble accessing the file, ignore the error and consider it "not executable"
// since that's what a shell would do
}
}
return true;
}
/**
* Returns the list of folders where we will search for an executable,
* based on the PATH environment variable.
*/
static _getSearchFolders(context) {
const pathList = context.environmentMap.get('PATH') || '';
const folders = [];
// Avoid processing duplicates
const seenPaths = new Set();
// NOTE: Cmd.exe on Windows always searches the current working directory first.
// PowerShell and Unix shells do NOT do that, because it's a security concern.
// We follow their behavior.
for (const splitPath of pathList.split(path.delimiter)) {
const trimmedPath = splitPath.trim();
if (trimmedPath !== '') {
if (!seenPaths.has(trimmedPath)) {
// Fun fact: If you put relative paths in your PATH environment variable,
// all shells will dynamically match them against the current working directory.
// This is a terrible design, and in practice nobody does that, but it is supported...
// so we allow it here.
const resolvedPath = path.resolve(context.currentWorkingDirectory, trimmedPath);
if (!seenPaths.has(resolvedPath)) {
if (FileSystem_1.FileSystem.exists(resolvedPath)) {
folders.push(resolvedPath);
}
seenPaths.add(resolvedPath);
}
seenPaths.add(trimmedPath);
}
}
}
return folders;
}
static _getExecutableContext(options) {
if (!options) {
options = {};
}
const environment = Executable._buildEnvironmentMap(options);
let currentWorkingDirectory;
if (options.currentWorkingDirectory) {
currentWorkingDirectory = path.resolve(options.currentWorkingDirectory);
}
else {
currentWorkingDirectory = process.cwd();
}
const windowsExecutableExtensions = [];
if (OS_PLATFORM === 'win32') {
const pathExtVariable = environment.get('PATHEXT') || '';
for (const splitValue of pathExtVariable.split(';')) {
const trimmed = splitValue.trim().toLowerCase();
// Ignore malformed extensions
if (/^\.[a-z0-9\.]*[a-z0-9]$/i.test(trimmed)) {
// Don't add the same extension twice
if (windowsExecutableExtensions.indexOf(trimmed) < 0) {
windowsExecutableExtensions.push(trimmed);
}
}
}
}
return {
environmentMap: environment,
currentWorkingDirectory,
windowsExecutableExtensions
};
}
/**
* Given an input string containing special symbol characters, this inserts the "^" escape
* character to ensure the symbols are interpreted literally by the Windows shell.
*/
static _getEscapedForWindowsShell(text) {
const escapableCharRegExp = /[%\^&|<> ]/g;
return text.replace(escapableCharRegExp, (value) => '^' + value);
}
/**
* Checks for characters that are unsafe to pass to a Windows batch file
* due to the way that cmd.exe implements escaping.
*/
static _validateArgsForWindowsShell(args) {
const specialCharRegExp = /[%\^&|<>\r\n]/g;
for (const arg of args) {
const match = arg.match(specialCharRegExp);
if (match) {
// NOTE: It is possible to escape some of these characters by prefixing them
// with a caret (^), which allows these characters to be successfully passed
// through to the batch file %1 variables. But they will be expanded again
// whenever they are used. For example, NPM's binary wrapper batch files
// use "%*" to pass their arguments to Node.exe, which causes them to be expanded
// again. Unfortunately the Cmd.exe batch language provides native escaping
// function (that could be used to insert the carets again).
//
// We could work around that by adding double carets, but in general there
// is no way to predict how many times the variable will get expanded.
// Thus, there is no generally reliable way to pass these characters.
throw new Error(`The command line argument ${JSON.stringify(arg)} contains a` +
` special character ${JSON.stringify(match[0])} that cannot be escaped for the Windows shell`);
}
}
}
}
exports.Executable = Executable;
//# sourceMappingURL=Executable.js.map