"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TerminalUtils = void 0;
const tslib_1 = require("tslib");
const inversify_1 = require("inversify");
const child_process_1 = require("child_process");
const codiac_common_1 = require("@codiac.io/codiac-common");
const IBetterLogger_1 = require("@codiac.io/codiac-common/IBetterLogger");
const types = require("@codiac.io/codiac-ops-common");
const terminal_command_request_1 = require("../message-contracts/terminal-command-request");
let TerminalUtils = class TerminalUtils {
    constructor(logger) {
        this.logger = logger;
        this.cliExec = new types.CliExec(this.logger);
    }
    execute(request) {
        var _a, _b, _c;
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            try {
                // TODO: Consider launching a completely separate terminal WINDOW for these.
                let output;
                const ultEnv = (_a = request.env) !== null && _a !== void 0 ? _a : {};
                const ultCwd = (_b = request.cwd) !== null && _b !== void 0 ? _b : process.cwd();
                if (request.shellExe) { // (WITH SHELL)
                    let shellArgs = (_c = request.shellArgs) !== null && _c !== void 0 ? _c : [];
                    let collectStdOut = Boolean(request.outputMode != terminal_command_request_1.CommandOutputModeEnum.log); // always collect, unless they called out for a void
                    if (request.inlineOperator) {
                        // INLINE
                        shellArgs.push(request.inlineOperator.trim());
                        output = yield this.spawnTerminalCommands(request.shellExe, shellArgs, request.command, ultCwd, ultEnv, collectStdOut);
                    }
                    else {
                        // SEPARATE
                        output = yield this.executeSingleCommandInTerminal(request.shellExe, shellArgs, request.command, ultCwd, ultEnv);
                    }
                }
                else { // (NO SHELL GIVEN)
                    if (request.outputMode == terminal_command_request_1.CommandOutputModeEnum.log) {
                        // Execute VOID
                        output = { code: 1, stdout: "" };
                        delete output.stdout;
                        output.code = yield this.cliExec.spawnAndLog(request.command, ultCwd, ultEnv, undefined, true);
                    }
                    else if (request.outputMode == terminal_command_request_1.CommandOutputModeEnum.async) {
                        // Execute ASYNC (dont wait for it)
                        output = { code: 1, stdout: "" };
                        delete output.stdout;
                        this.cliExec.spawnAndLog(request.command, ultCwd, ultEnv, undefined, true);
                        output.code = 0;
                    }
                    else {
                        // Execute STDOUT
                        output = { code: 1, stdout: "" };
                        output.stdout = yield this.cliExec.execAsyncStdOut(request.command, { cwd: ultCwd, env: ultEnv }, undefined, true);
                        output.code = 0;
                    }
                }
                return output;
            }
            catch (err) {
                throw err;
            }
        });
    }
    /** Fires the given shell command into a child process
     * that inherits the parent's stdio (input, output, and error streams),
     * and resolves the promise on exit of that child process.
     * Returns the exit code from the child process.
     *
     * @param terminalExe The executable to open the terminal.  Examples:  `powershell`, `wsl`, `bash`, `sh`
     * @param terminalArgs Arguments for the terminal executable itself.  Examples:  (for powershell: `["-c"]`, for wsl: `["-d", "ubuntu-20.04", "bash", "-l", "-c"]` OR `["-d", "ubuntu-20.04", "bash", "-l", "--"]`, for bash: `["-c"]`).  IMPORTANT!  Be sure to include the `-c` or `--` arg at the end because these arguments string will be suffixed by the `commandString` parameter of this function.
     * @param commandString the commands to execute from within the terminal process (typically delimited by "&&" or "&"). NOTE: This string will be automatically enclosed in double quotes for final execution, with any existing double-quotes escaped as `\"`, unless you set the `doNotEnquoteCommand` parameter to true on this fucntion
     * @param env If this gets passed in, it replaces the current env.  That is, no env vars from the parent get carried into the child process.
     *            So if you use this param and you still want the current env vars, you'll have to add process.env to your dictionary (use a spread operator).
     * @param collectStdOut (optional, defaults to false) Prevents piping runtime data directly to stdout and instead renders it after completion. Some processes provide runtime status messages (eg: docker's percent completes, etc), which get obstructed by piping to stdout. If so, set this argument to true.
     * @param enquoteCommand (optional, defaults to auto) Setting to true forces enclosing the command string in double quotes for final execution. By default the given commandString param will get appended as is to the terminal command when the last terminal argument is the double-dash: `--`, otherwise it will be enclosed in double quotes (with escaping).
     * */
    spawnTerminalCommands(terminalExe, terminalArgs, commandString, cwd, env, collectStdOut = false, enquoteCommand) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            return new Promise((resolve, reject) => tslib_1.__awaiter(this, void 0, void 0, function* () {
                var _a, _b;
                try {
                    // DEV NOTE:   This is a good refresher:  https://www.freecodecamp.org/news/node-js-child-processes-everything-you-need-to-know-e69498fe970a/
                    this.logger.detail(`Executing: ${commandString}  from: ${cwd}`);
                    let output = { code: 1, stdout: "" };
                    let termData;
                    let options = {
                        cwd: cwd,
                        // stdio: "inherit",
                        stdio: [process.stdin, process.stdout, process.stderr],
                        shell: false
                    };
                    if (env)
                        options.env = env;
                    // Dim = "\x1b[2m"
                    // process.stdout.write("\x1b[2m")
                    let ultimateCmd = commandString; //.join(" ");
                    const lastTermArg = (terminalArgs.length > 0) ? terminalArgs[terminalArgs.length - 1].trim() : undefined;
                    const DOUBLE_DASH = "--";
                    if (enquoteCommand == true || (enquoteCommand == undefined && lastTermArg !== DOUBLE_DASH)) {
                        ultimateCmd = "\"".concat(ultimateCmd.replace("\"", "\\\""), "\"");
                    }
                    // TO PREVENT mismatched encodings (which results in every other character being a space)
                    if (options.env == undefined)
                        options.env = {};
                    options.env["WSL_UTF8"] = "1"; // from the internet:  https://stackoverflow.com/questions/64104790/powershell-strange-wsl-output-string-encoding
                    let child = (0, child_process_1.spawn)(terminalExe, [...terminalArgs, ultimateCmd], options);
                    // Can we set child stdout to a chalk stream that colors the output with our single color?
                    // eg:          
                    //      let chalkStrm = new Writable();
                    //      chalkStrm.addListener('pipe', (src) => {
                    //        let chunk = src.read();
                    //        process.stdout.write(chalk.grey(chunk));
                    //      });
                    // NOTE ALSO: child processes tend to lose their own coloring when being piped (see link below)
                    // https://stackoverflow.com/questions/31620298/is-it-possible-for-child-processes-in-node-js-to-preserve-colored-output
                    // For since the child proc is already writing to our own stdio streams, 
                    // logging these error feeds is going to show up as duplicate output in the console. 
                    // child.stderr.on('data', (data) => { this.logger.trace(data)});  
                    if (child.stdout) {
                        child.stdout.on('data', (chunk) => {
                            var _a, _b;
                            // this.logger.trace(chunk);
                            // process.stdout.write(chalk.grey(chunk));
                            let stringChunk = (typeof chunk != 'string') ? chunk.toString("utf8") : chunk;
                            if (collectStdOut === true) {
                                output.stdout = (_b = (_a = output.stdout) === null || _a === void 0 ? void 0 : _a.concat(stringChunk)) !== null && _b !== void 0 ? _b : stringChunk;
                            }
                            else {
                                // It appears that we lose the runtime status messages 
                                // when we pipe like this (eg: docker's percent completes, etc).
                                // If so, set collectStdOut to true.
                                process.stdout.write(stringChunk); // this could be accomplished by setting stdio to: { stdout: "pipe" }
                            }
                        });
                    }
                    child.on('data', (chunk) => {
                        var _a;
                        let stringChunk = (typeof chunk != 'string') ? chunk.toString("utf8") : chunk;
                        this.logger.trace(stringChunk);
                        // process.stdout.write(chalk.grey(chunk));
                        if (collectStdOut === true) {
                            termData = (_a = termData === null || termData === void 0 ? void 0 : termData.concat(stringChunk)) !== null && _a !== void 0 ? _a : stringChunk;
                        }
                        else {
                            // It appears that we lose the runtime status messages 
                            // when we pipe like this (eg: docker's percent completes, etc).
                            // If so, set collectStdOut to true.
                            process.stdout.write(stringChunk);
                        }
                    });
                    if (child.stderr) {
                        child.stderr.on('data', (chunk) => {
                            // this.logger.trace(chunk);
                            // process.stdout.write(chalk.grey(chunk));
                            var _a, _b;
                            let stringChunk = (typeof chunk != 'string') ? chunk.toString("utf8") : chunk;
                            this.logger.error(stringChunk);
                            if (collectStdOut === true) {
                                output.stderr = (_b = (_a = output.stderr) === null || _a === void 0 ? void 0 : _a.concat(stringChunk)) !== null && _b !== void 0 ? _b : stringChunk;
                            }
                            else {
                                // It appears that we lose the runtime status messages 
                                // when we pipe like this (eg: docker's percent completes, etc).
                                // If so, set collectStdOut to true.
                                process.stderr.write(stringChunk);
                            }
                        });
                    }
                    // When we do this, something is keeping the process from exiting.  unpiping afterwards didnt help
                    // child.stdout.pipe(process.stdout);
                    // child.stderr.pipe(process.stderr);
                    // process.stdin.pipe(child.stdin);        
                    // child.on('close', (code, signal) => {
                    //   // this.logger.trace(`Command closed with code: ${code} and signal ${signal}`);
                    //   if (code == 0) {
                    //     // this.logger.trace(`Command exited ${(code != undefined) ? 'with code: ' + code : 'without code'} and ${(signal) ? 'with signal ' + signal : 'without signal'}`);
                    //     resolve(output);
                    //   } else {
                    //     // TODO:  Add the stderr output to the error object.
                    //     reject(new Error(`Error (return code: ${code})`))
                    //   }
                    // });
                    child.on('error', (err) => {
                        this.logger.error(`Command returned error ${err.message || ""}`, err);
                        reject(err);
                    });
                    child.on('exit', (code, signal) => {
                        // process.stdin.unpipe(child.stdin);
                        // child.stderr.unpipe(process.stderr);
                        // child.stdout.unpipe(process.stdout);
                        if (code != null)
                            output.code = code;
                        if (output)
                            this.logger.detail("exiting terminal command", output);
                        // reset:  \x1b[0m
                        process.stdout.write("\x1b[0m");
                        // If we're handling both close and error, we dont need this handler, right?
                        if (code == 0) {
                            // this.logger.trace(`Command exited ${(code != undefined) ? 'with code: ' + code : 'without code'} and ${(signal) ? 'with signal ' + signal : 'without signal'}`);
                            resolve(output);
                        }
                        else {
                            // TODO:  Add the stderr output to the error object.
                            reject(new Error(`Error (return code: ${code})`));
                        }
                    });
                    (_a = child.stdin) === null || _a === void 0 ? void 0 : _a.write("exit");
                    (_b = child.stdin) === null || _b === void 0 ? void 0 : _b.end();
                }
                catch (err) {
                    reject(err);
                }
            }));
        });
    }
    /** Fires the given shell command into a child process
   * that inherits the parent's stdio (input, output, and error streams),
   * and resolves the promise on exit of that child process.
   * Returns the exit code from the child process.
   * @param env If this gets passed in, it replaces the current env.  That is, no env vars from the parent get carried into the child process.
   *            So if you use this param and you still want the current env vars, you'll have to add process.env to your dictionary (use a spread operator).
   * @param collectStdOut (optional, defaults to false) Prevents piping runtime data directly to stdout and instead renders it after completion. Some processes provide runtime status messages (eg: docker's percent completes, etc), which get obstructed by piping to stdout. If so, set this argument to true.
   * */
    spawnAndLog(cmd, cwd, env, logLevel = IBetterLogger_1.LogLevel.debug, collectStdOut = false, stdoutEncoding = "utf8") {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            return new Promise((resolve, reject) => tslib_1.__awaiter(this, void 0, void 0, function* () {
                try {
                    // DEV NOTE:   This is a good refresher:  https://www.freecodecamp.org/news/node-js-child-processes-everything-you-need-to-know-e69498fe970a/
                    this.logger.write(`Executing: ${cmd}  from: ${cwd}`, logLevel);
                    let output;
                    let options = {
                        cwd: cwd,
                        // stdio: "inherit",
                        // stdio: [process.stdin, process.stdout, process.stderr],  // TODO:  Capture stderr instead
                        shell: true
                    };
                    if (env)
                        options.env = env;
                    // Dim = "\x1b[2m"
                    process.stdout.write("\x1b[2m");
                    // TO PREVENT mismatched encodings (which results in every other character being a space)
                    if (options.env == undefined)
                        options.env = {};
                    options.env["WSL_UTF8"] = "1"; // from the internet:  https://stackoverflow.com/questions/64104790/powershell-strange-wsl-output-string-encoding
                    let child = (0, child_process_1.spawn)(cmd, options);
                    // TO PREVENT mismatched encodings (which results in every other character being a space)
                    // if (stdoutEncoding) {
                    //   child.stdout?.setEncoding(stdoutEncoding);
                    // }
                    // Can we set child stdout to a chalk stream that colors the output with our single color?
                    // eg:          
                    //      let chalkStrm = new Writable();
                    //      chalkStrm.addListener('pipe', (src) => {
                    //        let chunk = src.read();
                    //        process.stdout.write(chalk.grey(chunk));
                    //      });
                    // NOTE ALSO: child processes tend to lose their own coloring when being piped (see link below)
                    // https://stackoverflow.com/questions/31620298/is-it-possible-for-child-processes-in-node-js-to-preserve-colored-output
                    // For since the child proc is already writing to our own stdio streams, 
                    // logging these error feeds is going to show up as duplicate output in the console. 
                    // child.stderr.on('data', (data) => { this.logger.trace(data)});  
                    if (child.stdout) {
                        child.stdout.on('data', (chunk) => {
                            // this.logger.trace(chunk);
                            // process.stdout.write(chalk.grey(chunk));
                            var _a;
                            let stringChunk = (typeof chunk != 'string') ? chunk.toString("utf8") : chunk;
                            this.logger.detail(stringChunk);
                            if (collectStdOut === true) {
                                output = (_a = output === null || output === void 0 ? void 0 : output.concat(stringChunk)) !== null && _a !== void 0 ? _a : stringChunk;
                            }
                            else {
                                // It appears that we lose the runtime status messages 
                                // when we pipe like this (eg: docker's percent completes, etc).
                                // If so, set collectStdOut to true.
                                process.stdout.write(stringChunk);
                            }
                        });
                    } // else {
                    child.on('data', (chunk) => {
                        var _a;
                        let stringChunk = (typeof chunk != 'string') ? chunk.toString("utf8") : chunk;
                        this.logger.detail(stringChunk);
                        // process.stdout.write(chalk.grey(chunk));
                        if (collectStdOut === true) {
                            output = (_a = output === null || output === void 0 ? void 0 : output.concat(stringChunk)) !== null && _a !== void 0 ? _a : stringChunk;
                        }
                        else {
                            // It appears that we lose the runtime status messages 
                            // when we pipe like this (eg: docker's percent completes, etc).
                            // If so, set collectStdOut to true.
                            process.stdout.write(stringChunk);
                        }
                    });
                    // }
                    if (child.stderr) {
                        child.stderr.on('data', (chunk) => {
                            // this.logger.trace(chunk);
                            // process.stdout.write(chalk.grey(chunk));
                            var _a;
                            let stringChunk = (typeof chunk != 'string') ? chunk.toString("utf8") : chunk;
                            this.logger.error(stringChunk);
                            if (collectStdOut === true) {
                                output = (_a = output === null || output === void 0 ? void 0 : output.concat(stringChunk)) !== null && _a !== void 0 ? _a : stringChunk;
                            }
                            else {
                                // It appears that we lose the runtime status messages 
                                // when we pipe like this (eg: docker's percent completes, etc).
                                // If so, set collectStdOut to true.
                                process.stdout.write(stringChunk);
                            }
                        });
                    }
                    // When we do this, something is keeping the process from exiting.  unpiping afterwards didnt help
                    // child.stdout.pipe(process.stdout);
                    // child.stderr.pipe(process.stderr);
                    // process.stdin.pipe(child.stdin);        
                    child.on('close', (code, signal) => {
                        // this.logger.trace(`Command closed with code: ${code} and signal ${signal}`);
                        if (code == 0) {
                            // this.logger.trace(`Command exited ${(code != undefined) ? 'with code: ' + code : 'without code'} and ${(signal) ? 'with signal ' + signal : 'without signal'}`);
                            resolve([code, output]);
                        }
                        else {
                            // TODO:  Add the stderr output to the error object.
                            reject(new Error(`Error (return code: ${code})`));
                        }
                    });
                    child.on('error', (err) => {
                        this.logger.error(`Command returned error ${err.message || ""}`, err);
                        reject(err);
                    });
                    child.on('exit', (code, signal) => {
                        // process.stdin.unpipe(child.stdin);
                        // child.stderr.unpipe(process.stderr);
                        // child.stdout.unpipe(process.stdout);
                        if (output)
                            this.logger.write(output, logLevel);
                        // reset:  \x1b[0m
                        process.stdout.write("\x1b[0m");
                        // If we're handling both close and error, we dont need this handler, right?
                        if (code == 0) {
                            // this.logger.trace(`Command exited ${(code != undefined) ? 'with code: ' + code : 'without code'} and ${(signal) ? 'with signal ' + signal : 'without signal'}`);
                            resolve([code, output]);
                        }
                        else {
                            // TODO:  Add the stderr output to the error object.
                            reject(new Error(`Error (return code: ${code})`));
                        }
                    });
                }
                catch (err) {
                    reject(err);
                }
            }));
        });
    }
    /** Fires the given shell command into a child process
   * that inherits the parent's stdio (input, output, and error streams),
   * and resolves the promise on exit of that child process.
   * Returns the exit code from the child process.
   *
   * @param terminalExe The executable to open the terminal.  Examples:  `powershell`, `wsl`, `bash`, `sh`
   * @param terminalArgs Arguments for the terminal executable itself.  Examples:  (for powershell: `["-c"]`, for wsl: `["-d", "ubuntu-20.04", "bash", "-l", "-c"]` OR `["-d", "ubuntu-20.04", "bash", "-l", "--"]`, for bash: `["-c"]`).  IMPORTANT!  Be sure to include the `-c` or `--` arg at the end because these arguments string will be suffixed by the `commandString` parameter of this function.
   * @param commandString the commands to execute from within the terminal process (typically delimited by "&&" or "&"). NOTE: This string will be automatically enclosed in double quotes for final execution, with any existing double-quotes escaped as `\"`, unless you set the `doNotEnquoteCommand` parameter to true on this fucntion
   * @param env If this gets passed in, it replaces the current env.  That is, no env vars from the parent get carried into the child process.
   *            So if you use this param and you still want the current env vars, you'll have to add process.env to your dictionary (use a spread operator).
   * @param collectStdOut (optional, defaults to false) Prevents piping runtime data directly to stdout and instead renders it after completion. Some processes provide runtime status messages (eg: docker's percent completes, etc), which get obstructed by piping to stdout. If so, set this argument to true.
   * @param enquoteCommand (optional, defaults to auto) Setting to true forces enclosing the command string in double quotes for final execution. By default the given commandString param will get appended as is to the terminal command when the last terminal argument is the double-dash: `--`, otherwise it will be enclosed in double quotes (with escaping).
   * */
    openTerminal(terminalExe, terminalArgs, cwd, env, collectStdOut = false, enquoteCommand) {
        var _a;
        // let termData: string | undefined;
        // let openResult: CommandResponse = { code: 1, stdout: "" };
        try {
            // DEV NOTE:   This is a good refresher:  https://www.freecodecamp.org/news/node-js-child-processes-everything-you-need-to-know-e69498fe970a/
            this.logger.detail("Opening ".concat(terminalExe, " terminal..."));
            // let output: string | undefined;
            let options = {
                cwd: cwd,
                // stdio: "inherit",
                // stdio: ['pipe', process.stdout, process.stderr],  // TODO:  Capture stderr instead
                shell: false
            };
            if (env)
                options.env = env;
            // Dim = "\x1b[2m"
            // process.stdout.write("\x1b[2m")
            // TO PREVENT mismatched encodings (which results in every other character being a space)
            if (options.env == undefined)
                options.env = {};
            options.env["WSL_UTF8"] = "1"; // from the internet:  https://stackoverflow.com/questions/64104790/powershell-strange-wsl-output-string-encoding
            let child = (0, child_process_1.spawn)(terminalExe, terminalArgs, options);
            // Can we set child stdout to a chalk stream that colors the output with our single color?
            // eg:          
            //      let chalkStrm = new Writable();
            //      chalkStrm.addListener('pipe', (src) => {
            //        let chunk = src.read();
            //        process.stdout.write(chalk.grey(chunk));
            //      });
            // NOTE ALSO: child processes tend to lose their own coloring when being piped (see link below)
            // https://stackoverflow.com/questions/31620298/is-it-possible-for-child-processes-in-node-js-to-preserve-colored-output
            // For since the child proc is already writing to our own stdio streams, 
            // logging these error feeds is going to show up as duplicate output in the console. 
            // child.stderr.on('data', (data) => { this.logger.trace(data)});  
            // ----------------------------
            // Set event handlers
            // ----------------------------
            // child.on("error", (err) => {
            //   this.logger.error("Error in terminal command: ".concat(err.name ?? "", " ", err.message ?? ""), err);
            //   openResult.error = err;
            //   // reject(err);
            // });
            // child.on('data', (chunk) => {
            //   // this.logger.trace(" on terminal data");
            //   let stringChunk = (typeof chunk != 'string') ? chunk.toString("utf8") : chunk;
            //   termData = termData?.concat(stringChunk) ?? stringChunk;
            // });
            // child.on("exit", (code, signal) => {
            //   if (code != null) openResult.code = code;
            //   this.logger.debug("Resolving on terminal exit", openResult);
            //   // resolve(response);
            // });
            // if (child.stdout) {
            //   child.stdout.on('data', (chunk) => {
            //     // this.logger.trace(chunk);
            //     // process.stdout.write(chalk.grey(chunk));
            //     let stringChunk = (typeof chunk != 'string') ? chunk.toString("utf8") : chunk;
            //     if (collectStdOut === true) {
            //       output = output?.concat(stringChunk) ?? stringChunk;
            //     } else {
            //       // It appears that we lose the runtime status messages 
            //       // when we pipe like this (eg: docker's percent completes, etc).
            //       // If so, set collectStdOut to true.
            //       process.stdout.write(stringChunk);
            //     }
            //   })
            //   child.stdout.on('readable', () => {
            //     this.logger.debug("stdout is now readable");
            //   })
            // }
            // if (child.stderr) {
            //   child.stderr.on('data', (chunk) => {
            //     // this.logger.trace(chunk);
            //     // process.stdout.write(chalk.grey(chunk));
            //     let stringChunk = (typeof chunk != 'string') ? chunk.toString("utf8") : chunk;
            //     this.logger.error(stringChunk);
            //     if (collectStdOut === true) {
            //       output = output?.concat(stringChunk) ?? stringChunk;
            //     } else {
            //       // It appears that we lose the runtime status messages 
            //       // when we pipe like this (eg: docker's percent completes, etc).
            //       // If so, set collectStdOut to true.
            //       process.stdout.write(stringChunk);
            //     }
            //   })
            //   child.stderr.on('error', (err) => {
            //     // this.logger.trace(chunk);
            //     // process.stdout.write(chalk.grey(chunk));
            //     this.logger.error(err.message);
            //     console.dir(err);
            //     if (collectStdOut === true) {
            //       output = output?.concat(err.message) ?? err.message;
            //     } else {
            //       // It appears that we lose the runtime status messages 
            //       // when we pipe like this (eg: docker's percent completes, etc).
            //       // If so, set collectStdOut to true.
            //       process.stdout.write(err.message);
            //     }
            //   })
            // }
            // child.on('data', (chunk) => {
            // 
            //   let stringChunk = (typeof chunk != 'string') ? chunk.toString("utf8") : chunk;
            //   this.logger.trace(stringChunk);
            //   // process.stdout.write(chalk.grey(chunk));
            // 
            //   if (collectStdOut === true) {
            //     output = output?.concat(stringChunk) ?? stringChunk;
            //   } else {
            //     // It appears that we lose the runtime status messages 
            //     // when we pipe like this (eg: docker's percent completes, etc).
            //     // If so, set collectStdOut to true.
            //     process.stdout.write(stringChunk);
            //   }
            // })
            // // When we do this, something is keeping the process from exiting.  unpiping afterwards didnt help
            // // child.stdout.pipe(process.stdout);
            // // child.stderr.pipe(process.stderr);
            // // process.stdin.pipe(child.stdin);        
            // child.on('close', (code, signal) => {
            //   // this.logger.trace(`Command closed with code: ${code} and signal ${signal}`);
            //   if (code == 0) {
            //     // this.logger.trace(`Command exited ${(code != undefined) ? 'with code: ' + code : 'without code'} and ${(signal) ? 'with signal ' + signal : 'without signal'}`);
            //     this.logger.debug("Closing ".concat(terminalExe, " child process [pid: ", child.pid.toString(), "] with code ".concat(code.toString())));
            //     // resolve([code, output]);
            //   } else {
            //     // TODO:  Add the stderr output to the error object.
            //     // reject(new Error(`Error (return code: ${code})`))
            //     throw new Error(`Error (return code: ${code})`);  // <--- where will this get caught?
            //   }
            // });
            child.on('error', (err) => {
                this.logger.error(`Open Terminal command returned error ${err.message || ""}`, err);
                // reject(err);
                throw err; // <--- where will this get caught?
            });
            // child.on('message', (msg, sendHandle) => {
            //   let serialized = msg.toString();
            //   this.logger.debug("child process message: ".concat(serialized))
            // })
            // child.on('exit', (code, signal) => {
            //   // process.stdin.unpipe(child.stdin);
            //   // child.stderr.unpipe(process.stderr);
            //   // child.stdout.unpipe(process.stdout);
            //   if (output) this.logger.detail(output);
            //   // reset:  \x1b[0m
            //   process.stdout.write("\x1b[0m")
            //   // If we're handling both close and error, we dont need this handler, right?
            //   if (code == 0) {
            //     // this.logger.trace(`Command exited ${(code != undefined) ? 'with code: ' + code : 'without code'} and ${(signal) ? 'with signal ' + signal : 'without signal'}`);
            //     // resolve([code, output]);
            //     this.logger.debug("Closing ".concat(terminalExe, " child process [pid: ", child.pid.toString(), "] with code ".concat(code.toString())));
            //   } else {
            //     // TODO:  Add the stderr output to the error object.
            //     // reject(new Error(`Error (return code: ${code})`))
            //     throw new Error(`Error (return code: ${code})`);  // <--- where will this get caught?
            //   }
            // });
            return (child);
        }
        catch (err) {
            this.logger.error("Error spawning ".concat(terminalExe, " terminal process: ", (_a = err.message) !== null && _a !== void 0 ? _a : ""));
            throw err;
        }
    }
    executeInExistingTerminal(terminal, command) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            return new Promise((resolve, reject) => tslib_1.__awaiter(this, void 0, void 0, function* () {
                var _a, _b, _c, _d, _e, _f, _g, _h, _j;
                let response = { code: 1, stdout: "" };
                try {
                    // terminal.removeAllListeners();
                    (_a = terminal.stdout) === null || _a === void 0 ? void 0 : _a.removeAllListeners();
                    (_b = terminal.stderr) === null || _b === void 0 ? void 0 : _b.removeAllListeners();
                    let termData;
                    // ----------------------------------------
                    //  terminal events
                    // ----------------------------------------
                    terminal.on("error", (err) => {
                        var _a, _b;
                        this.logger.error("Error in terminal command: ".concat((_a = err.name) !== null && _a !== void 0 ? _a : "", " ", (_b = err.message) !== null && _b !== void 0 ? _b : ""), err);
                        response.error = err;
                        reject(err);
                    });
                    terminal.on('data', (chunk) => {
                        var _a;
                        // this.logger.trace(" on terminal data");
                        let stringChunk = (typeof chunk != 'string') ? chunk.toString("utf8") : chunk;
                        termData = (_a = termData === null || termData === void 0 ? void 0 : termData.concat(stringChunk)) !== null && _a !== void 0 ? _a : stringChunk;
                    });
                    terminal.on("exit", (code, signal) => {
                        if (code != null)
                            response.code = code;
                        this.logger.debug("Resolving on terminal exit", response);
                        resolve(response);
                    });
                    // terminal.on("disconnect", () => {
                    //   this.logger.trace("on terminal disconnect");
                    //   // resolve()
                    // });
                    // terminal.on("close", () => {
                    //   this.logger.trace("on terminal close");
                    //   // resolve()
                    // });
                    // terminal.on("message", (msg, sendhandle) => {
                    //   this.logger.trace("on terminal message");
                    //   // resolve()
                    // });
                    // ----------------------------------------
                    // stdout events
                    // ----------------------------------------
                    (_c = terminal.stdout) === null || _c === void 0 ? void 0 : _c.on('error', (err) => {
                        var _a, _b;
                        this.logger.trace("Error from stdout during terminal command: ".concat((_a = err.name) !== null && _a !== void 0 ? _a : "", " ", (_b = err.message) !== null && _b !== void 0 ? _b : ""), err);
                        response.error = err;
                        reject(err);
                    });
                    (_d = terminal.stdout) === null || _d === void 0 ? void 0 : _d.on('end', () => {
                        this.logger.debug("Terminal command end of stdout", response.stdout);
                        resolve(response);
                    });
                    (_e = terminal.stdout) === null || _e === void 0 ? void 0 : _e.on('data', (chunk) => {
                        var _a, _b;
                        // this.logger.trace(" on terminal stdout data");
                        let stringChunk = (typeof chunk != 'string') ? chunk.toString("utf8") : chunk;
                        response.stdout = (_b = (_a = response.stdout) === null || _a === void 0 ? void 0 : _a.concat(stringChunk)) !== null && _b !== void 0 ? _b : stringChunk;
                    });
                    // terminal.stdout?.on('close', () => {
                    //   // this.logger.trace("on terminal stdout close");
                    // });
                    // terminal.stdout?.on('readable', () => {
                    //   // this.logger.trace("terminal stdout readable");
                    // });
                    // ----------------------------------------
                    // stderr events
                    // ----------------------------------------
                    (_f = terminal.stderr) === null || _f === void 0 ? void 0 : _f.on('error', (err) => {
                        var _a, _b;
                        this.logger.trace("Error from stderr during terminal command: ".concat((_a = err.name) !== null && _a !== void 0 ? _a : "", " ", (_b = err.message) !== null && _b !== void 0 ? _b : ""), err);
                        response.error = err;
                        reject(err);
                    });
                    (_g = terminal.stderr) === null || _g === void 0 ? void 0 : _g.on('data', (chunk) => {
                        var _a, _b;
                        // this.logger.trace(" on terminal stderr data");
                        let stringChunk = (typeof chunk != 'string') ? chunk.toString("utf8") : chunk;
                        response.stderr = (_b = (_a = response.stderr) === null || _a === void 0 ? void 0 : _a.concat(stringChunk)) !== null && _b !== void 0 ? _b : stringChunk;
                    });
                    (_h = terminal.stderr) === null || _h === void 0 ? void 0 : _h.on('end', () => {
                        this.logger.debug("Terminal command end of stderr", response.stderr);
                        resolve(response);
                    });
                    // terminal.stderr?.on('close', () => {
                    //   // this.logger.trace("terminal stderr close");
                    // });
                    // ISSUE THE COMMAND
                    if (terminal.stdin) {
                        this.logger.debug("Executing command in terminal: ".concat(command));
                        terminal.stdin.write(command);
                        terminal.stdin.end(); // <-- acts as the user hitting the "ENTER" key in a cli console
                    }
                    else {
                        throw new Error("Cannot send command into terminal via stdin; process has a null stdin.");
                    }
                    this.logger.trace("Command was issued.");
                }
                catch (err) {
                    this.logger.error("Error executing terminal commmand: ".concat(err.message, " ", (_j = response.stderr) !== null && _j !== void 0 ? _j : ""), err);
                    reject(err);
                }
            }));
        });
    }
    /** Returns the exit code (NOTE: this exit code reflects the exit code of the most recently executed command). */
    closeTerminal(term) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            return new Promise((resolve, reject) => tslib_1.__awaiter(this, void 0, void 0, function* () {
                var _a, _b, _c;
                try {
                    const DEFAULT_EXIT_CODE = 1; // this should never happen
                    (_a = term.stdout) === null || _a === void 0 ? void 0 : _a.removeAllListeners();
                    (_b = term.stderr) === null || _b === void 0 ? void 0 : _b.removeAllListeners();
                    term.removeAllListeners();
                    term.on("close", () => {
                        this.logger.debug("terminal close");
                    });
                    term.on("exit", (code) => {
                        var _a;
                        this.logger.debug("Resolving on terminal exit ".concat("(exit code: ", (_a = code === null || code === void 0 ? void 0 : code.toString()) !== null && _a !== void 0 ? _a : "not available", ")"));
                        resolve(code !== null && code !== void 0 ? code : DEFAULT_EXIT_CODE);
                    });
                    term.on("message", (msg, sendHandle) => {
                        this.logger.debug("terminal message");
                    });
                    term.on("error", (err) => {
                        this.logger.error("Rejecting on terminal error: ".concat(err.message), err);
                        reject(err);
                    });
                    this.logger.debug("Closing terminal...");
                    if (term.stdin && (term.stdin.writable == true)) {
                        term.stdin.write("exit");
                        term.stdin.end(); // <-- acts as the user hitting the "ENTER" key in a cli console
                    }
                    else {
                        this.logger.debug("Cannot send an exit command to the terminal bc the child process has no stdin; killing the terminal process instead.");
                        if (!term.killed) {
                            let done = term.kill();
                            this.logger.debug("Terminal killed: ".concat(done.toString()));
                        }
                        else {
                            this.logger.debug("Terminal already killed.");
                        }
                    }
                }
                catch (err) {
                    this.logger.error("Error attempting to close terminal: ".concat((_c = err.message) !== null && _c !== void 0 ? _c : "(check logs for details)"), err.stack);
                    reject(err);
                }
            }));
        });
    }
    /** Equivalent of: `openTerminal() + executeInTerminal() + closeTerminal()`.
     *
     */
    executeSingleCommandInTerminal(terminalExe, terminalArgs, command, cwd, env) {
        var _a;
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            let termA;
            let code;
            let response;
            try {
                termA = yield this.openTerminal(terminalExe, [...terminalArgs], process.cwd(), process.env);
                response = yield this.executeInExistingTerminal(termA, command);
            }
            catch (err) {
                this.logger.error("Error executing terminal command: ".concat((_a = err.message) !== null && _a !== void 0 ? _a : "(see log for details)"), err);
                throw err;
            }
            finally {
                if (termA) {
                    code = yield this.closeTerminal(termA); // exit code isnt available until now
                }
            }
            if (code != undefined)
                response.code = code;
            return response;
        });
    }
};
TerminalUtils = tslib_1.__decorate([
    (0, inversify_1.injectable)(),
    tslib_1.__param(0, (0, inversify_1.inject)(codiac_common_1.INTERFACES.IBetterLogger)),
    tslib_1.__metadata("design:paramtypes", [Object])
], TerminalUtils);
exports.TerminalUtils = TerminalUtils;
//# sourceMappingURL=terminal-utils.js.map