| "use strict"; | |
| var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | |
| if (k2 === undefined) k2 = k; | |
| Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); | |
| }) : (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.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); | |
| __setModuleDefault(result, mod); | |
| return result; | |
| }; | |
| var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | |
| function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } | |
| return new (P || (P = Promise))(function (resolve, reject) { | |
| function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | |
| function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | |
| function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } | |
| step((generator = generator.apply(thisArg, _arguments || [])).next()); | |
| }); | |
| }; | |
| Object.defineProperty(exports, "__esModule", { value: true }); | |
| exports.argStringToArray = exports.ToolRunner = void 0; | |
| const os = __importStar(require("os")); | |
| const events = __importStar(require("events")); | |
| const child = __importStar(require("child_process")); | |
| const path = __importStar(require("path")); | |
| const io = __importStar(require("@actions/io")); | |
| const ioUtil = __importStar(require("@actions/io/lib/io-util")); | |
| const timers_1 = require("timers"); | |
| /* eslint-disable @typescript-eslint/unbound-method */ | |
| const IS_WINDOWS = process.platform === 'win32'; | |
| /* | |
| * Class for running command line tools. Handles quoting and arg parsing in a platform agnostic way. | |
| */ | |
| class ToolRunner extends events.EventEmitter { | |
| constructor(toolPath, args, options) { | |
| super(); | |
| if (!toolPath) { | |
| throw new Error("Parameter 'toolPath' cannot be null or empty."); | |
| } | |
| this.toolPath = toolPath; | |
| this.args = args || []; | |
| this.options = options || {}; | |
| } | |
| _debug(message) { | |
| if (this.options.listeners && this.options.listeners.debug) { | |
| this.options.listeners.debug(message); | |
| } | |
| } | |
| _getCommandString(options, noPrefix) { | |
| const toolPath = this._getSpawnFileName(); | |
| const args = this._getSpawnArgs(options); | |
| let cmd = noPrefix ? '' : '[command]'; // omit prefix when piped to a second tool | |
| if (IS_WINDOWS) { | |
| // Windows + cmd file | |
| if (this._isCmdFile()) { | |
| cmd += toolPath; | |
| for (const a of args) { | |
| cmd += ` ${a}`; | |
| } | |
| } | |
| // Windows + verbatim | |
| else if (options.windowsVerbatimArguments) { | |
| cmd += `"${toolPath}"`; | |
| for (const a of args) { | |
| cmd += ` ${a}`; | |
| } | |
| } | |
| // Windows (regular) | |
| else { | |
| cmd += this._windowsQuoteCmdArg(toolPath); | |
| for (const a of args) { | |
| cmd += ` ${this._windowsQuoteCmdArg(a)}`; | |
| } | |
| } | |
| } | |
| else { | |
| // OSX/Linux - this can likely be improved with some form of quoting. | |
| // creating processes on Unix is fundamentally different than Windows. | |
| // on Unix, execvp() takes an arg array. | |
| cmd += toolPath; | |
| for (const a of args) { | |
| cmd += ` ${a}`; | |
| } | |
| } | |
| return cmd; | |
| } | |
| _processLineBuffer(data, strBuffer, onLine) { | |
| try { | |
| let s = strBuffer + data.toString(); | |
| let n = s.indexOf(os.EOL); | |
| while (n > -1) { | |
| const line = s.substring(0, n); | |
| onLine(line); | |
| // the rest of the string ... | |
| s = s.substring(n + os.EOL.length); | |
| n = s.indexOf(os.EOL); | |
| } | |
| return s; | |
| } | |
| catch (err) { | |
| // streaming lines to console is best effort. Don't fail a build. | |
| this._debug(`error processing line. Failed with error ${err}`); | |
| return ''; | |
| } | |
| } | |
| _getSpawnFileName() { | |
| if (IS_WINDOWS) { | |
| if (this._isCmdFile()) { | |
| return process.env['COMSPEC'] || 'cmd.exe'; | |
| } | |
| } | |
| return this.toolPath; | |
| } | |
| _getSpawnArgs(options) { | |
| if (IS_WINDOWS) { | |
| if (this._isCmdFile()) { | |
| let argline = `/D /S /C "${this._windowsQuoteCmdArg(this.toolPath)}`; | |
| for (const a of this.args) { | |
| argline += ' '; | |
| argline += options.windowsVerbatimArguments | |
| ? a | |
| : this._windowsQuoteCmdArg(a); | |
| } | |
| argline += '"'; | |
| return [argline]; | |
| } | |
| } | |
| return this.args; | |
| } | |
| _endsWith(str, end) { | |
| return str.endsWith(end); | |
| } | |
| _isCmdFile() { | |
| const upperToolPath = this.toolPath.toUpperCase(); | |
| return (this._endsWith(upperToolPath, '.CMD') || | |
| this._endsWith(upperToolPath, '.BAT')); | |
| } | |
| _windowsQuoteCmdArg(arg) { | |
| // for .exe, apply the normal quoting rules that libuv applies | |
| if (!this._isCmdFile()) { | |
| return this._uvQuoteCmdArg(arg); | |
| } | |
| // otherwise apply quoting rules specific to the cmd.exe command line parser. | |
| // the libuv rules are generic and are not designed specifically for cmd.exe | |
| // command line parser. | |
| // | |
| // for a detailed description of the cmd.exe command line parser, refer to | |
| // http://stackoverflow.com/questions/4094699/how-does-the-windows-command-interpreter-cmd-exe-parse-scripts/7970912#7970912 | |
| // need quotes for empty arg | |
| if (!arg) { | |
| return '""'; | |
| } | |
| // determine whether the arg needs to be quoted | |
| const cmdSpecialChars = [ | |
| ' ', | |
| '\t', | |
| '&', | |
| '(', | |
| ')', | |
| '[', | |
| ']', | |
| '{', | |
| '}', | |
| '^', | |
| '=', | |
| ';', | |
| '!', | |
| "'", | |
| '+', | |
| ',', | |
| '`', | |
| '~', | |
| '|', | |
| '<', | |
| '>', | |
| '"' | |
| ]; | |
| let needsQuotes = false; | |
| for (const char of arg) { | |
| if (cmdSpecialChars.some(x => x === char)) { | |
| needsQuotes = true; | |
| break; | |
| } | |
| } | |
| // short-circuit if quotes not needed | |
| if (!needsQuotes) { | |
| return arg; | |
| } | |
| // the following quoting rules are very similar to the rules that by libuv applies. | |
| // | |
| // 1) wrap the string in quotes | |
| // | |
| // 2) double-up quotes - i.e. " => "" | |
| // | |
| // this is different from the libuv quoting rules. libuv replaces " with \", which unfortunately | |
| // doesn't work well with a cmd.exe command line. | |
| // | |
| // note, replacing " with "" also works well if the arg is passed to a downstream .NET console app. | |
| // for example, the command line: | |
| // foo.exe "myarg:""my val""" | |
| // is parsed by a .NET console app into an arg array: | |
| // [ "myarg:\"my val\"" ] | |
| // which is the same end result when applying libuv quoting rules. although the actual | |
| // command line from libuv quoting rules would look like: | |
| // foo.exe "myarg:\"my val\"" | |
| // | |
| // 3) double-up slashes that precede a quote, | |
| // e.g. hello \world => "hello \world" | |
| // hello\"world => "hello\\""world" | |
| // hello\\"world => "hello\\\\""world" | |
| // hello world\ => "hello world\\" | |
| // | |
| // technically this is not required for a cmd.exe command line, or the batch argument parser. | |
| // the reasons for including this as a .cmd quoting rule are: | |
| // | |
| // a) this is optimized for the scenario where the argument is passed from the .cmd file to an | |
| // external program. many programs (e.g. .NET console apps) rely on the slash-doubling rule. | |
| // | |
| // b) it's what we've been doing previously (by deferring to node default behavior) and we | |
| // haven't heard any complaints about that aspect. | |
| // | |
| // note, a weakness of the quoting rules chosen here, is that % is not escaped. in fact, % cannot be | |
| // escaped when used on the command line directly - even though within a .cmd file % can be escaped | |
| // by using %%. | |
| // | |
| // the saving grace is, on the command line, %var% is left as-is if var is not defined. this contrasts | |
| // the line parsing rules within a .cmd file, where if var is not defined it is replaced with nothing. | |
| // | |
| // one option that was explored was replacing % with ^% - i.e. %var% => ^%var^%. this hack would | |
| // often work, since it is unlikely that var^ would exist, and the ^ character is removed when the | |
| // variable is used. the problem, however, is that ^ is not removed when %* is used to pass the args | |
| // to an external program. | |
| // | |
| // an unexplored potential solution for the % escaping problem, is to create a wrapper .cmd file. | |
| // % can be escaped within a .cmd file. | |
| let reverse = '"'; | |
| let quoteHit = true; | |
| for (let i = arg.length; i > 0; i--) { | |
| // walk the string in reverse | |
| reverse += arg[i - 1]; | |
| if (quoteHit && arg[i - 1] === '\\') { | |
| reverse += '\\'; // double the slash | |
| } | |
| else if (arg[i - 1] === '"') { | |
| quoteHit = true; | |
| reverse += '"'; // double the quote | |
| } | |
| else { | |
| quoteHit = false; | |
| } | |
| } | |
| reverse += '"'; | |
| return reverse | |
| .split('') | |
| .reverse() | |
| .join(''); | |
| } | |
| _uvQuoteCmdArg(arg) { | |
| // Tool runner wraps child_process.spawn() and needs to apply the same quoting as | |
| // Node in certain cases where the undocumented spawn option windowsVerbatimArguments | |
| // is used. | |
| // | |
| // Since this function is a port of quote_cmd_arg from Node 4.x (technically, lib UV, | |
| // see https://github.com/nodejs/node/blob/v4.x/deps/uv/src/win/process.c for details), | |
| // pasting copyright notice from Node within this function: | |
| // | |
| // Copyright Joyent, Inc. and other Node contributors. All rights reserved. | |
| // | |
| // Permission is hereby granted, free of charge, to any person obtaining a copy | |
| // of this software and associated documentation files (the "Software"), to | |
| // deal in the Software without restriction, including without limitation the | |
| // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or | |
| // sell copies of the Software, and to permit persons to whom the Software is | |
| // furnished to do so, subject to the following conditions: | |
| // | |
| // The above copyright notice and this permission notice shall be included in | |
| // all copies or substantial portions of the Software. | |
| // | |
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
| // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS | |
| // IN THE SOFTWARE. | |
| if (!arg) { | |
| // Need double quotation for empty argument | |
| return '""'; | |
| } | |
| if (!arg.includes(' ') && !arg.includes('\t') && !arg.includes('"')) { | |
| // No quotation needed | |
| return arg; | |
| } | |
| if (!arg.includes('"') && !arg.includes('\\')) { | |
| // No embedded double quotes or backslashes, so I can just wrap | |
| // quote marks around the whole thing. | |
| return `"${arg}"`; | |
| } | |
| // Expected input/output: | |
| // input : hello"world | |
| // output: "hello\"world" | |
| // input : hello""world | |
| // output: "hello\"\"world" | |
| // input : hello\world | |
| // output: hello\world | |
| // input : hello\\world | |
| // output: hello\\world | |
| // input : hello\"world | |
| // output: "hello\\\"world" | |
| // input : hello\\"world | |
| // output: "hello\\\\\"world" | |
| // input : hello world\ | |
| // output: "hello world\\" - note the comment in libuv actually reads "hello world\" | |
| // but it appears the comment is wrong, it should be "hello world\\" | |
| let reverse = '"'; | |
| let quoteHit = true; | |
| for (let i = arg.length; i > 0; i--) { | |
| // walk the string in reverse | |
| reverse += arg[i - 1]; | |
| if (quoteHit && arg[i - 1] === '\\') { | |
| reverse += '\\'; | |
| } | |
| else if (arg[i - 1] === '"') { | |
| quoteHit = true; | |
| reverse += '\\'; | |
| } | |
| else { | |
| quoteHit = false; | |
| } | |
| } | |
| reverse += '"'; | |
| return reverse | |
| .split('') | |
| .reverse() | |
| .join(''); | |
| } | |
| _cloneExecOptions(options) { | |
| options = options || {}; | |
| const result = { | |
| cwd: options.cwd || process.cwd(), | |
| env: options.env || process.env, | |
| silent: options.silent || false, | |
| windowsVerbatimArguments: options.windowsVerbatimArguments || false, | |
| failOnStdErr: options.failOnStdErr || false, | |
| ignoreReturnCode: options.ignoreReturnCode || false, | |
| delay: options.delay || 10000 | |
| }; | |
| result.outStream = options.outStream || process.stdout; | |
| result.errStream = options.errStream || process.stderr; | |
| return result; | |
| } | |
| _getSpawnOptions(options, toolPath) { | |
| options = options || {}; | |
| const result = {}; | |
| result.cwd = options.cwd; | |
| result.env = options.env; | |
| result['windowsVerbatimArguments'] = | |
| options.windowsVerbatimArguments || this._isCmdFile(); | |
| if (options.windowsVerbatimArguments) { | |
| result.argv0 = `"${toolPath}"`; | |
| } | |
| return result; | |
| } | |
| /** | |
| * Exec a tool. | |
| * Output will be streamed to the live console. | |
| * Returns promise with return code | |
| * | |
| * @param tool path to tool to exec | |
| * @param options optional exec options. See ExecOptions | |
| * @returns number | |
| */ | |
| exec() { | |
| return __awaiter(this, void 0, void 0, function* () { | |
| // root the tool path if it is unrooted and contains relative pathing | |
| if (!ioUtil.isRooted(this.toolPath) && | |
| (this.toolPath.includes('/') || | |
| (IS_WINDOWS && this.toolPath.includes('\\')))) { | |
| // prefer options.cwd if it is specified, however options.cwd may also need to be rooted | |
| this.toolPath = path.resolve(process.cwd(), this.options.cwd || process.cwd(), this.toolPath); | |
| } | |
| // if the tool is only a file name, then resolve it from the PATH | |
| // otherwise verify it exists (add extension on Windows if necessary) | |
| this.toolPath = yield io.which(this.toolPath, true); | |
| return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () { | |
| this._debug(`exec tool: ${this.toolPath}`); | |
| this._debug('arguments:'); | |
| for (const arg of this.args) { | |
| this._debug(` ${arg}`); | |
| } | |
| const optionsNonNull = this._cloneExecOptions(this.options); | |
| if (!optionsNonNull.silent && optionsNonNull.outStream) { | |
| optionsNonNull.outStream.write(this._getCommandString(optionsNonNull) + os.EOL); | |
| } | |
| const state = new ExecState(optionsNonNull, this.toolPath); | |
| state.on('debug', (message) => { | |
| this._debug(message); | |
| }); | |
| if (this.options.cwd && !(yield ioUtil.exists(this.options.cwd))) { | |
| return reject(new Error(`The cwd: ${this.options.cwd} does not exist!`)); | |
| } | |
| const fileName = this._getSpawnFileName(); | |
| const cp = child.spawn(fileName, this._getSpawnArgs(optionsNonNull), this._getSpawnOptions(this.options, fileName)); | |
| let stdbuffer = ''; | |
| if (cp.stdout) { | |
| cp.stdout.on('data', (data) => { | |
| if (this.options.listeners && this.options.listeners.stdout) { | |
| this.options.listeners.stdout(data); | |
| } | |
| if (!optionsNonNull.silent && optionsNonNull.outStream) { | |
| optionsNonNull.outStream.write(data); | |
| } | |
| stdbuffer = this._processLineBuffer(data, stdbuffer, (line) => { | |
| if (this.options.listeners && this.options.listeners.stdline) { | |
| this.options.listeners.stdline(line); | |
| } | |
| }); | |
| }); | |
| } | |
| let errbuffer = ''; | |
| if (cp.stderr) { | |
| cp.stderr.on('data', (data) => { | |
| state.processStderr = true; | |
| if (this.options.listeners && this.options.listeners.stderr) { | |
| this.options.listeners.stderr(data); | |
| } | |
| if (!optionsNonNull.silent && | |
| optionsNonNull.errStream && | |
| optionsNonNull.outStream) { | |
| const s = optionsNonNull.failOnStdErr | |
| ? optionsNonNull.errStream | |
| : optionsNonNull.outStream; | |
| s.write(data); | |
| } | |
| errbuffer = this._processLineBuffer(data, errbuffer, (line) => { | |
| if (this.options.listeners && this.options.listeners.errline) { | |
| this.options.listeners.errline(line); | |
| } | |
| }); | |
| }); | |
| } | |
| cp.on('error', (err) => { | |
| state.processError = err.message; | |
| state.processExited = true; | |
| state.processClosed = true; | |
| state.CheckComplete(); | |
| }); | |
| cp.on('exit', (code) => { | |
| state.processExitCode = code; | |
| state.processExited = true; | |
| this._debug(`Exit code ${code} received from tool '${this.toolPath}'`); | |
| state.CheckComplete(); | |
| }); | |
| cp.on('close', (code) => { | |
| state.processExitCode = code; | |
| state.processExited = true; | |
| state.processClosed = true; | |
| this._debug(`STDIO streams have closed for tool '${this.toolPath}'`); | |
| state.CheckComplete(); | |
| }); | |
| state.on('done', (error, exitCode) => { | |
| if (stdbuffer.length > 0) { | |
| this.emit('stdline', stdbuffer); | |
| } | |
| if (errbuffer.length > 0) { | |
| this.emit('errline', errbuffer); | |
| } | |
| cp.removeAllListeners(); | |
| if (error) { | |
| reject(error); | |
| } | |
| else { | |
| resolve(exitCode); | |
| } | |
| }); | |
| if (this.options.input) { | |
| if (!cp.stdin) { | |
| throw new Error('child process missing stdin'); | |
| } | |
| cp.stdin.end(this.options.input); | |
| } | |
| })); | |
| }); | |
| } | |
| } | |
| exports.ToolRunner = ToolRunner; | |
| /** | |
| * Convert an arg string to an array of args. Handles escaping | |
| * | |
| * @param argString string of arguments | |
| * @returns string[] array of arguments | |
| */ | |
| function argStringToArray(argString) { | |
| const args = []; | |
| let inQuotes = false; | |
| let escaped = false; | |
| let arg = ''; | |
| function append(c) { | |
| // we only escape double quotes. | |
| if (escaped && c !== '"') { | |
| arg += '\\'; | |
| } | |
| arg += c; | |
| escaped = false; | |
| } | |
| for (let i = 0; i < argString.length; i++) { | |
| const c = argString.charAt(i); | |
| if (c === '"') { | |
| if (!escaped) { | |
| inQuotes = !inQuotes; | |
| } | |
| else { | |
| append(c); | |
| } | |
| continue; | |
| } | |
| if (c === '\\' && escaped) { | |
| append(c); | |
| continue; | |
| } | |
| if (c === '\\' && inQuotes) { | |
| escaped = true; | |
| continue; | |
| } | |
| if (c === ' ' && !inQuotes) { | |
| if (arg.length > 0) { | |
| args.push(arg); | |
| arg = ''; | |
| } | |
| continue; | |
| } | |
| append(c); | |
| } | |
| if (arg.length > 0) { | |
| args.push(arg.trim()); | |
| } | |
| return args; | |
| } | |
| exports.argStringToArray = argStringToArray; | |
| class ExecState extends events.EventEmitter { | |
| constructor(options, toolPath) { | |
| super(); | |
| this.processClosed = false; // tracks whether the process has exited and stdio is closed | |
| this.processError = ''; | |
| this.processExitCode = 0; | |
| this.processExited = false; // tracks whether the process has exited | |
| this.processStderr = false; // tracks whether stderr was written to | |
| this.delay = 10000; // 10 seconds | |
| this.done = false; | |
| this.timeout = null; | |
| if (!toolPath) { | |
| throw new Error('toolPath must not be empty'); | |
| } | |
| this.options = options; | |
| this.toolPath = toolPath; | |
| if (options.delay) { | |
| this.delay = options.delay; | |
| } | |
| } | |
| CheckComplete() { | |
| if (this.done) { | |
| return; | |
| } | |
| if (this.processClosed) { | |
| this._setResult(); | |
| } | |
| else if (this.processExited) { | |
| this.timeout = timers_1.setTimeout(ExecState.HandleTimeout, this.delay, this); | |
| } | |
| } | |
| _debug(message) { | |
| this.emit('debug', message); | |
| } | |
| _setResult() { | |
| // determine whether there is an error | |
| let error; | |
| if (this.processExited) { | |
| if (this.processError) { | |
| error = new Error(`There was an error when attempting to execute the process '${this.toolPath}'. This may indicate the process failed to start. Error: ${this.processError}`); | |
| } | |
| else if (this.processExitCode !== 0 && !this.options.ignoreReturnCode) { | |
| error = new Error(`The process '${this.toolPath}' failed with exit code ${this.processExitCode}`); | |
| } | |
| else if (this.processStderr && this.options.failOnStdErr) { | |
| error = new Error(`The process '${this.toolPath}' failed because one or more lines were written to the STDERR stream`); | |
| } | |
| } | |
| // clear the timeout | |
| if (this.timeout) { | |
| clearTimeout(this.timeout); | |
| this.timeout = null; | |
| } | |
| this.done = true; | |
| this.emit('done', error, this.processExitCode); | |
| } | |
| static HandleTimeout(state) { | |
| if (state.done) { | |
| return; | |
| } | |
| if (!state.processClosed && state.processExited) { | |
| const message = `The STDIO streams did not close within ${state.delay / | |
| 1000} seconds of the exit event from process '${state.toolPath}'. This may indicate a child process inherited the STDIO streams and has not yet exited.`; | |
| state._debug(message); | |
| } | |
| state._setResult(); | |
| } | |
| } | |
| //# sourceMappingURL=toolrunner.js.map |