@hashgraph/solo
Version:
An opinionated CLI tool to deploy and manage private Hedera Networks.
1,657 lines (1,543 loc) • 95.6 kB
text/typescript
// SPDX-License-Identifier: Apache-2.0
import * as constants from '../core/constants.js';
import * as version from '../../version.js';
import {type CommandFlag, type CommandFlags} from '../types/flag-types.js';
import fs from 'node:fs';
import {IllegalArgumentError} from '../core/errors/illegal-argument-error.js';
import {SoloError} from '../core/errors/solo-error.js';
import {ListrInquirerPromptAdapter} from '@listr2/prompt-adapter-inquirer';
import {
select as selectPrompt,
input as inputPrompt,
number as numberPrompt,
confirm as confirmPrompt,
} from '@inquirer/prompts';
import {type AnyListrContext, type AnyObject, type AnyYargs} from '../types/aliases.js';
import {type ClusterReferenceName} from '../types/index.js';
import {type Optional, type SoloListrTaskWrapper} from '../types/index.js';
import {PathEx} from '../business/utils/path-ex.js';
import validator from 'validator';
export class Flags {
public static KEY_COMMON: string = '_COMMON_';
private static async prompt(
type: 'toggle' | 'input' | 'number',
task: SoloListrTaskWrapper<AnyListrContext>,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
input: any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
defaultValue: Optional<any>,
promptMessage: string,
emptyCheckMessage: Optional<string>,
flagName: string,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
): Promise<any> {
try {
let needsPrompt: boolean = type === 'toggle' ? input === undefined || typeof input !== 'boolean' : !input;
needsPrompt = type === 'number' ? typeof input !== 'number' : needsPrompt;
if (needsPrompt) {
if (!process.stdout.isTTY || !process.stdin.isTTY) {
// this is to help find issues with prompts running in non-interactive mode, user should supply quite mode,
// or provide all flags required for command
throw new SoloError('Cannot prompt for input in non-interactive mode');
}
const promptOptions = {default: defaultValue, message: promptMessage};
switch (type) {
case 'input': {
input = await task.prompt(ListrInquirerPromptAdapter).run(inputPrompt, promptOptions);
break;
}
case 'toggle': {
input = await task.prompt(ListrInquirerPromptAdapter).run(confirmPrompt, promptOptions);
break;
}
case 'number': {
input = await task.prompt(ListrInquirerPromptAdapter).run(numberPrompt, promptOptions);
break;
}
}
}
if (emptyCheckMessage && !input) {
throw new SoloError(emptyCheckMessage);
}
return input;
} catch (error) {
throw new SoloError(`input failed: ${flagName}: ${error.message}`, error);
}
}
private static async promptText(
task: SoloListrTaskWrapper<AnyListrContext>,
input: string,
defaultValue: Optional<string>,
promptMessage: string,
emptyCheckMessage: string | null,
flagName: string,
): Promise<string> {
return await Flags.prompt('input', task, input, defaultValue, promptMessage, emptyCheckMessage, flagName);
}
private static async promptToggle(
task: SoloListrTaskWrapper<AnyListrContext>,
input: boolean,
defaultValue: Optional<boolean>,
promptMessage: string,
emptyCheckMessage: string | null,
flagName: string,
): Promise<boolean> {
return await Flags.prompt('toggle', task, input, defaultValue, promptMessage, emptyCheckMessage, flagName);
}
/**
* Disable prompts for the given set of flags
* @param flags list of flags to disable prompts for
*/
public static disablePrompts(flags: CommandFlag[]): void {
Flags.resetDisabledPrompts();
for (const flag of flags) {
if (flag.definition) {
flag.definition.disablePrompt = true;
}
}
}
/**
* Set flag from the flag option
* @param y instance of yargs
* @param commandFlags a set of command flags
*
*/
public static setRequiredCommandFlags(y: AnyYargs, ...commandFlags: CommandFlag[]): void {
for (const flag of commandFlags) {
y.option(flag.name, {...flag.definition, demandOption: true});
}
}
/**
* Set flag from the flag option
* @param y instance of yargs
* @param commandFlags a set of command flags
*
*/
public static setOptionalCommandFlags(y: AnyYargs, ...commandFlags: CommandFlag[]): void {
for (const flag of commandFlags) {
const defaultValue: string | number | boolean =
flag.definition.defaultValue === '' ? undefined : flag.definition.defaultValue;
y.option(flag.name, {
...flag.definition,
default: defaultValue,
});
}
}
public static readonly devMode: CommandFlag = {
constName: 'devMode',
name: 'dev',
definition: {
describe: 'Enable developer mode',
defaultValue: constants.SOLO_DEV_OUTPUT,
type: 'boolean',
},
prompt: undefined,
};
public static readonly predefinedAccounts: CommandFlag = {
constName: 'predefinedAccounts',
name: 'predefined-accounts',
definition: {
describe: 'Create predefined accounts on network creation',
defaultValue: true,
type: 'boolean',
},
prompt: undefined,
};
public static readonly forcePortForward: CommandFlag = {
constName: 'forcePortForward',
name: 'force-port-forward',
definition: {
describe: 'Force port forward to access the network services',
defaultValue: true, // always use local port-forwarding by default
type: 'boolean',
},
prompt: undefined,
};
public static readonly externalAddress: CommandFlag = {
constName: 'externalAddress',
name: 'external-address',
definition: {
describe: 'Bind address for kubectl port-forward (for example 127.0.0.1 or 0.0.0.0)',
type: 'string',
},
prompt: undefined,
};
// list of common flags across commands. command specific flags are defined in the command's module.
public static readonly clusterRef: CommandFlag = {
constName: 'clusterRef',
name: 'cluster-ref',
definition: {
describe:
'The cluster reference that will be used for referencing the Kubernetes cluster and stored in the local and ' +
'remote configuration for the deployment. For commands that take multiple clusters they can be separated by commas.',
alias: 'c',
type: 'string',
},
prompt: async function promptClusterReference(
task: SoloListrTaskWrapper<AnyListrContext>,
input: string,
): Promise<string> {
return await Flags.promptText(
task,
input,
Flags.clusterRef.definition.defaultValue as string,
'Enter cluster reference: ',
'cluster reference cannot be empty',
Flags.clusterRef.name,
);
},
};
public static readonly clusterSetupNamespace: CommandFlag = {
constName: 'clusterSetupNamespace',
name: 'cluster-setup-namespace',
definition: {
describe: 'Cluster Setup Namespace',
defaultValue: constants.SOLO_SETUP_NAMESPACE.name,
alias: 's',
type: 'string',
},
prompt: async function promptClusterSetupNamespace(
task: SoloListrTaskWrapper<AnyListrContext>,
input: string,
): Promise<string> {
return await Flags.promptText(
task,
input,
'solo-cluster',
'Enter cluster setup namespace name: ',
'cluster setup namespace cannot be empty',
Flags.clusterSetupNamespace.name,
);
},
};
public static readonly namespace: CommandFlag = {
constName: 'namespace',
name: 'namespace',
definition: {
describe: 'Namespace',
alias: 'n',
type: 'string',
},
prompt: async function promptNamespace(
task: SoloListrTaskWrapper<AnyListrContext>,
input: string,
): Promise<string> {
return await Flags.promptText(
task,
input,
'solo',
'Enter namespace name: ',
'namespace cannot be empty',
Flags.namespace.name,
);
},
};
public static readonly mirrorNamespace: CommandFlag = {
constName: 'mirrorNamespace',
name: 'mirror-namespace',
definition: {
describe: 'Namespace to use for the Mirror Node deployment, a new one will be created if it does not exist',
type: 'string',
},
prompt: undefined,
};
/**
* Parse the values files input string that includes the cluster reference and the values file path
* <p>It supports input as below:
* <p>--values-file aws-cluster=aws/solo-values.yaml,aws-cluster=aws/solo-values2.yaml,gcp-cluster=gcp/solo-values.yaml,gcp-cluster=gcp/solo-values2.yaml
* @param input
*/
public static parseValuesFilesInput(input: string): Record<ClusterReferenceName, Array<string>> {
const valuesFiles: Record<ClusterReferenceName, Array<string>> = {};
if (input) {
const inputItems: string[] = input.split(',');
for (const v of inputItems) {
const parts: string[] = v.split('=');
let clusterReference: string;
let valuesFile: string;
if (parts.length === 2) {
clusterReference = parts[0];
valuesFile = PathEx.resolve(parts[1]);
} else {
valuesFile = PathEx.resolve(v);
clusterReference = Flags.KEY_COMMON;
}
if (!valuesFiles[clusterReference]) {
valuesFiles[clusterReference] = [];
}
valuesFiles[clusterReference].push(valuesFile);
}
}
return valuesFiles;
}
public static readonly valuesFile: CommandFlag = {
constName: 'valuesFile',
name: 'values-file',
definition: {
describe: 'Comma separated chart values file',
defaultValue: '',
alias: 'f',
type: 'string',
},
prompt: async function promptValuesFile(_: SoloListrTaskWrapper<AnyListrContext>, input: string): Promise<string> {
return input; // no prompt is needed for values file
},
};
public static readonly networkDeploymentValuesFile: CommandFlag = {
constName: 'valuesFile',
name: 'values-file',
definition: {
describe:
'Comma separated chart values file paths for each cluster (e.g. values.yaml,cluster-1=./a/b/values1.yaml,cluster-2=./a/b/values2.yaml)',
defaultValue: '',
alias: 'f',
type: 'string',
},
prompt: async function promptValuesFile(_: SoloListrTaskWrapper<AnyListrContext>, input: string): Promise<string> {
if (input) {
Flags.parseValuesFilesInput(input); // validate input as early as possible by parsing it
}
return input; // no prompt is needed for values file
},
};
public static readonly deployPrometheusStack: CommandFlag = {
constName: 'deployPrometheusStack',
name: 'prometheus-stack',
definition: {
describe: 'Deploy prometheus stack',
defaultValue: false,
type: 'boolean',
},
prompt: async function promptDeployPrometheusStack(
task: SoloListrTaskWrapper<AnyListrContext>,
input: boolean,
): Promise<boolean> {
return await Flags.promptToggle(
task,
input,
Flags.deployPrometheusStack.definition.defaultValue as boolean,
'Would you like to deploy prometheus stack? ',
undefined,
Flags.deployPrometheusStack.name,
);
},
};
public static readonly deployMinio: CommandFlag = {
constName: 'deployMinio',
name: 'minio',
definition: {
describe: 'Deploy minio operator',
defaultValue: true,
type: 'boolean',
},
prompt: async function promptDeployMinio(
task: SoloListrTaskWrapper<AnyListrContext>,
input: boolean,
): Promise<boolean> {
return await Flags.promptToggle(
task,
input,
Flags.deployMinio.definition.defaultValue as boolean,
'Would you like to deploy MinIO? ',
undefined,
Flags.deployMinio.name,
);
},
};
public static readonly deployMetricsServer: CommandFlag = {
constName: 'deployMetricsServer',
name: 'metrics-server',
definition: {
describe: 'Deploy metrics server to enable kubectl top for CPU and memory usage monitoring',
defaultValue: false,
type: 'boolean',
},
prompt: undefined,
};
public static readonly deployCertManager: CommandFlag = {
constName: 'deployCertManager',
name: 'cert-manager',
definition: {
describe: 'Deploy cert manager, also deploys acme-cluster-issuer',
defaultValue: false,
type: 'boolean',
},
prompt: async function promptDeployCertManager(
task: SoloListrTaskWrapper<AnyListrContext>,
input: boolean,
): Promise<boolean> {
return await Flags.promptToggle(
task,
input,
Flags.deployCertManager.definition.defaultValue as boolean,
'Would you like to deploy Cert Manager? ',
undefined,
Flags.deployCertManager.name,
);
},
};
/*
Deploy cert manager CRDs separately from cert manager itself. Cert manager
CRDs are required for cert manager to deploy successfully.
*/
public static readonly deployCertManagerCrds: CommandFlag = {
constName: 'deployCertManagerCrds',
name: 'cert-manager-crds',
definition: {
describe: 'Deploy cert manager CRDs',
defaultValue: false,
type: 'boolean',
},
prompt: async function promptDeployCertManagerCrds(
task: SoloListrTaskWrapper<AnyListrContext>,
input: boolean,
): Promise<boolean> {
return await Flags.promptToggle(
task,
input,
Flags.deployCertManagerCrds.definition.defaultValue as boolean,
'Would you like to deploy Cert Manager CRDs? ',
undefined,
Flags.deployCertManagerCrds.name,
);
},
};
public static readonly deployJsonRpcRelay: CommandFlag = {
constName: 'deployJsonRpcRelay',
name: 'json-rpc-relay',
definition: {
describe: 'Deploy JSON RPC Relay',
defaultValue: false,
alias: 'j',
type: 'boolean',
},
prompt: undefined,
};
public static readonly stateFile: CommandFlag = {
constName: 'stateFile',
name: 'state-file',
definition: {
describe: 'A zipped state file to be used for the network',
defaultValue: '',
type: 'string',
},
prompt: undefined,
};
public static readonly upgradeZipFile: CommandFlag = {
constName: 'upgradeZipFile',
name: 'upgrade-zip-file',
definition: {
describe: 'A zipped file used for network upgrade',
defaultValue: '',
type: 'string',
},
prompt: undefined,
};
public static readonly releaseTag: CommandFlag = {
constName: 'releaseTag',
name: 'release-tag',
definition: {
describe: `Release tag to be used (e.g. ${version.HEDERA_PLATFORM_VERSION})`,
alias: 't',
defaultValue: version.HEDERA_PLATFORM_VERSION,
type: 'string',
},
prompt: async function promptReleaseTag(
task: SoloListrTaskWrapper<AnyListrContext>,
input: string,
): Promise<string> {
return await Flags.promptText(
task,
input,
version.HEDERA_PLATFORM_VERSION,
'Enter release version: ',
undefined,
Flags.releaseTag.name,
);
},
};
public static readonly upgradeVersion: CommandFlag = {
constName: 'upgradeVersion',
name: 'upgrade-version',
definition: {
describe: 'Version to be used for the upgrade',
defaultValue: '',
type: 'string',
},
prompt: undefined,
};
public static readonly imageTag: CommandFlag = {
constName: 'imageTag',
name: 'image-tag',
definition: {
describe: 'The Docker image tag to override what is in the Helm Chart',
defaultValue: '',
type: 'string',
},
prompt: undefined,
};
public static readonly componentImage: CommandFlag = {
constName: 'componentImage',
name: 'component-image',
definition: {
describe: 'Full Docker image reference override (e.g. ghcr.io/org/image:tag, docker.io/library/redis:7, redis:7)',
defaultValue: '',
type: 'string',
alias: 'relay-image',
},
prompt: undefined,
};
public static readonly relayReleaseTag: CommandFlag = {
constName: 'relayReleaseTag',
name: 'relay-release',
definition: {
describe: 'Relay release tag to be used (e.g. v0.48.0)',
defaultValue: version.HEDERA_JSON_RPC_RELAY_VERSION,
type: 'string',
},
prompt: async function promptRelayReleaseTag(
task: SoloListrTaskWrapper<AnyListrContext>,
input: string,
): Promise<string> {
return await Flags.promptText(
task,
input,
Flags.relayReleaseTag.definition.defaultValue as string,
'Enter relay release version: ',
'relay-release-tag cannot be empty',
Flags.relayReleaseTag.name,
);
},
};
public static readonly cacheDir: CommandFlag = {
constName: 'cacheDir',
name: 'cache-dir',
definition: {
describe: 'Local cache directory',
defaultValue: constants.SOLO_CACHE_DIR,
type: 'string',
},
prompt: async function promptCacheDirectory(
task: SoloListrTaskWrapper<AnyListrContext>,
input: string,
): Promise<string> {
return await Flags.promptText(
task,
input,
constants.SOLO_CACHE_DIR,
'Enter local cache directory path: ',
undefined,
Flags.cacheDir.name,
);
},
};
public static readonly nodeAliasesUnparsed: CommandFlag = {
constName: 'nodeAliasesUnparsed',
name: 'node-aliases',
definition: {
describe: 'Comma separated node aliases (empty means all nodes)',
alias: 'i',
type: 'string',
},
prompt: async function promptNodeAliases(
task: SoloListrTaskWrapper<AnyListrContext>,
input: string,
): Promise<string> {
return await Flags.prompt(
'input',
task,
input,
'node1,node2,node3',
'Enter list of node IDs (comma separated list): ',
undefined,
Flags.nodeAliasesUnparsed.name,
);
},
};
public static readonly force: CommandFlag = {
constName: 'force',
name: 'force',
definition: {
describe: 'Force actions even if those can be skipped',
defaultValue: false,
type: 'boolean',
},
prompt: async function promptForce(task: SoloListrTaskWrapper<AnyListrContext>, input: boolean): Promise<boolean> {
return await Flags.promptToggle(
task,
input,
Flags.force.definition.defaultValue as boolean,
'Would you like to force changes? ',
undefined,
Flags.force.name,
);
},
};
public static readonly forceBlockNodeIntegration: CommandFlag = {
constName: 'forceBlockNodeIntegration',
name: 'force',
definition: {
describe:
'Force enable block node integration bypassing the version requirements CN >= v0.72.0, BN >= 0.29.0, CN >= 0.150.0',
defaultValue: false,
type: 'boolean',
},
prompt: undefined,
};
public static readonly javaFlightRecorderConfiguration: CommandFlag = {
constName: 'javaFlightRecorderConfiguration',
name: 'jfr-config',
definition: {
describe: 'Java Flight Recorder configuration file path',
defaultValue: '',
type: 'string',
},
prompt: undefined,
};
public static readonly chartDirectory: CommandFlag = {
constName: 'chartDirectory',
name: 'chart-dir',
definition: {
describe: 'Local chart directory path (e.g. ~/solo-charts/charts)',
defaultValue: '',
type: 'string',
},
prompt: async function promptChartDirectory(
task: SoloListrTaskWrapper<AnyListrContext>,
input: string,
): Promise<string> {
if (input === 'false') {
return '';
}
try {
if (input && !fs.existsSync(input)) {
input = await task.prompt(ListrInquirerPromptAdapter).run(inputPrompt, {
default: Flags.chartDirectory.definition.defaultValue as string,
message: 'Enter local charts directory path: ',
});
if (!fs.existsSync(input)) {
throw new IllegalArgumentError('Invalid chart directory', input);
}
}
return input;
} catch (error) {
throw new SoloError(`input failed: ${Flags.chartDirectory.name}`, error);
}
},
};
public static readonly relayChartDirectory: CommandFlag = {
constName: 'relayChartDirectory',
name: 'relay-chart-dir',
definition: {
describe: 'Relay local chart directory path (e.g. ~/hiero-json-rpc-relay/charts)',
defaultValue: '',
type: 'string',
},
prompt: undefined,
};
public static readonly explorerChartDirectory: CommandFlag = {
constName: 'explorerChartDirectory',
name: 'explorer-chart-dir',
definition: {
describe: 'Explorer local chart directory path (e.g. ~/hiero-mirror-node-explorer/charts)',
defaultValue: '',
type: 'string',
},
prompt: undefined,
};
public static readonly blockNodeChartDirectory: CommandFlag = {
constName: 'blockNodeChartDirectory',
name: 'block-node-chart-dir',
definition: {
describe: 'Block node local chart directory path (e.g. ~/hiero-block-node/charts)',
defaultValue: '',
type: 'string',
},
prompt: undefined,
};
public static readonly blockNodeTssOverlay: CommandFlag = {
constName: 'blockNodeTssOverlay',
name: 'block-node-tss-overlay',
definition: {
describe:
'Force-apply block-node TSS values overlay when deploying block nodes before consensus deployment sets tssEnabled in remote config.',
defaultValue: false,
type: 'boolean',
},
prompt: undefined,
};
public static readonly blockNodeMapping: CommandFlag = {
constName: 'blockNodeIds',
name: 'block-node-mapping',
definition: {
describe: Flags.renderBlockNodeMappingDescription('block-node'),
type: 'string',
},
prompt: undefined,
};
public static readonly externalBlockNodeMapping: CommandFlag = {
constName: 'externalBlockNodeIds',
name: 'external-block-node-mapping',
definition: {
describe: Flags.renderBlockNodeMappingDescription('external-block-node'),
type: 'string',
},
prompt: undefined,
};
public static renderBlockNodeMappingDescription(name: 'block-node' | 'external-block-node'): string {
return (
`Configure ${name} priority mapping.` +
` Default: all ${name} included, first's priority is 2.` +
` Unlisted ${name} will not routed to the consensus node node.` +
` Example: --${name}-mapping 1=2,2=1`
);
}
public static readonly mirrorNodeChartDirectory: CommandFlag = {
constName: 'mirrorNodeChartDirectory',
name: 'mirror-node-chart-dir',
definition: {
describe:
'Mirror node local chart directory path (e.g. ~/hiero-mirror-node/charts). ' +
'NOTE: This only provides the Helm chart templates — it does NOT make the chart images available to the cluster. ' +
'All container images referenced by the chart must already be pullable (e.g. published to a registry or loaded ' +
'into the cluster with `kind load docker-image`). Using a local branch chart with SNAPSHOT image tags will ' +
'cause pods to fail with ImagePullBackOff unless those images have been built and pushed to a registry or ' +
'loaded into the cluster.',
defaultValue: '',
type: 'string',
},
prompt: undefined,
};
public static readonly replicaCount: CommandFlag = {
constName: 'replicaCount',
name: 'replica-count',
definition: {
describe: 'Replica count',
defaultValue: 1,
alias: '',
type: 'number',
},
prompt: async function promptReplicaCount(
task: SoloListrTaskWrapper<AnyListrContext>,
input: number,
): Promise<number> {
return await Flags.prompt(
'number',
task,
input,
Flags.replicaCount.definition.defaultValue,
'How many replica do you want? ',
undefined,
Flags.replicaCount.name,
);
},
};
public static readonly id: CommandFlag = {
constName: 'id',
name: 'id',
definition: {
describe: 'The numeric identifier for the component',
type: 'number',
},
prompt: async function (task: SoloListrTaskWrapper<AnyListrContext>, input: string): Promise<number> {
return await Flags.prompt('number', task, input, undefined, 'Enter component id: ', undefined, Flags.id.name);
},
};
public static readonly grpcWebEndpoints: CommandFlag = {
constName: 'grpcWebEndpoints',
name: 'grpc-web-endpoints',
definition: {
describe:
'Configure gRPC Web endpoints mapping, comma separated' +
`\n(Default port: ${constants.GRPC_WEB_PORT ?? 8080})` +
'\n(Aliases can be provided explicitly, or inferred by node id order)' +
'\n[Format: <alias>=<address>[:<port>][,<alias>=<address>[:<port>]]]' +
'\nExamples:' +
'\n\tnode1=127.0.0.1:8080,node2=127.0.0.1:8081' +
'\n\tnode1=localhost,node2=localhost:8081' +
'\n\tlocalhost,127.0.0.2:8081',
type: 'string',
},
prompt: undefined,
};
public static readonly grpcWebEndpoint: CommandFlag = {
constName: 'grpcWebEndpoint',
name: 'grpc-web-endpoint',
definition: {
describe:
'Configure gRPC Web endpoint' +
`\n(Default port: ${constants.GRPC_WEB_PORT ?? 8080})` +
'\n[Format: <address>[:<port>]]',
type: 'string',
},
prompt: undefined,
};
public static readonly mirrorNodeId: CommandFlag = {
constName: 'mirrorNodeId',
name: 'mirror-node-id',
definition: {
describe: 'The id of the mirror node which to connect',
type: 'number',
},
prompt: async function (task: SoloListrTaskWrapper<AnyListrContext>, input: string): Promise<number> {
return await Flags.prompt(
'number',
task,
input,
undefined,
'Enter mirror node id: ',
undefined,
Flags.mirrorNodeId.name,
);
},
};
public static readonly chainId: CommandFlag = {
constName: 'chainId',
name: 'chain-id',
definition: {
describe: 'Chain ID',
defaultValue: constants.HEDERA_CHAIN_ID, // Ref: https://github.com/hiero-ledger/hiero-json-rpc-relay#configuration
alias: 'l',
type: 'string',
},
prompt: async function promptChainId(task: SoloListrTaskWrapper<AnyListrContext>, input: string): Promise<string> {
return await Flags.promptText(
task,
input,
Flags.chainId.definition.defaultValue as string,
'Enter chain ID: ',
undefined,
Flags.chainId.name,
);
},
};
// Ref: https://github.com/hiero-ledger/hiero-json-rpc-relay/blob/main/docs/configuration.md
public static readonly operatorId: CommandFlag = {
constName: 'operatorId',
name: 'operator-id',
definition: {
describe: 'Operator ID',
defaultValue: undefined,
type: 'string',
},
prompt: async function promptOperatorId(
task: SoloListrTaskWrapper<AnyListrContext>,
input: string,
): Promise<string> {
return await Flags.promptText(
task,
input,
Flags.operatorId.definition.defaultValue as string,
'Enter operator ID: ',
undefined,
Flags.operatorId.name,
);
},
};
// Ref: https://github.com/hiero-ledger/hiero-json-rpc-relay/blob/main/docs/configuration.md
public static readonly operatorKey: CommandFlag = {
constName: 'operatorKey',
name: 'operator-key',
definition: {
describe: 'Operator Key',
defaultValue: undefined,
type: 'string',
dataMask: constants.STANDARD_DATAMASK,
},
prompt: async function promptOperatorKey(
task: SoloListrTaskWrapper<AnyListrContext>,
input: string,
): Promise<string> {
return await Flags.promptText(
task,
input,
Flags.operatorKey.definition.defaultValue as string,
'Enter operator private key: ',
undefined,
Flags.operatorKey.name,
);
},
};
public static readonly privateKey: CommandFlag = {
constName: 'privateKey',
name: 'private-key',
definition: {
describe: 'Show private key information',
defaultValue: false,
type: 'boolean',
dataMask: constants.STANDARD_DATAMASK,
},
prompt: async function promptPrivateKey(
task: SoloListrTaskWrapper<AnyListrContext>,
input: string,
): Promise<string> {
return await Flags.promptText(
task,
input,
Flags.ed25519PrivateKey.definition.defaultValue as string,
'Enter the private key: ',
undefined,
Flags.ed25519PrivateKey.name,
);
},
};
public static readonly generateGossipKeys: CommandFlag = {
constName: 'generateGossipKeys',
name: 'gossip-keys',
definition: {
describe: 'Generate gossip keys for nodes',
defaultValue: false,
type: 'boolean',
},
prompt: async function promptGenerateGossipKeys(
task: SoloListrTaskWrapper<AnyListrContext>,
input: boolean,
): Promise<boolean> {
return await Flags.promptToggle(
task,
input,
Flags.generateGossipKeys.definition.defaultValue as boolean,
`Would you like to generate Gossip keys? ${typeof input} ${input} `,
undefined,
Flags.generateGossipKeys.name,
);
},
};
public static readonly generateTlsKeys: CommandFlag = {
constName: 'generateTlsKeys',
name: 'tls-keys',
definition: {
describe: 'Generate gRPC TLS keys for nodes',
defaultValue: false,
type: 'boolean',
},
prompt: async function promptGenerateTLSKeys(
task: SoloListrTaskWrapper<AnyListrContext>,
input: boolean,
): Promise<boolean> {
return await Flags.promptToggle(
task,
input,
Flags.generateTlsKeys.definition.defaultValue as boolean,
'Would you like to generate TLS keys? ',
undefined,
Flags.generateTlsKeys.name,
);
},
};
public static readonly enableTimeout: CommandFlag = {
constName: 'enableTimeout',
name: 'enable-timeout',
definition: {
describe: 'enable time out for running a command',
defaultValue: false,
type: 'boolean',
},
prompt: undefined,
};
public static readonly tlsClusterIssuerType: CommandFlag = {
constName: 'tlsClusterIssuerType',
name: 'tls-cluster-issuer-type',
definition: {
describe:
'The TLS cluster issuer type to use for hedera explorer, defaults to "self-signed", the available options are: "acme-staging", "acme-prod", or "self-signed"',
defaultValue: 'self-signed',
type: 'string',
},
prompt: async function promptTlsClusterIssuerType(
task: SoloListrTaskWrapper<AnyListrContext>,
input: string,
): Promise<string | void> {
if (input) {
return;
}
try {
input = (await task.prompt(ListrInquirerPromptAdapter).run(selectPrompt, {
default: Flags.tlsClusterIssuerType.definition.defaultValue as string,
message:
'Enter TLS cluster issuer type, available options are: "acme-staging", "acme-prod", or "self-signed":',
choices: ['acme-staging', 'acme-prod', 'self-signed'],
})) as string;
return input;
} catch (error) {
throw new SoloError(`input failed: ${Flags.tlsClusterIssuerType.name}`, error);
}
},
};
public static readonly enableExplorerTls: CommandFlag = {
constName: 'enableExplorerTls',
name: 'enable-explorer-tls',
definition: {
describe:
'Enable Explorer TLS, defaults to false, requires certManager and certManagerCrds, which can be deployed through solo-cluster-setup chart or standalone',
defaultValue: false,
type: 'boolean',
},
prompt: async function promptEnableExplorerTls(
task: SoloListrTaskWrapper<AnyListrContext>,
input: boolean,
): Promise<boolean> {
return await Flags.promptToggle(
task,
input,
Flags.enableExplorerTls.definition.defaultValue as boolean,
'Would you like to enable the Explorer TLS? ',
undefined,
Flags.enableExplorerTls.name,
);
},
};
public static readonly ingressControllerValueFile: CommandFlag = {
constName: 'ingressControllerValueFile',
name: 'ingress-controller-value-file',
definition: {
describe: 'The value file to use for ingress controller, defaults to ""',
defaultValue: '',
type: 'string',
},
prompt: undefined,
};
public static readonly explorerStaticIp: CommandFlag = {
constName: 'explorerStaticIp',
name: 'explorer-static-ip',
definition: {
describe: 'The static IP address to use for the Explorer load balancer, defaults to ""',
defaultValue: '',
type: 'string',
},
prompt: undefined,
};
public static readonly explorerTlsHostName: CommandFlag = {
constName: 'explorerTlsHostName',
name: 'explorer-tls-host-name',
definition: {
describe: 'The host name to use for the Explorer TLS, defaults to "explorer.solo.local"',
defaultValue: 'explorer.solo.local',
type: 'string',
},
prompt: async function promptExplorerTlsHostName(
task: SoloListrTaskWrapper<AnyListrContext>,
input: string,
): Promise<string> {
return await Flags.promptText(
task,
input,
Flags.explorerTlsHostName.definition.defaultValue as string,
'Enter the host name to use for the Explorer TLS: ',
undefined,
Flags.explorerTlsHostName.name,
);
},
};
public static readonly enableMonitoringSupport: CommandFlag = {
constName: 'enableMonitoringSupport',
name: 'enable-monitoring-support',
definition: {
describe: 'Enables CRDs for Prometheus and Grafana.',
defaultValue: true,
type: 'boolean',
},
prompt: undefined,
};
public static readonly deletePvcs: CommandFlag = {
constName: 'deletePvcs',
name: 'delete-pvcs',
definition: {
describe:
'Delete the persistent volume claims. If both --delete-pvcs and --delete-secrets are set to true, the namespace will be deleted.',
defaultValue: false,
type: 'boolean',
},
prompt: async function promptDeletePvcs(
task: SoloListrTaskWrapper<AnyListrContext>,
input: boolean,
): Promise<boolean> {
return await Flags.promptToggle(
task,
input,
Flags.deletePvcs.definition.defaultValue as boolean,
'Would you like to delete persistent volume claims upon uninstall? ',
undefined,
Flags.deletePvcs.name,
);
},
};
public static readonly deleteSecrets: CommandFlag = {
constName: 'deleteSecrets',
name: 'delete-secrets',
definition: {
describe:
'Delete the network secrets. If both --delete-pvcs and --delete-secrets are set to true, the namespace will be deleted.',
defaultValue: false,
type: 'boolean',
},
prompt: async function promptDeleteSecrets(
task: SoloListrTaskWrapper<AnyListrContext>,
input: boolean,
): Promise<boolean> {
return await Flags.promptToggle(
task,
input,
Flags.deleteSecrets.definition.defaultValue as boolean,
'Would you like to delete secrets upon uninstall? ',
undefined,
Flags.deleteSecrets.name,
);
},
};
public static readonly soloChartVersion: CommandFlag = {
constName: 'soloChartVersion',
name: 'solo-chart-version',
definition: {
describe: 'Solo testing chart version',
defaultValue: version.SOLO_CHART_VERSION,
type: 'string',
},
prompt: async function promptSoloChartVersion(
task: SoloListrTaskWrapper<AnyListrContext>,
input: string,
): Promise<string> {
return await Flags.promptText(
task,
input,
Flags.soloChartVersion.definition.defaultValue as string,
'Enter solo testing chart version: ',
undefined,
Flags.soloChartVersion.name,
);
},
};
public static readonly blockNodeChartVersion: CommandFlag = {
constName: 'chartVersion',
name: 'chart-version',
definition: {
describe: 'Block nodes chart version',
defaultValue: version.BLOCK_NODE_VERSION,
type: 'string',
},
prompt: undefined,
};
public static readonly priorityMapping: CommandFlag = {
constName: 'priorityMapping',
name: 'priority-mapping',
definition: {
describe:
'Configure block node priority mapping.' +
' Unlisted nodes will not be routed to a block node' +
' Default: all consensus nodes included, first node priority is 2.' +
' Example: "priority-mapping node1=2,node2=1"',
type: 'string',
},
prompt: undefined,
};
public static readonly externalBlockNodeAddress: CommandFlag = {
constName: 'externalBlockNodeAddress',
name: 'address',
definition: {
describe:
'Provide external block node address (IP or domain), with optional port' +
` (Default port: ${constants.BLOCK_NODE_PORT})` +
' Examples: "--address localhost:8080", "--address 192.0.0.1"',
type: 'string',
},
prompt: undefined,
};
public static readonly wrapsEnabled: CommandFlag = {
constName: 'wrapsEnabled',
name: 'wraps',
definition: {
describe: 'Enable recursive WRAPs aggregation for hinTS/TSS (CN >= v0.72).',
type: 'boolean',
defaultValue: false,
},
prompt: undefined,
};
public static readonly wrapsKeyPath: CommandFlag = {
constName: 'wrapsKeyPath',
name: 'wraps-key-path',
definition: {
describe: 'Path to a local directory containing pre-existing WRAPs proving key files (.bin)',
type: 'string',
},
prompt: undefined,
};
public static readonly tssEnabled: CommandFlag = {
constName: 'tssEnabled',
name: 'tss',
definition: {
describe: 'Enable hinTS/TSS (CN >= v0.72).',
type: 'boolean',
defaultValue: true,
},
prompt: undefined,
};
public static readonly applicationProperties: CommandFlag = {
constName: 'applicationProperties',
name: 'application-properties',
definition: {
describe: 'application.properties file for node',
defaultValue: PathEx.join('templates', constants.APPLICATION_PROPERTIES),
type: 'string',
},
prompt: undefined,
};
public static readonly applicationEnv: CommandFlag = {
constName: 'applicationEnv',
name: 'application-env',
definition: {
describe:
'the application.env file for the node provides environment variables to the solo-container' +
' to be used when the hedera platform is started',
defaultValue: PathEx.join('templates', 'application.env'),
type: 'string',
},
prompt: undefined,
};
public static readonly apiPermissionProperties: CommandFlag = {
constName: 'apiPermissionProperties',
name: 'api-permission-properties',
definition: {
describe: 'api-permission.properties file for node',
defaultValue: PathEx.join('templates', 'api-permission.properties'),
type: 'string',
},
prompt: undefined,
};
public static readonly bootstrapProperties: CommandFlag = {
constName: 'bootstrapProperties',
name: 'bootstrap-properties',
definition: {
describe: 'bootstrap.properties file for node',
defaultValue: PathEx.join('templates', 'bootstrap.properties'),
type: 'string',
},
prompt: undefined,
};
public static readonly genesisThrottlesFile: CommandFlag = {
constName: 'genesisThrottlesFile',
name: 'genesis-throttles-file',
definition: {
describe: 'throttles.json file used during network genesis',
defaultValue: '',
type: 'string',
},
prompt: undefined,
};
public static readonly settingTxt: CommandFlag = {
constName: 'settingTxt',
name: 'settings-txt',
definition: {
describe: 'settings.txt file for node',
defaultValue: PathEx.join('templates', 'settings.txt'),
type: 'string',
},
prompt: undefined,
};
public static readonly app: CommandFlag = {
constName: 'app',
name: 'app',
definition: {
describe: 'Testing app name',
defaultValue: constants.HEDERA_APP_NAME,
type: 'string',
},
prompt: undefined,
};
public static readonly appConfig: CommandFlag = {
constName: 'appConfig',
name: 'app-config',
definition: {
describe: 'json config file of testing app',
defaultValue: '',
type: 'string',
},
prompt: undefined,
};
public static readonly localBuildPath: CommandFlag = {
constName: 'localBuildPath',
name: 'local-build-path',
definition: {
describe: 'path of hedera local repo',
defaultValue: constants.getEnvironmentVariable('SOLO_LOCAL_BUILD_PATH') || '',
type: 'string',
},
prompt: undefined,
};
public static readonly newAccountNumber: CommandFlag = {
constName: 'newAccountNumber',
name: 'new-account-number',
definition: {
describe: 'new account number for node update transaction',
defaultValue: '',
type: 'string',
},
prompt: undefined,
};
public static readonly newAdminKey: CommandFlag = {
constName: 'newAdminKey',
name: 'new-admin-key',
definition: {
describe: 'new admin key for the Hedera account',
defaultValue: '',
type: 'string',
},
prompt: undefined,
};
public static readonly gossipPublicKey: CommandFlag = {
constName: 'gossipPublicKey',
name: 'gossip-public-key',
definition: {
describe: 'path and file name of the public key for signing gossip in PEM key format to be used',
defaultValue: '',
type: 'string',
},
prompt: undefined,
};
public static readonly gossipPrivateKey: CommandFlag = {
constName: 'gossipPrivateKey',
name: 'gossip-private-key',
definition: {
describe: 'path and file name of the private key for signing gossip in PEM key format to be used',
defaultValue: '',
type: 'string',
dataMask: constants.STANDARD_DATAMASK,
},
prompt: undefined,
};
public static readonly tlsPublicKey: CommandFlag = {
constName: 'tlsPublicKey',
name: 'tls-public-key',
definition: {
describe: 'path and file name of the public TLS key to be used',
defaultValue: '',
type: 'string',
},
prompt: undefined,
};
public static readonly tlsPrivateKey: CommandFlag = {
constName: 'tlsPrivateKey',
name: 'tls-private-key',
definition: {
describe: 'path and file name of the private TLS key to be used',
defaultValue: '',
type: 'string',
dataMask: constants.STANDARD_DATAMASK,
},
prompt: undefined,
};
public static readonly log4j2Xml: CommandFlag = {
constName: 'log4j2Xml',
name: 'log4j2-xml',
definition: {
describe: 'log4j2.xml file for node',
defaultValue: PathEx.join('templates', 'log4j2.xml'),
type: 'string',
},
prompt: undefined,
};
public static readonly updateAccountKeys: CommandFlag = {
constName: 'updateAccountKeys',
name: 'update-account-keys',
definition: {
describe:
'Updates the special account keys to new keys and stores their keys in a corresponding Kubernetes secret',
defaultValue: true,
type: 'boolean',
},
prompt: async function promptUpdateAccountKeys(
task: SoloListrTaskWrapper<AnyListrContext>,
input: boolean,
): Promise<boolean> {
return await Flags.promptToggle(
task,
input,
Flags.updateAccountKeys.definition.defaultValue as boolean,
'Would you like to updates the special account keys to new keys and stores their keys in a corresponding Kubernetes secret? ',
undefined,
Flags.updateAccountKeys.name,
);
},
};
public static readonly ed25519PrivateKey: CommandFlag = {
constName: 'ed25519PrivateKey',
name: 'ed25519-private-key',
definition: {
describe: 'Specify a hex-encoded ED25519 private key for the Hedera account',
defaultValue: '',
type: 'string',
dataMask: constants.STANDARD_DATAMASK,
},
prompt: async function promptPrivateKey(
task: SoloListrTaskWrapper<AnyListrContext>,
input: string,
): Promise<string> {
return await Flags.promptText(
task,
input,
Flags.ed25519PrivateKey.definition.defaultValue as string,
'Enter the private key: ',
undefined,
Flags.ed25519PrivateKey.name,
);
},
};
public static readonly generateEcdsaKey: CommandFlag = {
constName: 'generateEcdsaKey',
name: 'generate-ecdsa-key',
definition: {
describe: 'Generate ECDSA private key for the Hedera account',
defaultValue: false,
type: 'boolean',
},
prompt: undefined,
};
public static readonly ecdsaPrivateKey: CommandFlag = {
constName: 'ecdsaPrivateKey',
name: 'ecdsa-private-key',
definition: {
describe: 'Specify a hex-encoded ECDSA private key for the Hedera account',
defaultValue: '',
type: 'string',
dataMask: constants.STANDARD_DATAMASK,
},
prompt: async function promptPrivateKey(
task: SoloListrTaskWrapper<AnyListrContext>,
input: string,
): Promise<string> {
return await Flags.promptText(
task,
input,
Flags.ed25519PrivateKey.definition.defaultValue as string,
'Enter the private key: ',
undefined,
Flags.ed25519PrivateKey.name,
);
},
};
public static readonly setAlias: CommandFlag = {
constName: 'setAlias',
name: 'set-alias',
definition: {
describe: 'Sets the alias for the Hedera account when it is created, requires --ecdsa-private-key',
defaultValue: false,
type: 'boolean',
},
prompt: undefined,
};
public static readonly accountId: CommandFlag = {
constName: 'accountId',
name: 'account-id',
definition: {
describe: 'The Hedera account id, e.g.: 0.0.1001',
defaultValue: '',
type: 'string',
},
prompt: async function promptAccountId(
task: SoloListrTaskWrapper<AnyListrContext>,
input: string,
): Promise<string> {
return await Flags.promptText(
task,
input,
Flags.accountId.definition.defaultValue as string,
'Enter the account id: ',
undefined,
Flags.accountId.name,
);
},
};
public static readonly fileId: CommandFlag = {
constName: 'fileId',
name: 'file-id',
definition: {
describe: 'The network file id, e.g.: 0.0.150',
defaultValue: '',
type: 'string',
},
prompt: async function promptFileId(task: SoloListrTaskWrapper<AnyListrContext>, input: string): Promise<string> {
return await Flags.promptText(
task,
input,
Flags.fileId.definition.defaultValue as string,
'Enter the file id: ',
'File ID cannot be empty',
Flags.fileId.name,
);
},
};
public static readonly filePath: CommandFlag = {
constName: 'filePath',
name: 'file-path',
definition: {
describe: 'Local path to the file to upload',
defaultValue: '',
type: 'string',
},
prompt: async function promptFilePath(task: SoloListrTaskWrapper<AnyListrContext>, input: string): Promise<string> {
return await Flags.promptText(
task,
input,
Flags.filePath.definition.defaultValue as string,
'Enter the file path: ',
'File path cannot be empty',
Flags.filePath.name,
);
},
};
public static readonly amount: CommandFlag = {
constName: 'amount',
name: 'hbar-amount',
definition: {
describe: 'Amount of HBAR to add',
defaultValue: 100,
type: 'number',
},
prompt: async function promptAmount(task: SoloListrTaskWrapper<AnyListrContext>, input: number): Promise<number> {
return await Flags.prompt(
'number',
task,
input,
Flags.amount.definition.defaultValue,
'How much HBAR do you want to add? ',
undefined,
Flags.amount.name,
);
},
};
public static readonly createAmount: CommandFlag = {
constName: 'createAmount',
name: 'create-amount',
definition: {
describe: 'Amount of new account to create',
defaultValue: 1,
type: 'number',
},
prompt: async function promptCreateAmount(
task: SoloListrTaskWrapper<AnyListrContext>,
input: number,
): Promise<number> {
return await Flags.prompt(
'number',
task,
input,
Flags.createAmount.definition.defaultValue,
'How many account to create? ',
undefined,
Flags.createAmount.name,
);
},
};
public static readonly nodeAlias: CommandFlag = {
constName: 'nodeAlias',
name: 'node-alias',
definition: {
describe: 'Node alias (e.g. node99)',
type: 'string',
},
prompt: async function promptNewNodeAlias(
task: SoloListrTaskWrapper<AnyListrContext>,
input: string,
): Promise<string> {
return await Flags.promptText(
task,
input,