// SPDX-License-Identifier: Apache-2.0
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
import { spawn } from 'node:child_process';
import chalk from 'chalk';
import { inject, injectable } from 'tsyringe-neo';
import { patchInject } from './dependency-injection/container-helper.js';
import { InjectTokens } from './dependency-injection/inject-tokens.js';
import { OperatingSystem } from '../business/utils/operating-system.js';
let ShellRunner = class ShellRunner {
    logger;
    constructor(logger) {
        this.logger = logger;
        this.logger = patchInject(logger, InjectTokens.SoloLogger, this.constructor.name);
    }
    /** Returns a promise that invokes the shell command */
    async run(cmd, arguments_ = [], verbose = false, detached = false, environmentVariablesToAppend = {}, timeoutMs) {
        const message = `Executing command${OperatingSystem.isWin32() ? ' (Windows)' : ''}: ${cmd} ${arguments_.join(' ')}`;
        const callStack = new Error(message).stack; // capture the callstack to be included in error
        this.logger.info(message);
        return new Promise((resolve, reject) => {
            const child = spawn(cmd, arguments_, {
                env: { ...process.env, ...environmentVariablesToAppend },
                shell: true,
                detached,
                stdio: detached ? 'ignore' : undefined,
                windowsHide: OperatingSystem.isWin32(), // hide the console window on Windows
            });
            if (detached) {
                child.unref(); // allow the parent process to exit independently of this child
                resolve([]);
                return;
            }
            let timedOut = false;
            let timeoutHandle;
            if (timeoutMs !== undefined) {
                timeoutHandle = setTimeout(() => {
                    timedOut = true;
                    child.kill();
                    const error = new Error(`Command timed out after ${timeoutMs}ms: '${cmd}'`);
                    error.stack = callStack;
                    reject(error);
                }, timeoutMs);
            }
            const output = [];
            child.stdout.on('data', (data) => {
                const items = data.toString().split(/\r?\n/);
                for (const item of items) {
                    if (item) {
                        output.push(item);
                    }
                }
            });
            const errorOutput = [];
            child.stderr.on('data', (data) => {
                const items = data.toString().split(/\r?\n/);
                for (const item of items) {
                    if (item) {
                        errorOutput.push(item.trim());
                    }
                }
            });
            child.on('exit', (code, signal) => {
                if (timeoutHandle) {
                    clearTimeout(timeoutHandle);
                }
                if (timedOut) {
                    return; // already rejected by timeout handler
                }
                if (code) {
                    const error = new Error(`Command exit with error code ${code}, [command: '${cmd}'], [message: '${errorOutput.join('\n')}']`);
                    // include the callStack to the parent run() instead of from inside this handler.
                    // this is needed to ensure we capture the proper callstack for easier debugging.
                    error.stack = callStack;
                    if (verbose) {
                        for (const m of errorOutput) {
                            this.logger.showUser(chalk.red(m));
                        }
                    }
                    this.logger.error(`Error executing: '${cmd}'`, {
                        commandExitCode: code,
                        commandExitSignal: signal,
                        commandOutput: output,
                        errOutput: errorOutput,
                        error: { message: error.message, stack: error.stack },
                    });
                    reject(error);
                    return;
                }
                this.logger.debug(`Finished executing: '${cmd}', ${JSON.stringify({
                    commandExitCode: code,
                    commandExitSignal: signal,
                    commandOutput: output,
                    errOutput: errorOutput,
                })}`);
                resolve(output);
            });
        });
    }
    async sudoRun(sudoRequested, sudoGranted, cmd, arguments_ = [], verbose = false, detached = false, environmentVariablesToAppend = {}) {
        // Use Promise.race to handle sudo whoami and timeout
        let whoamiResolved = false;
        const whoamiPromise = this.run('sudo whoami').then(async (result) => {
            whoamiResolved = true;
            sudoGranted('Root access granted.');
            return result;
        });
        // eslint-disable-next-line no-async-promise-executor
        const timeoutPromise = new Promise(async (resolve) => {
            await new Promise(callback => setTimeout(callback, 500));
            if (!whoamiResolved) {
                sudoRequested('Please provide root permissions to proceed...');
            }
            resolve([]);
        });
        await Promise.race([whoamiPromise, timeoutPromise]);
        return this.run(`sudo ${cmd}`, arguments_, verbose, detached, environmentVariablesToAppend);
    }
};
ShellRunner = __decorate([
    injectable(),
    __param(0, inject(InjectTokens.SoloLogger)),
    __metadata("design:paramtypes", [Object])
], ShellRunner);
export { ShellRunner };
//# sourceMappingURL=shell-runner.js.map