@hashgraph/solo
Version:
An opinionated CLI tool to deploy and manage private Hedera Networks.
151 lines • 7.21 kB
JavaScript
// 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); }
};
var ShellRunner_1;
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';
import { SensitiveDataRedactor } from './util/sensitive-data-redactor.js';
let ShellRunner = ShellRunner_1 = class ShellRunner {
logger;
constructor(logger) {
this.logger = logger;
this.logger = patchInject(logger, InjectTokens.SoloLogger, this.constructor.name);
}
/**
* Redacts sensitive arguments from a command array.
* Delegates to the shared {@link SensitiveDataRedactor} utility.
* @param arguments_ The arguments array to redact
* @returns A new redacted arguments array
*/
static redactArguments(arguments_) {
return SensitiveDataRedactor.redactArguments(arguments_, {
flagsToRedactNextArgument: ['--password', '-p'],
setStyleFlags: ['--set', '--set-string', '--set-file'],
});
}
/** Returns a promise that invokes the shell command */
async run(cmd, arguments_ = [], verbose = false, detached = false, environmentVariablesToAppend = {}, timeoutMs) {
const redactedArguments = ShellRunner_1.redactArguments(arguments_);
const message = `Executing command${OperatingSystem.isWin32() ? ' (Windows)' : ''}: ${cmd} ${redactedArguments.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 = ShellRunner_1 = __decorate([
injectable(),
__param(0, inject(InjectTokens.SoloLogger)),
__metadata("design:paramtypes", [Object])
], ShellRunner);
export { ShellRunner };
//# sourceMappingURL=shell-runner.js.map