UNPKG

@hashgraph/solo

Version:

An opinionated CLI tool to deploy and manage private Hedera Networks.

229 lines 8.87 kB
// SPDX-License-Identifier: Apache-2.0 import { spawn } from 'node:child_process'; import { KindExecutionException } from '../errors/kind-execution-exception.js'; import { KindParserException } from '../errors/kind-parser-exception.js'; /** * Represents the execution of a kind command and is responsible for parsing the response. */ export class KindExecution { /** * The message for a timeout error. */ static MSG_TIMEOUT_ERROR = 'Timed out waiting for the process to complete'; /** * The message for an error deserializing the output into a specified class. */ static MSG_DESERIALIZATION_ERROR = 'Failed to deserialize the output into the specified class: %s'; /** * The message for an error reading the output from the process. */ static MSG_READ_OUTPUT_ERROR = 'Failed to read the output from the process'; /** * The message for a deserialization error. */ static MSG_LIST_DESERIALIZATION_ERROR = 'Failed to deserialize the output into a list of the specified class: %s'; process; output = []; errOutput = []; exitCodeValue = null; /** * Creates a new KindExecution instance. * @param command The command array to execute * @param environmentVariables The environment variables to set */ constructor(command, environmentVariables) { this.process = spawn(command.join(' '), { shell: true, env: { ...process.env, ...environmentVariables }, }); } /** * Waits for the process to complete. * @returns A promise that resolves when the process completes */ async waitFor() { return new Promise((resolve, reject) => { // const output: string[] = []; this.process.stdout.on('data', (d) => { const items = d.toString().split(/\r?\n/); for (const item of items) { if (item) { this.output.push(item); } } }); this.process.stderr.on('data', (d) => { const items = d.toString().split(/\r?\n/); for (const item of items) { if (item) { this.errOutput.push(item.trim()); } } }); this.process.on('close', (code) => { this.exitCodeValue = code; if (code === 0) { resolve(); } else { reject(new KindExecutionException(code || 1, `Process exited with code ${code}: ${this.standardError()}`, this.standardOutput(), this.standardError())); } }); }); } /** * Waits for the process to complete with a timeout. * @param timeout The maximum time to wait, or null to wait indefinitely * @returns A promise that resolves with true if the process completed, or false if it timed out */ async waitForTimeout(timeout) { const timeoutPromise = new Promise((resolve) => { setTimeout(() => resolve(false), timeout.toMillis()); }); const successPromise = new Promise((resolve) => { this.process.on('close', (code) => { resolve(code === 0); }); }); return Promise.race([successPromise, timeoutPromise]); } /** * Gets the exit code of the process. * @returns The exit code or null if the process hasn't completed */ exitCode() { return this.exitCodeValue; } /** * Gets the standard output of the process. * @returns concatenated standard output as a string */ standardOutput() { return this.output.join('\n'); } /** * Gets the standard error of the process. * @returns concatenated standard error as a string */ standardError() { return this.errOutput.join('\n'); } /** * Gets the response as a parsed object. * @param responseClass The class to parse the response into * @returns A promise that resolves with the parsed response */ async responseAs(responseClass) { return this.responseAsTimeout(responseClass, null); } /** * Gets the response as a parsed object with a timeout. * @param responseClass The class to parse the response into * @param timeout The maximum time to wait, or null to wait indefinitely * @returns A promise that resolves with the parsed response or rejects on timeout */ async responseAsTimeout(responseClass, timeout) { if (timeout === null) { await this.waitFor(); } else { const success = await this.waitForTimeout(timeout); if (!success) { throw new KindParserException(KindExecution.MSG_TIMEOUT_ERROR); } } const exitCode = this.exitCode(); if (exitCode !== 0) { const stdOut = this.standardOutput(); const stdError = this.standardError(); throw new KindExecutionException(exitCode, `Process exited with code ${exitCode}`, stdOut, stdError); } if (responseClass === undefined) { return null; } const stdOut = this.standardOutput(); // Kind outputs to stdErr, so when the exit code is 0, we can assume stdErr is the expected output logs. const stdLogs = this.standardError(); // If both stdOut and stdLogs are empty, we throw an error. const output = stdOut || stdLogs; if (!output) { throw new KindParserException(KindExecution.MSG_READ_OUTPUT_ERROR); } try { const parsed = output.split(/\r?\n/).filter((line) => line.trim() !== ''); return new responseClass(...parsed); } catch { throw new KindParserException(KindExecution.MSG_DESERIALIZATION_ERROR.replace('%s', responseClass.name)); } } /** * Gets the response as a list of parsed objects. * @param responseClass The class to parse each item in the response into * @returns A promise that resolves with the parsed response list */ async responseAsList(responseClass) { return this.responseAsListTimeout(responseClass, null); } /** * Gets the response as a list of parsed objects with a timeout. * @param responseClass The class to parse each item in the response into * @param timeout The maximum time to wait, or null to wait indefinitely * @returns A promise that resolves with the parsed response list or rejects on timeout */ async responseAsListTimeout(responseClass, timeout) { if (timeout === null) { await this.waitFor(); } else { const success = await this.waitForTimeout(timeout); if (!success) { throw new KindParserException(KindExecution.MSG_TIMEOUT_ERROR); } } const exitCode = this.exitCode(); if (exitCode !== 0) { const stdOut = this.standardOutput(); const stdError = this.standardError(); throw new KindExecutionException(exitCode, `Process exited with code ${exitCode}`, stdOut, stdError); } const output = this.standardOutput(); try { const splitOutput = output.split(/\r?\n/).filter((line) => line.trim() !== ''); return splitOutput.map(line => new responseClass(...line.split(','))); } catch { throw new KindParserException(KindExecution.MSG_LIST_DESERIALIZATION_ERROR.replace('%s', responseClass.name)); } } /** * Executes the command and waits for completion. * @returns A promise that resolves when the command completes */ async call() { await this.callTimeout(null); } /** * Executes the command and waits for completion with a timeout. * @param timeout The maximum time to wait, or null to wait indefinitely * @returns A promise that resolves when the command completes or rejects on timeout */ async callTimeout(timeout) { if (timeout === null) { await this.waitFor(); } else { const success = await this.waitForTimeout(timeout); if (!success) { throw new KindParserException(KindExecution.MSG_TIMEOUT_ERROR); } } const exitCode = this.exitCode(); if (exitCode !== 0) { const stdOut = this.standardOutput(); const stdError = this.standardError(); throw new KindExecutionException(exitCode, `Process exited with code ${exitCode}`, stdOut, stdError); } } } //# sourceMappingURL=kind-execution.js.map