diff --git a/.github/workflows/flow-deploy-release-artifact.yaml b/.github/workflows/flow-deploy-release-artifact.yaml index a31ce0586..8bd5b29b2 100644 --- a/.github/workflows/flow-deploy-release-artifact.yaml +++ b/.github/workflows/flow-deploy-release-artifact.yaml @@ -150,7 +150,7 @@ jobs: npm run build - name: Setup JFrog CLI - uses: jfrog/setup-jfrog-cli@e7cc33a01bc1b1df406bac8a1c4a409a34bdca0a # v4.4.3 + uses: jfrog/setup-jfrog-cli@040913fd7ac2a2bcb29a7e963ea99e7c309d63e5 # v4.5.0 env: JF_URL: ${{ vars.JF_URL }} JF_ACCESS_TOKEN: ${{ secrets.JF_ACCESS_TOKEN }} diff --git a/package-lock.json b/package-lock.json index 468f0f8fa..ea86f2ebe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,7 @@ "class-transformer": "^0.5.1", "class-validator": "^0.14.1", "dot-object": "^2.1.5", - "dotenv": "^16.4.6", + "dotenv": "^16.4.7", "enquirer": "^2.4.1", "esm": "^3.2.25", "figlet": "^1.8.0", @@ -91,7 +91,7 @@ "mocha-each": "^2.0.1", "mocha-junit-reporter": "^2.2.1", "mocha-multi-reporters": "^1.5.1", - "prettier": "^3.4.1", + "prettier": "^3.4.2", "remark-cli": "^12.0.1", "remark-frontmatter": "^5.0.0", "remark-lint-list-item-indent": "^4.0.0", @@ -100,7 +100,7 @@ "remark-preset-lint-recommended": "^7.0.0", "sinon": "^19.0.2", "sinon-chai": "^4.0.0", - "typedoc": "^0.27.2", + "typedoc": "^0.27.3", "typescript": "^5.7.2", "typescript-eslint": "^8.17.0" }, @@ -4320,9 +4320,9 @@ } }, "node_modules/dotenv": { - "version": "16.4.6", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.6.tgz", - "integrity": "sha512-JhcR/+KIjkkjiU8yEpaB/USlzVi3i5whwOjpIRNGi9svKEXZSe+Qp6IWAjFjv+2GViAoDRCUv/QLNziQxsLqDg==", + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -8977,10 +8977,11 @@ } }, "node_modules/prettier": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.1.tgz", - "integrity": "sha512-G+YdqtITVZmOJje6QkXQWzl3fSfMxFwm1tjTyo9exhkmWSqC4Yhd1+lug++IlR2mvRVAxEDDWYkQdeSztajqgg==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz", + "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", "dev": true, + "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" }, @@ -11015,9 +11016,9 @@ "dev": true }, "node_modules/typedoc": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.27.2.tgz", - "integrity": "sha512-C2ima5TZJHU3ecnRIz50lKd1BsYck5LhYQIy7MRPmjuSEJreUEAt+uAVcZgY7wZsSORzEI7xW8miZIdxv/cbmw==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.27.3.tgz", + "integrity": "sha512-oWT7zDS5oIaxYL5yOikBX4cL99CpNAZn6mI24JZQxsYuIHbtguSSwJ7zThuzNNwSE0wqhlfTSd99HgqKu2aQXQ==", "dev": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index 49c220cf7..0fb2defe1 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ "class-transformer": "^0.5.1", "class-validator": "^0.14.1", "dot-object": "^2.1.5", - "dotenv": "^16.4.6", + "dotenv": "^16.4.7", "enquirer": "^2.4.1", "esm": "^3.2.25", "figlet": "^1.8.0", @@ -119,7 +119,7 @@ "mocha-each": "^2.0.1", "mocha-junit-reporter": "^2.2.1", "mocha-multi-reporters": "^1.5.1", - "prettier": "^3.4.1", + "prettier": "^3.4.2", "remark-cli": "^12.0.1", "remark-frontmatter": "^5.0.0", "remark-lint-list-item-indent": "^4.0.0", @@ -128,7 +128,7 @@ "remark-preset-lint-recommended": "^7.0.0", "sinon": "^19.0.2", "sinon-chai": "^4.0.0", - "typedoc": "^0.27.2", + "typedoc": "^0.27.3", "typescript": "^5.7.2", "typescript-eslint": "^8.17.0" }, diff --git a/src/commands/account.ts b/src/commands/account.ts index 19ddd2819..df788ae5c 100644 --- a/src/commands/account.ts +++ b/src/commands/account.ts @@ -19,7 +19,6 @@ import {BaseCommand} from './base.js'; import {SoloError, IllegalArgumentError} from '../core/errors.js'; import {flags} from './index.js'; import {Listr} from 'listr2'; -import * as prompts from './prompts.js'; import {constants, type AccountManager} from '../core/index.js'; import {type AccountId, AccountInfo, HbarUnit, PrivateKey} from '@hashgraph/sdk'; import {FREEZE_ADMIN_ACCOUNT} from '../core/constants.js'; @@ -159,7 +158,7 @@ export class AccountCommand extends BaseCommand { title: 'Initialize', task: async (ctx, task) => { self.configManager.update(argv); - await prompts.execute(task, self.configManager, [flags.namespace]); + await flags.executePrompt(task, self.configManager, [flags.namespace]); const config = { namespace: self.configManager.getFlag(flags.namespace) as string, @@ -310,7 +309,7 @@ export class AccountCommand extends BaseCommand { title: 'Initialize', task: async (ctx, task) => { self.configManager.update(argv); - await prompts.execute(task, self.configManager, [flags.namespace]); + await flags.executePrompt(task, self.configManager, [flags.namespace]); const config = { amount: self.configManager.getFlag(flags.amount) as number, @@ -386,7 +385,7 @@ export class AccountCommand extends BaseCommand { title: 'Initialize', task: async (ctx, task) => { self.configManager.update(argv); - await prompts.execute(task, self.configManager, [flags.accountId, flags.namespace]); + await flags.executePrompt(task, self.configManager, [flags.accountId, flags.namespace]); const config = { accountId: self.configManager.getFlag(flags.accountId) as string, @@ -469,7 +468,7 @@ export class AccountCommand extends BaseCommand { title: 'Initialize', task: async (ctx, task) => { self.configManager.update(argv); - await prompts.execute(task, self.configManager, [flags.accountId, flags.namespace]); + await flags.executePrompt(task, self.configManager, [flags.accountId, flags.namespace]); const config = { accountId: self.configManager.getFlag(flags.accountId) as string, diff --git a/src/commands/cluster.ts b/src/commands/cluster.ts index 1892d5dde..7ceba4a68 100644 --- a/src/commands/cluster.ts +++ b/src/commands/cluster.ts @@ -17,11 +17,10 @@ import {ListrEnquirerPromptAdapter} from '@listr2/prompt-adapter-enquirer'; import {Listr} from 'listr2'; import {SoloError} from '../core/errors.js'; -import * as flags from './flags.js'; +import {flags} from './index.js'; import {BaseCommand} from './base.js'; import chalk from 'chalk'; import {constants} from '../core/index.js'; -import * as prompts from './prompts.js'; import path from 'path'; import {ListrLease} from '../core/lease/listr_lease.js'; import {CommandBuilder} from '../types/aliases.js'; @@ -79,7 +78,7 @@ export class ClusterCommand extends BaseCommand { title: 'Initialize', task: async (ctx, task) => { self.configManager.update(argv); - await prompts.execute(task, self.configManager, [ + await flags.executePrompt(task, self.configManager, [ flags.chartDirectory, flags.clusterSetupNamespace, flags.deployCertManager, diff --git a/src/commands/context/flags.ts b/src/commands/context/flags.ts index 1f4ebda3a..4b53e6d2e 100644 --- a/src/commands/context/flags.ts +++ b/src/commands/context/flags.ts @@ -15,7 +15,7 @@ * */ -import * as flags from '../flags.js'; +import {flags} from '../index.js'; export const USE_FLAGS = { requiredFlags: [], diff --git a/src/commands/context/index.ts b/src/commands/context/index.ts index 501df807c..bf3964849 100644 --- a/src/commands/context/index.ts +++ b/src/commands/context/index.ts @@ -21,7 +21,6 @@ import type {Opts} from '../../types/index.js'; import {ContextCommandTasks} from './tasks.js'; import {ContextCommandHandlers} from './handlers.js'; import * as ContextFlags from './flags.js'; -import {getPromptMap} from '../prompts.js'; /** * Defines the core functionalities of 'node' command @@ -32,7 +31,7 @@ export class ContextCommand extends BaseCommand { constructor(opts: Opts) { super(opts); - this.handlers = new ContextCommandHandlers(this, new ContextCommandTasks(this, getPromptMap())); + this.handlers = new ContextCommandHandlers(this, new ContextCommandTasks(this)); } getCommandDefinition() { diff --git a/src/commands/context/tasks.ts b/src/commands/context/tasks.ts index d50a39f00..fe7ccd21a 100644 --- a/src/commands/context/tasks.ts +++ b/src/commands/context/tasks.ts @@ -15,18 +15,15 @@ * */ import {Task, Templates} from '../../core/index.js'; -import * as flags from '../flags.js'; +import {flags} from '../index.js'; import type {ListrTaskWrapper} from 'listr2'; import {type BaseCommand} from '../base.js'; -import {UserPrompt} from '../../types/aliases.js'; export class ContextCommandTasks { private readonly parent: BaseCommand; - private readonly promptMap: Map; - constructor(parent, promptMap) { + constructor(parent) { this.parent = parent; - this.promptMap = promptMap; } updateLocalConfig(argv) { @@ -52,19 +49,19 @@ export class ContextCommandTasks { } } else { if (!clusters.length) { - const prompt = this.promptMap.get(flags.clusterName.name); + const prompt = flags.clusterName.prompt; const unparsedClusterAliases = await prompt(task, clusters); clusters = Templates.parseClusterAliases(unparsedClusterAliases); } if (!contextName) { - const prompt = this.promptMap.get(flags.context.name); + const prompt = flags.context.prompt; contextName = await prompt( task, kubeContexts.map(c => c.name), ); } if (!currentDeploymentName) { - const prompt = this.promptMap.get(flags.namespace.name); + const prompt = flags.namespace.prompt; currentDeploymentName = await prompt(task, currentDeploymentName); } } diff --git a/src/commands/deployment.ts b/src/commands/deployment.ts index e0d5d8d9e..3e5d8dcf5 100644 --- a/src/commands/deployment.ts +++ b/src/commands/deployment.ts @@ -17,9 +17,8 @@ import {Listr, type ListrTaskWrapper} from 'listr2'; import {SoloError} from '../core/errors.js'; import {BaseCommand} from './base.js'; -import * as flags from './flags.js'; +import {flags} from './index.js'; import {constants, Templates} from '../core/index.js'; -import * as prompts from './prompts.js'; import chalk from 'chalk'; import {RemoteConfigTasks} from '../core/config/remote/remote_config_tasks.js'; import {ListrLease} from '../core/lease/listr_lease.js'; @@ -59,7 +58,7 @@ export class DeploymentCommand extends BaseCommand { self.configManager.update(argv); self.logger.debug('Loaded cached config', {config: self.configManager.config}); - await prompts.execute(task, self.configManager, DeploymentCommand.DEPLOY_FLAGS_LIST); + await flags.executePrompt(task, self.configManager, DeploymentCommand.DEPLOY_FLAGS_LIST); ctx.config = { contextClusterUnparsed: self.configManager.getFlag(flags.contextClusterUnparsed), diff --git a/src/commands/flags.ts b/src/commands/flags.ts index a2d9cf470..8ff216f6f 100644 --- a/src/commands/flags.ts +++ b/src/commands/flags.ts @@ -14,944 +14,1677 @@ * limitations under the License. * */ -import {constants} from '../core/index.js'; +import {ConfigManager, constants} from '../core/index.js'; import * as core from '../core/index.js'; import * as version from '../../version.js'; import path from 'path'; import type {CommandFlag} from '../types/index.js'; - -/** - * Set flag from the flag option - * @param y instance of yargs - * @param commandFlags a set of command flags - * - */ -export function setCommandFlags(y: any, ...commandFlags: CommandFlag[]) { - commandFlags.forEach(flag => { - y.option(flag.name, flag.definition); - }); -} - -export const devMode: CommandFlag = { - constName: 'devMode', - name: 'dev', - definition: { - describe: 'Enable developer mode', - defaultValue: false, - type: 'boolean', - }, -}; - -// list of common flags across commands. command specific flags are defined in the command's module. -export const clusterName: CommandFlag = { - constName: 'clusterName', - name: 'cluster-name', - definition: { - describe: 'Cluster name', - defaultValue: 'solo-cluster-setup', - alias: 'c', - type: 'string', - }, -}; - -export const clusterSetupNamespace: CommandFlag = { - constName: 'clusterSetupNamespace', - name: 'cluster-setup-namespace', - definition: { - describe: 'Cluster Setup Namespace', - defaultValue: constants.SOLO_SETUP_NAMESPACE, - alias: 's', - type: 'string', - }, -}; - -export const namespace: CommandFlag = { - constName: 'namespace', - name: 'namespace', - definition: { - describe: 'Namespace', - alias: 'n', - type: 'string', - }, -}; - -export const deployHederaExplorer: CommandFlag = { - constName: 'deployHederaExplorer', - name: 'hedera-explorer', - definition: { - describe: 'Deploy hedera explorer', - defaultValue: true, - alias: 'x', - type: 'boolean', - }, -}; - -export const valuesFile: CommandFlag = { - constName: 'valuesFile', - name: 'values-file', - definition: { - describe: 'Comma separated chart values files', - defaultValue: '', - alias: 'f', - type: 'string', - }, -}; - -export const profileFile: CommandFlag = { - constName: 'profileFile', - name: 'profile-file', - definition: { - describe: 'Resource profile definition (e.g. custom-spec.yaml)', - defaultValue: constants.DEFAULT_PROFILE_FILE, - type: 'string', - }, -}; - -export const profileName: CommandFlag = { - constName: 'profileName', - name: 'profile', - definition: { - describe: `Resource profile (${constants.ALL_PROFILES.join(' | ')})`, - defaultValue: constants.PROFILE_LOCAL, - type: 'string', - }, -}; - -export const deployPrometheusStack: CommandFlag = { - constName: 'deployPrometheusStack', - name: 'prometheus-stack', - definition: { - describe: 'Deploy prometheus stack', - defaultValue: false, - type: 'boolean', - }, -}; - -export const enablePrometheusSvcMonitor: CommandFlag = { - constName: 'enablePrometheusSvcMonitor', - name: 'prometheus-svc-monitor', - definition: { - describe: 'Enable prometheus service monitor for the network nodes', - defaultValue: false, - type: 'boolean', - }, -}; - -export const deployMinio: CommandFlag = { - constName: 'deployMinio', - name: 'minio', - definition: { - describe: 'Deploy minio operator', - defaultValue: true, - type: 'boolean', - }, -}; - -export const deployCertManager: CommandFlag = { - constName: 'deployCertManager', - name: 'cert-manager', - definition: { - describe: 'Deploy cert manager, also deploys acme-cluster-issuer', - defaultValue: false, - type: 'boolean', - }, -}; - -/* +import {ListrTaskWrapper} from 'listr2'; +import fs from 'fs'; +import {IllegalArgumentError, SoloError} from '../core/errors.js'; +import {ListrEnquirerPromptAdapter} from '@listr2/prompt-adapter-enquirer'; +import * as helpers from '../core/helpers.js'; +import validator from 'validator'; + +export class Flags { + private static async prompt( + type: string, + task: ListrTaskWrapper, + input: any, + defaultValue: any, + promptMessage: string, + emptyCheckMessage: string | null, + flagName: string, + ) { + try { + let needsPrompt = 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'); + } + + input = await task.prompt(ListrEnquirerPromptAdapter).run({ + type, + default: defaultValue, + message: promptMessage, + }); + } + + if (emptyCheckMessage && !input) { + throw new SoloError(emptyCheckMessage); + } + + return input; + } catch (e: Error | any) { + throw new SoloError(`input failed: ${flagName}: ${e.message}`, e); + } + } + + private static async promptText( + task: ListrTaskWrapper, + input: any, + defaultValue: any, + promptMessage: string, + emptyCheckMessage: string | null, + flagName: string, + ) { + return await Flags.prompt('text', task, input, defaultValue, promptMessage, emptyCheckMessage, flagName); + } + + private static async promptToggle( + task: ListrTaskWrapper, + input: any, + defaultValue: any, + promptMessage: string, + emptyCheckMessage: string | null, + flagName: string, + ) { + return await Flags.prompt('toggle', task, input, defaultValue, promptMessage, emptyCheckMessage, flagName); + } + + /** + * Run prompts for the given set of flags + * @param task task object from listr2 + * @param configManager config manager to store flag values + * @param flagList list of flag objects + */ + static async executePrompt( + task: ListrTaskWrapper, + configManager: ConfigManager, + flagList: CommandFlag[] = [], + ) { + if (!configManager || !(configManager instanceof ConfigManager)) { + throw new IllegalArgumentError('an instance of ConfigManager is required'); + } + for (const flag of flagList) { + if (flag.definition.disablePrompt || flag.prompt === undefined) { + continue; + } + + if (configManager.getFlag(Flags.quiet)) { + return; + } + const input = await flag.prompt(task, configManager.getFlag(flag)); + configManager.setFlag(flag, input); + } + } + + /** + * Disable prompts for the given set of flags + * @param flags list of flags to disable prompts for + */ + static disablePrompts(flags: CommandFlag[]) { + 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 + * + */ + static setCommandFlags(y: any, ...commandFlags: CommandFlag[]) { + commandFlags.forEach(flag => { + y.option(flag.name, flag.definition); + }); + } + + static readonly devMode: CommandFlag = { + constName: 'devMode', + name: 'dev', + definition: { + describe: 'Enable developer mode', + defaultValue: false, + type: 'boolean', + }, + prompt: undefined, + }; + + // list of common flags across commands. command specific flags are defined in the command's module. + static readonly clusterName: CommandFlag = { + constName: 'clusterName', + name: 'cluster-name', + definition: { + describe: 'Cluster name', + defaultValue: 'solo-cluster-setup', + alias: 'c', + type: 'string', + }, + prompt: async function promptClusterName(task: ListrTaskWrapper, input: any) { + return await Flags.promptText( + task, + input, + Flags.clusterName.definition.defaultValue, + 'Enter cluster name: ', + 'cluster name cannot be empty', + Flags.clusterName.name, + ); + }, + }; + + static readonly clusterSetupNamespace: CommandFlag = { + constName: 'clusterSetupNamespace', + name: 'cluster-setup-namespace', + definition: { + describe: 'Cluster Setup Namespace', + defaultValue: constants.SOLO_SETUP_NAMESPACE, + alias: 's', + type: 'string', + }, + prompt: async function promptClusterSetupNamespace(task: ListrTaskWrapper, input: any) { + return await Flags.promptText( + task, + input, + 'solo-cluster', + 'Enter cluster setup namespace name: ', + 'cluster setup namespace cannot be empty', + Flags.clusterSetupNamespace.name, + ); + }, + }; + + static readonly namespace: CommandFlag = { + constName: 'namespace', + name: 'namespace', + definition: { + describe: 'Namespace', + alias: 'n', + type: 'string', + }, + prompt: async function promptNamespace(task: ListrTaskWrapper, input: any) { + return await Flags.promptText( + task, + input, + 'solo', + 'Enter namespace name: ', + 'namespace cannot be empty', + Flags.namespace.name, + ); + }, + }; + + static readonly deployHederaExplorer: CommandFlag = { + constName: 'deployHederaExplorer', + name: 'hedera-explorer', + definition: { + describe: 'Deploy hedera explorer', + defaultValue: true, + alias: 'x', + type: 'boolean', + }, + prompt: async function promptDeployHederaExplorer(task: ListrTaskWrapper, input: any) { + return await Flags.promptToggle( + task, + input, + Flags.deployHederaExplorer.definition.defaultValue, + 'Would you like to deploy Hedera Explorer? ', + null, + Flags.deployHederaExplorer.name, + ); + }, + }; + + static readonly valuesFile: CommandFlag = { + constName: 'valuesFile', + name: 'values-file', + definition: { + describe: 'Comma separated chart values files', + defaultValue: '', + alias: 'f', + type: 'string', + }, + prompt: async function promptValuesFile(task: ListrTaskWrapper, input: any) { + try { + if (input && !fs.existsSync(input)) { + input = await task.prompt(ListrEnquirerPromptAdapter).run({ + type: 'text', + default: Flags.valuesFile.definition.defaultValue, + message: 'Enter path to values.yaml: ', + }); + + if (!fs.existsSync(input)) { + throw new IllegalArgumentError('Invalid values.yaml file', input); + } + } + + return input; + } catch (e: Error | any) { + throw new SoloError(`input failed: ${Flags.valuesFile.name}`, e); + } + }, + }; + + static readonly profileFile: CommandFlag = { + constName: 'profileFile', + name: 'profile-file', + definition: { + describe: 'Resource profile definition (e.g. custom-spec.yaml)', + defaultValue: constants.DEFAULT_PROFILE_FILE, + type: 'string', + }, + prompt: async function promptProfileFile(task: ListrTaskWrapper, input: any) { + if (input && !fs.existsSync(input)) { + input = await task.prompt(ListrEnquirerPromptAdapter).run({ + type: 'text', + default: Flags.valuesFile.definition.defaultValue, + message: 'Enter path to custom resource profile definition file: ', + }); + } + + if (input && !fs.existsSync(input)) { + throw new IllegalArgumentError(`Invalid profile definition file: ${input}}`, input); + } + + return input; + }, + }; + + static readonly profileName: CommandFlag = { + constName: 'profileName', + name: 'profile', + definition: { + describe: `Resource profile (${constants.ALL_PROFILES.join(' | ')})`, + defaultValue: constants.PROFILE_LOCAL, + type: 'string', + }, + prompt: async function promptProfile( + task: ListrTaskWrapper, + input: any, + choices = constants.ALL_PROFILES, + ) { + try { + const initial = choices.indexOf(input); + if (initial < 0) { + const input = await task.prompt(ListrEnquirerPromptAdapter).run({ + type: 'select', + message: 'Select profile for solo network deployment', + choices: helpers.cloneArray(choices), + }); + + if (!input) { + throw new SoloError('key-format cannot be empty'); + } + + return input; + } + + return input; + } catch (e: Error | any) { + throw new SoloError(`input failed: ${Flags.profileName.name}`, e); + } + }, + }; + + static readonly deployPrometheusStack: CommandFlag = { + constName: 'deployPrometheusStack', + name: 'prometheus-stack', + definition: { + describe: 'Deploy prometheus stack', + defaultValue: false, + type: 'boolean', + }, + prompt: async function promptDeployPrometheusStack(task: ListrTaskWrapper, input: any) { + return await Flags.promptToggle( + task, + input, + Flags.deployPrometheusStack.definition.defaultValue, + 'Would you like to deploy prometheus stack? ', + null, + Flags.deployPrometheusStack.name, + ); + }, + }; + + static readonly enablePrometheusSvcMonitor: CommandFlag = { + constName: 'enablePrometheusSvcMonitor', + name: 'prometheus-svc-monitor', + definition: { + describe: 'Enable prometheus service monitor for the network nodes', + defaultValue: false, + type: 'boolean', + }, + prompt: async function promptEnablePrometheusSvcMonitor(task: ListrTaskWrapper, input: any) { + return await Flags.promptToggle( + task, + input, + Flags.enablePrometheusSvcMonitor.definition.defaultValue, + 'Would you like to enable the Prometheus service monitor for the network nodes? ', + null, + Flags.enablePrometheusSvcMonitor.name, + ); + }, + }; + + static readonly deployMinio: CommandFlag = { + constName: 'deployMinio', + name: 'minio', + definition: { + describe: 'Deploy minio operator', + defaultValue: true, + type: 'boolean', + }, + prompt: async function promptDeployMinio(task: ListrTaskWrapper, input: any) { + return await Flags.promptToggle( + task, + input, + Flags.deployMinio.definition.defaultValue, + 'Would you like to deploy MinIO? ', + null, + Flags.deployMinio.name, + ); + }, + }; + + 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: ListrTaskWrapper, input: any) { + return await Flags.promptToggle( + task, + input, + Flags.deployCertManager.definition.defaultValue, + 'Would you like to deploy Cert Manager? ', + null, + Flags.deployCertManager.name, + ); + }, + }; + + /* Deploy cert manager CRDs separately from cert manager itself. Cert manager CRDs are required for cert manager to deploy successfully. */ -export const deployCertManagerCrds: CommandFlag = { - constName: 'deployCertManagerCrds', - name: 'cert-manager-crds', - definition: { - describe: 'Deploy cert manager CRDs', - defaultValue: false, - type: 'boolean', - }, -}; - -export const deployJsonRpcRelay: CommandFlag = { - constName: 'deployJsonRpcRelay', - name: 'json-rpc-relay', - definition: { - describe: 'Deploy JSON RPC Relay', - defaultValue: false, - alias: 'j', - type: 'boolean', - }, -}; - -export const stateFile: CommandFlag = { - constName: 'stateFile', - name: 'state-file', - definition: { - describe: 'A zipped state file to be used for the network', - defaultValue: '', - type: 'string', - }, -}; - -export const 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', - }, -}; - -export const 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', - }, -}; - -export const cacheDir: CommandFlag = { - constName: 'cacheDir', - name: 'cache-dir', - definition: { - describe: 'Local cache directory', - defaultValue: core.constants.SOLO_CACHE_DIR, - type: 'string', - }, -}; - -export const nodeAliasesUnparsed: CommandFlag = { - constName: 'nodeAliasesUnparsed', - name: 'node-aliases-unparsed', - definition: { - describe: 'Comma separated node aliases (empty means all nodes)', - alias: 'i', - type: 'string', - }, -}; - -export const force: CommandFlag = { - constName: 'force', - name: 'force', - definition: { - describe: 'Force actions even if those can be skipped', - defaultValue: false, - alias: 'f', - type: 'boolean', - }, -}; - -export const chartDirectory: CommandFlag = { - constName: 'chartDirectory', - name: 'chart-dir', - definition: { - describe: 'Local chart directory path (e.g. ~/solo-charts/charts', - defaultValue: '', - alias: 'd', - type: 'string', - }, -}; - -export const replicaCount: CommandFlag = { - constName: 'replicaCount', - name: 'replica-count', - definition: { - describe: 'Replica count', - defaultValue: 1, - alias: '', - type: 'number', - }, -}; - -export const chainId: CommandFlag = { - constName: 'chainId', - name: 'ledger-id', - definition: { - describe: 'Ledger ID (a.k.a. Chain ID)', - defaultValue: constants.HEDERA_CHAIN_ID, // Ref: https://github.com/hashgraph/hedera-json-rpc-relay#configuration - alias: 'l', - type: 'string', - }, -}; - -// Ref: https://github.com/hashgraph/hedera-json-rpc-relay/blob/main/docs/configuration.md -export const operatorId: CommandFlag = { - constName: 'operatorId', - name: 'operator-id', - definition: { - describe: 'Operator ID', - defaultValue: constants.OPERATOR_ID, - type: 'string', - }, -}; - -// Ref: https://github.com/hashgraph/hedera-json-rpc-relay/blob/main/docs/configuration.md -export const operatorKey: CommandFlag = { - constName: 'operatorKey', - name: 'operator-key', - definition: { - describe: 'Operator Key', - defaultValue: constants.OPERATOR_KEY, - type: 'string', - }, -}; - -export const privateKey: CommandFlag = { - constName: 'privateKey', - name: 'private-key', - definition: { - describe: 'Show private key information', - defaultValue: false, - type: 'boolean', - }, -}; - -export const generateGossipKeys: CommandFlag = { - constName: 'generateGossipKeys', - name: 'gossip-keys', - definition: { - describe: 'Generate gossip keys for nodes', - defaultValue: false, - type: 'boolean', - }, -}; - -export const generateTlsKeys: CommandFlag = { - constName: 'generateTlsKeys', - name: 'tls-keys', - definition: { - describe: 'Generate gRPC TLS keys for nodes', - defaultValue: false, - type: 'boolean', - }, -}; - -export const enableTimeout: CommandFlag = { - constName: 'enableTimeout', - name: 'enable-timeout', - definition: { - describe: 'enable time out for running a command', - defaultValue: false, - type: 'boolean', - }, -}; - -export const 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', - }, -}; - -export const enableHederaExplorerTls: CommandFlag = { - constName: 'enableHederaExplorerTls', - name: 'enable-hedera-explorer-tls', - definition: { - describe: - 'Enable the Hedera Explorer TLS, defaults to false, requires certManager and certManagerCrds, which can be deployed through solo-cluster-setup chart or standalone', - defaultValue: false, - type: 'boolean', - }, -}; - -export const hederaExplorerTlsLoadBalancerIp: CommandFlag = { - constName: 'hederaExplorerTlsLoadBalancerIp', - name: 'hedera-explorer-tls-load-balancer-ip', - definition: { - describe: 'The static IP address to use for the Hedera Explorer TLS load balancer, defaults to ""', - defaultValue: '', - type: 'string', - }, -}; - -export const hederaExplorerTlsHostName: CommandFlag = { - constName: 'hederaExplorerTlsHostName', - name: 'hedera-explorer-tls-host-name', - definition: { - describe: 'The host name to use for the Hedera Explorer TLS, defaults to "explorer.solo.local"', - defaultValue: 'explorer.solo.local', - type: 'string', - }, -}; - -export const deletePvcs: CommandFlag = { - constName: 'deletePvcs', - name: 'delete-pvcs', - definition: { - describe: 'Delete the persistent volume claims', - defaultValue: false, - type: 'boolean', - }, -}; - -export const deleteSecrets: CommandFlag = { - constName: 'deleteSecrets', - name: 'delete-secrets', - definition: { - describe: 'Delete the network secrets', - defaultValue: false, - type: 'boolean', - }, -}; - -export const soloChartVersion: CommandFlag = { - constName: 'soloChartVersion', - name: 'solo-chart-version', - definition: { - describe: 'Solo testing chart version', - defaultValue: version.SOLO_CHART_VERSION, - type: 'string', - }, -}; - -export const applicationProperties: CommandFlag = { - constName: 'applicationProperties', - name: 'application-properties', - definition: { - describe: 'application.properties file for node', - defaultValue: path.join(constants.SOLO_CACHE_DIR, 'templates', 'application.properties'), - type: 'string', - }, -}; - -export const applicationEnv: CommandFlag = { - constName: 'applicationEnv', - name: 'application-env', - definition: { - describe: 'application.env file for node', - defaultValue: '', - type: 'string', - }, -}; - -export const apiPermissionProperties: CommandFlag = { - constName: 'apiPermissionProperties', - name: 'api-permission-properties', - definition: { - describe: 'api-permission.properties file for node', - defaultValue: path.join(constants.SOLO_CACHE_DIR, 'templates', 'api-permission.properties'), - type: 'string', - }, -}; - -export const bootstrapProperties: CommandFlag = { - constName: 'bootstrapProperties', - name: 'bootstrap-properties', - definition: { - describe: 'bootstrap.properties file for node', - defaultValue: path.join(constants.SOLO_CACHE_DIR, 'templates', 'bootstrap.properties'), - type: 'string', - }, -}; - -export const settingTxt: CommandFlag = { - constName: 'settingTxt', - name: 'settings-txt', - definition: { - describe: 'settings.txt file for node', - defaultValue: path.join(constants.SOLO_CACHE_DIR, 'templates', 'settings.txt'), - type: 'string', - }, -}; - -export const app: CommandFlag = { - constName: 'app', - name: 'app', - definition: { - describe: 'Testing app name', - defaultValue: constants.HEDERA_APP_NAME, - type: 'string', - }, -}; - -export const appConfig: CommandFlag = { - constName: 'appConfig', - name: 'app-config', - definition: { - describe: 'json config file of testing app', - defaultValue: '', - type: 'string', - }, -}; - -export const localBuildPath: CommandFlag = { - constName: 'localBuildPath', - name: 'local-build-path', - definition: { - describe: 'path of hedera local repo', - defaultValue: '', - type: 'string', - }, -}; - -export const newAccountNumber: CommandFlag = { - constName: 'newAccountNumber', - name: 'new-account-number', - definition: { - describe: 'new account number for node update transaction', - defaultValue: '', - type: 'string', - }, -}; - -export const newAdminKey: CommandFlag = { - constName: 'newAdminKey', - name: 'new-admin-key', - definition: { - describe: 'new admin key for the Hedera account', - defaultValue: '', - type: 'string', - }, -}; - -export const 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', - }, -}; - -export const 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', - }, -}; - -export const 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', - }, -}; - -export const 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', - }, -}; - -export const log4j2Xml: CommandFlag = { - constName: 'log4j2Xml', - name: 'log4j2-xml', - definition: { - describe: 'log4j2.xml file for node', - defaultValue: path.join(constants.SOLO_CACHE_DIR, 'templates', 'log4j2.xml'), - type: 'string', - }, -}; - -export const 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', - }, -}; - -export const ed25519PrivateKey: CommandFlag = { - constName: 'ed25519PrivateKey', - name: 'ed25519-private-key', - definition: { - describe: 'ED25519 private key for the Hedera account', - defaultValue: '', - type: 'string', - }, -}; - -export const ecdsaPrivateKey: CommandFlag = { - constName: 'ecdsaPrivateKey', - name: 'ecdsa-private-key', - definition: { - describe: 'ECDSA private key for the Hedera account', - defaultValue: '', - type: 'string', - }, -}; - -export const 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', - }, -}; - -export const accountId: CommandFlag = { - constName: 'accountId', - name: 'account-id', - definition: { - describe: 'The Hedera account id, e.g.: 0.0.1001', - defaultValue: '', - type: 'string', - }, -}; - -export const amount: CommandFlag = { - constName: 'amount', - name: 'hbar-amount', - definition: { - describe: 'Amount of HBAR to add', - defaultValue: 100, - type: 'number', - }, -}; - -export const nodeAlias: CommandFlag = { - constName: 'nodeAlias', - name: 'node-alias', - definition: { - describe: 'Node alias (e.g. node99)', - type: 'string', - }, -}; - -export const gossipEndpoints: CommandFlag = { - constName: 'gossipEndpoints', - name: 'gossip-endpoints', - definition: { - describe: 'Comma separated gossip endpoints of the node(e.g. first one is internal, second one is external)', - type: 'string', - }, -}; - -export const grpcEndpoints: CommandFlag = { - constName: 'grpcEndpoints', - name: 'grpc-endpoints', - definition: { - describe: 'Comma separated gRPC endpoints of the node (at most 8)', - type: 'string', - }, -}; - -export const endpointType: CommandFlag = { - constName: 'endpointType', - name: 'endpoint-type', - definition: { - describe: 'Endpoint type (IP or FQDN)', - defaultValue: constants.ENDPOINT_TYPE_FQDN, - type: 'string', - }, -}; - -export const persistentVolumeClaims: CommandFlag = { - constName: 'persistentVolumeClaims', - name: 'pvcs', - definition: { - describe: 'Enable persistent volume claims to store data outside the pod, required for node add', - defaultValue: false, - type: 'boolean', - }, -}; - -export const debugNodeAlias: CommandFlag = { - constName: 'debugNodeAlias', - name: 'debug-node-alias', - definition: { - describe: 'Enable default jvm debug port (5005) for the given node id', - defaultValue: '', - type: 'string', - }, -}; - -export const outputDir: CommandFlag = { - constName: 'outputDir', - name: 'output-dir', - definition: { - describe: 'Path to the directory where the command context will be saved to', - defaultValue: '', - type: 'string', - }, -}; - -export const inputDir: CommandFlag = { - constName: 'inputDir', - name: 'input-dir', - definition: { - describe: 'Path to the directory where the command context will be loaded from', - defaultValue: '', - type: 'string', - }, -}; - -export const adminKey: CommandFlag = { - constName: 'adminKey', - name: 'admin-key', - definition: { - describe: 'Admin key', - defaultValue: constants.GENESIS_KEY, - type: 'string', - }, -}; - -export const quiet: CommandFlag = { - constName: 'quiet', - name: 'quiet-mode', - definition: { - describe: 'Quiet mode, do not prompt for confirmation', - defaultValue: false, - alias: 'q', - type: 'boolean', - disablePrompt: true, - }, -}; - -export const mirrorNodeVersion: CommandFlag = { - constName: 'mirrorNodeVersion', - name: 'mirror-node-version', - definition: { - describe: 'Mirror node chart version', - defaultValue: version.MIRROR_NODE_VERSION, - type: 'string', - }, -}; - -export const hederaExplorerVersion: CommandFlag = { - constName: 'hederaExplorerVersion', - name: 'hedera-explorer-version', - definition: { - describe: 'Hedera explorer chart version', - defaultValue: version.HEDERA_EXPLORER_VERSION, - type: 'string', - }, -}; - -export const userEmailAddress: CommandFlag = { - constName: 'userEmailAddress', - name: 'email', - definition: { - describe: 'User email address used for local configuration', - type: 'string', - }, -}; - -export const context: CommandFlag = { - constName: 'contextName', - name: 'context', - definition: { - describe: 'The Kubernetes context name to be used', - defaultValue: '', - type: 'string', - }, -}; - -export const deploymentClusters: CommandFlag = { - constName: 'deploymentClusters', - name: 'deployment-clusters', - definition: { - describe: 'Solo deployment cluster list (comma separated)', - type: 'string', - }, -}; - -export const pinger: CommandFlag = { - constName: 'pinger', - name: 'pinger', - definition: { - describe: 'Enable Pinger service in the Mirror node monitor', - defaultValue: false, - type: 'boolean', - }, -}; - -//* ------------- Node Proxy Certificates ------------- !// - -export const grpcTlsCertificatePath: CommandFlag = { - constName: 'grpcTlsCertificatePath', - name: 'grpc-tls-cert', - definition: { - describe: - 'TLS Certificate path for the gRPC ' + - '(e.g. "node1=/Users/username/node1-grpc.cert" ' + - 'with multiple nodes comma seperated)', - defaultValue: '', - type: 'string', - }, -}; - -export const grpcWebTlsCertificatePath: CommandFlag = { - constName: 'grpcWebTlsCertificatePath', - name: 'grpc-web-tls-cert', - definition: { - describe: - 'TLS Certificate path for gRPC Web ' + - '(e.g. "node1=/Users/username/node1-grpc-web.cert" ' + - 'with multiple nodes comma seperated)', - defaultValue: '', - type: 'string', - }, -}; - -export const grpcTlsKeyPath: CommandFlag = { - constName: 'grpcTlsKeyPath', - name: 'grpc-tls-key', - definition: { - describe: - 'TLS Certificate key path for the gRPC ' + - '(e.g. "node1=/Users/username/node1-grpc.key" ' + - 'with multiple nodes comma seperated)', - defaultValue: '', - type: 'string', - }, -}; - -export const grpcWebTlsKeyPath: CommandFlag = { - constName: 'grpcWebTlsKeyPath', - name: 'grpc-web-tls-key', - definition: { - describe: - 'TLC Certificate key path for gRPC Web ' + - '(e.g. "node1=/Users/username/node1-grpc-web.key" ' + - 'with multiple nodes comma seperated)', - defaultValue: '', - type: 'string', - }, -}; - -export const stakeAmounts: CommandFlag = { - constName: 'stakeAmounts', - name: 'stake-amounts', - definition: { - describe: - 'The amount to be staked in the same order you list the node aliases with multiple node staked values comma seperated', - defaultValue: '', - type: 'string', - }, -}; - -export const contextClusterUnparsed: CommandFlag = { - constName: 'contextClusterUnparsed', - name: 'context-cluster', - definition: { - describe: - 'Context cluster mapping where context is key = value is cluster and comma delimited if more than one, ' + - '(e.g.: --context-cluster kind-solo=kind-solo,kind-solo-2=kind-solo-2)', - type: 'string', - }, -}; - -export const allFlags: CommandFlag[] = [ - accountId, - amount, - apiPermissionProperties, - app, - appConfig, - applicationEnv, - applicationProperties, - bootstrapProperties, - cacheDir, - chainId, - chartDirectory, - clusterName, - clusterSetupNamespace, - context, - deletePvcs, - deleteSecrets, - deployCertManager, - deployCertManagerCrds, - deployHederaExplorer, - deployJsonRpcRelay, - deploymentClusters, - deployMinio, - deployPrometheusStack, - devMode, - ecdsaPrivateKey, - ed25519PrivateKey, - enableHederaExplorerTls, - enablePrometheusSvcMonitor, - enableTimeout, - endpointType, - soloChartVersion, - generateGossipKeys, - generateTlsKeys, - gossipEndpoints, - gossipPrivateKey, - gossipPublicKey, - grpcEndpoints, - hederaExplorerTlsHostName, - hederaExplorerTlsLoadBalancerIp, - inputDir, - debugNodeAlias, - localBuildPath, - log4j2Xml, - namespace, - newAccountNumber, - newAdminKey, - nodeAlias, - nodeAliasesUnparsed, - operatorId, - operatorKey, - outputDir, - persistentVolumeClaims, - privateKey, - profileFile, - profileName, - pinger, - relayReleaseTag, - releaseTag, - replicaCount, - stateFile, - setAlias, - settingTxt, - stakeAmounts, - tlsClusterIssuerType, - tlsPrivateKey, - tlsPublicKey, - updateAccountKeys, - userEmailAddress, - valuesFile, - mirrorNodeVersion, - hederaExplorerVersion, - grpcTlsCertificatePath, - grpcWebTlsCertificatePath, - grpcTlsKeyPath, - grpcWebTlsKeyPath, - contextClusterUnparsed, -]; - -/** Resets the definition.disablePrompt for all flags */ -export function resetDisabledPrompts() { - allFlags.forEach(f => { - if (f.definition.disablePrompt) { - delete f.definition.disablePrompt; - } - }); + 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: ListrTaskWrapper, input: any) { + return await Flags.promptToggle( + task, + input, + Flags.deployCertManagerCrds.definition.defaultValue, + 'Would you like to deploy Cert Manager CRDs? ', + null, + Flags.deployCertManagerCrds.name, + ); + }, + }; + + static readonly deployJsonRpcRelay: CommandFlag = { + constName: 'deployJsonRpcRelay', + name: 'json-rpc-relay', + definition: { + describe: 'Deploy JSON RPC Relay', + defaultValue: false, + alias: 'j', + type: 'boolean', + }, + prompt: undefined, + }; + + 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, + }; + + 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: ListrTaskWrapper, input: any) { + return await Flags.promptText( + task, + input, + 'v0.42.5', + 'Enter release version: ', + 'release tag cannot be empty', + Flags.releaseTag.name, + ); + }, + }; + + 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: ListrTaskWrapper, input: any) { + return await Flags.promptText( + task, + input, + Flags.relayReleaseTag.definition.defaultValue, + 'Enter relay release version: ', + 'relay-release-tag cannot be empty', + Flags.relayReleaseTag.name, + ); + }, + }; + + static readonly cacheDir: CommandFlag = { + constName: 'cacheDir', + name: 'cache-dir', + definition: { + describe: 'Local cache directory', + defaultValue: core.constants.SOLO_CACHE_DIR, + type: 'string', + }, + prompt: async function promptCacheDir(task: ListrTaskWrapper, input: any) { + return await Flags.promptText( + task, + input, + constants.SOLO_CACHE_DIR, + 'Enter local cache directory path: ', + null, + Flags.cacheDir.name, + ); + }, + }; + + static readonly nodeAliasesUnparsed: CommandFlag = { + constName: 'nodeAliasesUnparsed', + name: 'node-aliases-unparsed', + definition: { + describe: 'Comma separated node aliases (empty means all nodes)', + alias: 'i', + type: 'string', + }, + prompt: async function promptNodeAliases(task: ListrTaskWrapper, input: any) { + return await Flags.prompt( + 'input', + task, + input, + 'node1,node2,node3', + 'Enter list of node IDs (comma separated list): ', + null, + Flags.nodeAliasesUnparsed.name, + ); + }, + }; + + static readonly force: CommandFlag = { + constName: 'force', + name: 'force', + definition: { + describe: 'Force actions even if those can be skipped', + defaultValue: false, + alias: 'f', + type: 'boolean', + }, + prompt: async function promptForce(task: ListrTaskWrapper, input: any) { + return await Flags.promptToggle( + task, + input, + Flags.force.definition.defaultValue, + 'Would you like to force changes? ', + null, + Flags.force.name, + ); + }, + }; + + static readonly chartDirectory: CommandFlag = { + constName: 'chartDirectory', + name: 'chart-dir', + definition: { + describe: 'Local chart directory path (e.g. ~/solo-charts/charts', + defaultValue: '', + alias: 'd', + type: 'string', + }, + prompt: async function promptChartDir(task: ListrTaskWrapper, input: any) { + try { + if (input === 'false') { + return ''; + } + + if (input && !fs.existsSync(input)) { + input = await task.prompt(ListrEnquirerPromptAdapter).run({ + type: 'text', + default: Flags.chartDirectory.definition.defaultValue, + message: 'Enter local charts directory path: ', + }); + + if (!fs.existsSync(input)) { + throw new IllegalArgumentError('Invalid chart directory', input); + } + } + + return input; + } catch (e: Error | any) { + throw new SoloError(`input failed: ${Flags.chartDirectory.name}`, e); + } + }, + }; + + static readonly replicaCount: CommandFlag = { + constName: 'replicaCount', + name: 'replica-count', + definition: { + describe: 'Replica count', + defaultValue: 1, + alias: '', + type: 'number', + }, + prompt: async function promptReplicaCount(task: ListrTaskWrapper, input: any) { + return await Flags.prompt( + 'number', + task, + input, + Flags.replicaCount.definition.defaultValue, + 'How many replica do you want? ', + null, + Flags.replicaCount.name, + ); + }, + }; + + static readonly chainId: CommandFlag = { + constName: 'chainId', + name: 'ledger-id', + definition: { + describe: 'Ledger ID (a.k.a. Chain ID)', + defaultValue: constants.HEDERA_CHAIN_ID, // Ref: https://github.com/hashgraph/hedera-json-rpc-relay#configuration + alias: 'l', + type: 'string', + }, + prompt: async function promptChainId(task: ListrTaskWrapper, input: any) { + return await Flags.promptText( + task, + input, + Flags.chainId.definition.defaultValue, + 'Enter chain ID: ', + null, + Flags.chainId.name, + ); + }, + }; + + // Ref: https://github.com/hashgraph/hedera-json-rpc-relay/blob/main/docs/configuration.md + static readonly operatorId: CommandFlag = { + constName: 'operatorId', + name: 'operator-id', + definition: { + describe: 'Operator ID', + defaultValue: constants.OPERATOR_ID, + type: 'string', + }, + prompt: async function promptOperatorId(task: ListrTaskWrapper, input: any) { + return await Flags.promptText( + task, + input, + Flags.operatorId.definition.defaultValue, + 'Enter operator ID: ', + null, + Flags.operatorId.name, + ); + }, + }; + + // Ref: https://github.com/hashgraph/hedera-json-rpc-relay/blob/main/docs/configuration.md + static readonly operatorKey: CommandFlag = { + constName: 'operatorKey', + name: 'operator-key', + definition: { + describe: 'Operator Key', + defaultValue: constants.OPERATOR_KEY, + type: 'string', + }, + prompt: async function promptOperatorKey(task: ListrTaskWrapper, input: any) { + return await Flags.promptText( + task, + input, + Flags.operatorKey.definition.defaultValue, + 'Enter operator private key: ', + null, + Flags.operatorKey.name, + ); + }, + }; + + static readonly privateKey: CommandFlag = { + constName: 'privateKey', + name: 'private-key', + definition: { + describe: 'Show private key information', + defaultValue: false, + type: 'boolean', + }, + prompt: async function promptPrivateKey(task: ListrTaskWrapper, input: any) { + return await Flags.promptText( + task, + input, + Flags.ed25519PrivateKey.definition.defaultValue, + 'Enter the private key: ', + null, + Flags.ed25519PrivateKey.name, + ); + }, + }; + + 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: ListrTaskWrapper, input: any) { + return await Flags.promptToggle( + task, + input, + Flags.generateGossipKeys.definition.defaultValue, + `Would you like to generate Gossip keys? ${typeof input} ${input} `, + null, + Flags.generateGossipKeys.name, + ); + }, + }; + + 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: ListrTaskWrapper, input: any) { + return await Flags.promptToggle( + task, + input, + Flags.generateTlsKeys.definition.defaultValue, + 'Would you like to generate TLS keys? ', + null, + Flags.generateTlsKeys.name, + ); + }, + }; + + static readonly enableTimeout: CommandFlag = { + constName: 'enableTimeout', + name: 'enable-timeout', + definition: { + describe: 'enable time out for running a command', + defaultValue: false, + type: 'boolean', + }, + prompt: undefined, + }; + + 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: ListrTaskWrapper, input: any) { + try { + if (!input) { + input = await task.prompt(ListrEnquirerPromptAdapter).run({ + type: 'text', + default: Flags.tlsClusterIssuerType.definition.defaultValue, + message: + 'Enter TLS cluster issuer type, available options are: "acme-staging", "acme-prod", or "self-signed":', + }); + } + + if (!input || !['acme-staging', 'acme-prod', 'self-signed'].includes(input)) { + throw new SoloError('must be one of: "acme-staging", "acme-prod", or "self-signed"'); + } + + return input; + } catch (e: Error | any) { + throw new SoloError(`input failed: ${Flags.tlsClusterIssuerType.name}`, e); + } + }, + }; + + static readonly enableHederaExplorerTls: CommandFlag = { + constName: 'enableHederaExplorerTls', + name: 'enable-hedera-explorer-tls', + definition: { + describe: + 'Enable the Hedera 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 promptEnableHederaExplorerTls(task: ListrTaskWrapper, input: any) { + return await Flags.promptToggle( + task, + input, + Flags.enableHederaExplorerTls.definition.defaultValue, + 'Would you like to enable the Hedera Explorer TLS? ', + null, + Flags.enableHederaExplorerTls.name, + ); + }, + }; + + static readonly hederaExplorerTlsLoadBalancerIp: CommandFlag = { + constName: 'hederaExplorerTlsLoadBalancerIp', + name: 'hedera-explorer-tls-load-balancer-ip', + definition: { + describe: 'The static IP address to use for the Hedera Explorer TLS load balancer, defaults to ""', + defaultValue: '', + type: 'string', + }, + prompt: undefined, + }; + + static readonly hederaExplorerTlsHostName: CommandFlag = { + constName: 'hederaExplorerTlsHostName', + name: 'hedera-explorer-tls-host-name', + definition: { + describe: 'The host name to use for the Hedera Explorer TLS, defaults to "explorer.solo.local"', + defaultValue: 'explorer.solo.local', + type: 'string', + }, + prompt: async function promptHederaExplorerTlsHostName(task: ListrTaskWrapper, input: any) { + return await Flags.promptText( + task, + input, + Flags.hederaExplorerTlsHostName.definition.defaultValue, + 'Enter the host name to use for the Hedera Explorer TLS: ', + null, + Flags.hederaExplorerTlsHostName.name, + ); + }, + }; + + static readonly deletePvcs: CommandFlag = { + constName: 'deletePvcs', + name: 'delete-pvcs', + definition: { + describe: 'Delete the persistent volume claims', + defaultValue: false, + type: 'boolean', + }, + prompt: async function promptDeletePvcs(task: ListrTaskWrapper, input: any) { + return await Flags.promptToggle( + task, + input, + Flags.deletePvcs.definition.defaultValue, + 'Would you like to delete persistent volume claims upon uninstall? ', + null, + Flags.deletePvcs.name, + ); + }, + }; + + static readonly deleteSecrets: CommandFlag = { + constName: 'deleteSecrets', + name: 'delete-secrets', + definition: { + describe: 'Delete the network secrets', + defaultValue: false, + type: 'boolean', + }, + prompt: async function promptDeleteSecrets(task: ListrTaskWrapper, input: any) { + return await Flags.promptToggle( + task, + input, + Flags.deleteSecrets.definition.defaultValue, + 'Would you like to delete secrets upon uninstall? ', + null, + Flags.deleteSecrets.name, + ); + }, + }; + + 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: ListrTaskWrapper, input: any) { + return await Flags.promptText( + task, + input, + Flags.soloChartVersion.definition.defaultValue, + 'Enter solo testing chart version: ', + null, + Flags.soloChartVersion.name, + ); + }, + }; + + static readonly applicationProperties: CommandFlag = { + constName: 'applicationProperties', + name: 'application-properties', + definition: { + describe: 'application.properties file for node', + defaultValue: path.join(constants.SOLO_CACHE_DIR, 'templates', 'application.properties'), + type: 'string', + }, + prompt: undefined, + }; + + static readonly applicationEnv: CommandFlag = { + constName: 'applicationEnv', + name: 'application-env', + definition: { + describe: 'application.env file for node', + defaultValue: '', + type: 'string', + }, + prompt: undefined, + }; + + static readonly apiPermissionProperties: CommandFlag = { + constName: 'apiPermissionProperties', + name: 'api-permission-properties', + definition: { + describe: 'api-permission.properties file for node', + defaultValue: path.join(constants.SOLO_CACHE_DIR, 'templates', 'api-permission.properties'), + type: 'string', + }, + prompt: undefined, + }; + + static readonly bootstrapProperties: CommandFlag = { + constName: 'bootstrapProperties', + name: 'bootstrap-properties', + definition: { + describe: 'bootstrap.properties file for node', + defaultValue: path.join(constants.SOLO_CACHE_DIR, 'templates', 'bootstrap.properties'), + type: 'string', + }, + prompt: undefined, + }; + + static readonly settingTxt: CommandFlag = { + constName: 'settingTxt', + name: 'settings-txt', + definition: { + describe: 'settings.txt file for node', + defaultValue: path.join(constants.SOLO_CACHE_DIR, 'templates', 'settings.txt'), + type: 'string', + }, + prompt: undefined, + }; + + static readonly app: CommandFlag = { + constName: 'app', + name: 'app', + definition: { + describe: 'Testing app name', + defaultValue: constants.HEDERA_APP_NAME, + type: 'string', + }, + prompt: undefined, + }; + + static readonly appConfig: CommandFlag = { + constName: 'appConfig', + name: 'app-config', + definition: { + describe: 'json config file of testing app', + defaultValue: '', + type: 'string', + }, + prompt: undefined, + }; + + static readonly localBuildPath: CommandFlag = { + constName: 'localBuildPath', + name: 'local-build-path', + definition: { + describe: 'path of hedera local repo', + defaultValue: '', + type: 'string', + }, + prompt: undefined, + }; + + static readonly newAccountNumber: CommandFlag = { + constName: 'newAccountNumber', + name: 'new-account-number', + definition: { + describe: 'new account number for node update transaction', + defaultValue: '', + type: 'string', + }, + prompt: undefined, + }; + + static readonly newAdminKey: CommandFlag = { + constName: 'newAdminKey', + name: 'new-admin-key', + definition: { + describe: 'new admin key for the Hedera account', + defaultValue: '', + type: 'string', + }, + prompt: undefined, + }; + + 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, + }; + + 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', + }, + prompt: undefined, + }; + + 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, + }; + + 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', + }, + prompt: undefined, + }; + + static readonly log4j2Xml: CommandFlag = { + constName: 'log4j2Xml', + name: 'log4j2-xml', + definition: { + describe: 'log4j2.xml file for node', + defaultValue: path.join(constants.SOLO_CACHE_DIR, 'templates', 'log4j2.xml'), + type: 'string', + }, + prompt: undefined, + }; + + 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: ListrTaskWrapper, input: any) { + return await Flags.promptToggle( + task, + input, + Flags.updateAccountKeys.definition.defaultValue, + 'Would you like to updates the special account keys to new keys and stores their keys in a corresponding Kubernetes secret? ', + null, + Flags.updateAccountKeys.name, + ); + }, + }; + + static readonly ed25519PrivateKey: CommandFlag = { + constName: 'ed25519PrivateKey', + name: 'ed25519-private-key', + definition: { + describe: 'ED25519 private key for the Hedera account', + defaultValue: '', + type: 'string', + }, + prompt: async function promptPrivateKey(task: ListrTaskWrapper, input: any) { + return await Flags.promptText( + task, + input, + Flags.ed25519PrivateKey.definition.defaultValue, + 'Enter the private key: ', + null, + Flags.ed25519PrivateKey.name, + ); + }, + }; + + static readonly ecdsaPrivateKey: CommandFlag = { + constName: 'ecdsaPrivateKey', + name: 'ecdsa-private-key', + definition: { + describe: 'ECDSA private key for the Hedera account', + defaultValue: '', + type: 'string', + }, + prompt: async function promptPrivateKey(task: ListrTaskWrapper, input: any) { + return await Flags.promptText( + task, + input, + Flags.ed25519PrivateKey.definition.defaultValue, + 'Enter the private key: ', + null, + Flags.ed25519PrivateKey.name, + ); + }, + }; + + 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, + }; + + 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: ListrTaskWrapper, input: any) { + return await Flags.promptText( + task, + input, + Flags.accountId.definition.defaultValue, + 'Enter the account id: ', + null, + Flags.accountId.name, + ); + }, + }; + + 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: ListrTaskWrapper, input: any) { + return await Flags.prompt( + 'number', + task, + input, + Flags.amount.definition.defaultValue, + 'How much HBAR do you want to add? ', + null, + Flags.amount.name, + ); + }, + }; + + static readonly nodeAlias: CommandFlag = { + constName: 'nodeAlias', + name: 'node-alias', + definition: { + describe: 'Node alias (e.g. node99)', + type: 'string', + }, + prompt: async function promptNewNodeAlias(task: ListrTaskWrapper, input: any) { + return await Flags.promptText( + task, + input, + Flags.nodeAlias.definition.defaultValue, + 'Enter the new node id: ', + null, + Flags.nodeAlias.name, + ); + }, + }; + + static readonly gossipEndpoints: CommandFlag = { + constName: 'gossipEndpoints', + name: 'gossip-endpoints', + definition: { + describe: 'Comma separated gossip endpoints of the node(e.g. first one is internal, second one is external)', + type: 'string', + }, + prompt: async function promptGossipEndpoints(task: ListrTaskWrapper, input: any) { + return await Flags.promptText( + task, + input, + Flags.gossipEndpoints.definition.defaultValue, + 'Enter the gossip endpoints(comma separated): ', + null, + Flags.gossipEndpoints.name, + ); + }, + }; + + static readonly grpcEndpoints: CommandFlag = { + constName: 'grpcEndpoints', + name: 'grpc-endpoints', + definition: { + describe: 'Comma separated gRPC endpoints of the node (at most 8)', + type: 'string', + }, + prompt: async function promptGrpcEndpoints(task: ListrTaskWrapper, input: any) { + return await Flags.promptText( + task, + input, + Flags.grpcEndpoints.definition.defaultValue, + 'Enter the gRPC endpoints(comma separated): ', + null, + Flags.grpcEndpoints.name, + ); + }, + }; + + static readonly endpointType: CommandFlag = { + constName: 'endpointType', + name: 'endpoint-type', + definition: { + describe: 'Endpoint type (IP or FQDN)', + defaultValue: constants.ENDPOINT_TYPE_FQDN, + type: 'string', + }, + prompt: async function promptEndpointType(task: ListrTaskWrapper, input: any) { + return await Flags.promptText( + task, + input, + Flags.endpointType.definition.defaultValue, + 'Enter the endpoint type(IP or FQDN): ', + null, + Flags.endpointType.name, + ); + }, + }; + + static readonly persistentVolumeClaims: CommandFlag = { + constName: 'persistentVolumeClaims', + name: 'pvcs', + definition: { + describe: 'Enable persistent volume claims to store data outside the pod, required for node add', + defaultValue: false, + type: 'boolean', + }, + prompt: async function promptPersistentVolumeClaims(task: ListrTaskWrapper, input: any) { + return await Flags.promptToggle( + task, + input, + Flags.persistentVolumeClaims.definition.defaultValue, + 'Would you like to enable persistent volume claims to store data outside the pod? ', + null, + Flags.persistentVolumeClaims.name, + ); + }, + }; + + static readonly debugNodeAlias: CommandFlag = { + constName: 'debugNodeAlias', + name: 'debug-node-alias', + definition: { + describe: 'Enable default jvm debug port (5005) for the given node id', + defaultValue: '', + type: 'string', + }, + prompt: undefined, + }; + + static readonly outputDir: CommandFlag = { + constName: 'outputDir', + name: 'output-dir', + definition: { + describe: 'Path to the directory where the command context will be saved to', + defaultValue: '', + type: 'string', + }, + prompt: async function promptOutputDir(task: ListrTaskWrapper, input: any) { + return await Flags.promptToggle( + task, + input, + Flags.outputDir.definition.defaultValue, + 'Enter path to directory to store the temporary context file', + null, + Flags.outputDir.name, + ); + }, + }; + + static readonly inputDir: CommandFlag = { + constName: 'inputDir', + name: 'input-dir', + definition: { + describe: 'Path to the directory where the command context will be loaded from', + defaultValue: '', + type: 'string', + }, + prompt: async function promptInputDir(task: ListrTaskWrapper, input: any) { + return await Flags.promptToggle( + task, + input, + Flags.inputDir.definition.defaultValue, + 'Enter path to directory containing the temporary context file', + null, + Flags.inputDir.name, + ); + }, + }; + + static readonly adminKey: CommandFlag = { + constName: 'adminKey', + name: 'admin-key', + definition: { + describe: 'Admin key', + defaultValue: constants.GENESIS_KEY, + type: 'string', + }, + prompt: undefined, + }; + + static readonly quiet: CommandFlag = { + constName: 'quiet', + name: 'quiet-mode', + definition: { + describe: 'Quiet mode, do not prompt for confirmation', + defaultValue: false, + alias: 'q', + type: 'boolean', + disablePrompt: true, + }, + prompt: undefined, + }; + + static readonly mirrorNodeVersion: CommandFlag = { + constName: 'mirrorNodeVersion', + name: 'mirror-node-version', + definition: { + describe: 'Mirror node chart version', + defaultValue: version.MIRROR_NODE_VERSION, + type: 'string', + }, + prompt: async function promptMirrorNodeVersion(task: ListrTaskWrapper, input: any) { + return await Flags.promptToggle( + task, + input, + Flags.mirrorNodeVersion.definition.defaultValue, + 'Would you like to choose mirror node version? ', + null, + Flags.mirrorNodeVersion.name, + ); + }, + }; + + static readonly hederaExplorerVersion: CommandFlag = { + constName: 'hederaExplorerVersion', + name: 'hedera-explorer-version', + definition: { + describe: 'Hedera explorer chart version', + defaultValue: version.HEDERA_EXPLORER_VERSION, + type: 'string', + }, + prompt: async function promptHederaExplorerVersion(task: ListrTaskWrapper, input: any) { + return await Flags.promptToggle( + task, + input, + Flags.hederaExplorerVersion.definition.defaultValue, + 'Would you like to choose hedera explorer version? ', + null, + Flags.hederaExplorerVersion.name, + ); + }, + }; + + static readonly userEmailAddress: CommandFlag = { + constName: 'userEmailAddress', + name: 'email', + definition: { + describe: 'User email address used for local configuration', + type: 'string', + }, + prompt: async function promptUserEmailAddress(task: ListrTaskWrapper, input: any) { + if (input?.length) { + return input; + } + + const promptForInput = async () => { + return await task.prompt(ListrEnquirerPromptAdapter).run({ + type: 'text', + message: 'Please enter your email address:', + }); + }; + + input = await promptForInput(); + while (!validator.isEmail(input)) { + input = await promptForInput(); + } + + return input; + }, + }; + + static readonly context: CommandFlag = { + constName: 'contextName', + name: 'context', + definition: { + describe: 'The Kubernetes context name to be used', + defaultValue: '', + type: 'string', + }, + prompt: async function promptContext(task: ListrTaskWrapper, input: string[]) { + return await task.prompt(ListrEnquirerPromptAdapter).run({ + type: 'select', + name: 'context', + message: 'Select kubectl context', + choices: input, + }); + }, + }; + + static readonly deploymentClusters: CommandFlag = { + constName: 'deploymentClusters', + name: 'deployment-clusters', + definition: { + describe: 'Solo deployment cluster list (comma separated)', + type: 'string', + }, + prompt: async function promptDeploymentClusters(task: ListrTaskWrapper, input: any) { + return await Flags.promptText( + task, + input, + Flags.deploymentClusters.definition.defaultValue, + 'Enter the Solo deployment cluster names (comma separated): ', + null, + Flags.deploymentClusters.name, + ); + }, + }; + + static readonly pinger: CommandFlag = { + constName: 'pinger', + name: 'pinger', + definition: { + describe: 'Enable Pinger service in the Mirror node monitor', + defaultValue: false, + type: 'boolean', + }, + prompt: undefined, + }; + + //* ------------- Node Proxy Certificates ------------- !// + + static readonly grpcTlsCertificatePath: CommandFlag = { + constName: 'grpcTlsCertificatePath', + name: 'grpc-tls-cert', + definition: { + describe: + 'TLS Certificate path for the gRPC ' + + '(e.g. "node1=/Users/username/node1-grpc.cert" ' + + 'with multiple nodes comma seperated)', + defaultValue: '', + type: 'string', + }, + prompt: async function promptGrpcTlsCertificatePath(task: ListrTaskWrapper, input: any) { + return await Flags.promptText( + task, + input, + Flags.grpcTlsCertificatePath.definition.defaultValue, + 'Enter node alias and path to TLS certificate for gRPC (ex. nodeAlias=path )', + null, + Flags.grpcTlsCertificatePath.name, + ); + }, + }; + + static readonly grpcWebTlsCertificatePath: CommandFlag = { + constName: 'grpcWebTlsCertificatePath', + name: 'grpc-web-tls-cert', + definition: { + describe: + 'TLS Certificate path for gRPC Web ' + + '(e.g. "node1=/Users/username/node1-grpc-web.cert" ' + + 'with multiple nodes comma seperated)', + defaultValue: '', + type: 'string', + }, + prompt: async function promptGrpcWebTlsCertificatePath(task: ListrTaskWrapper, input: any) { + return await Flags.promptText( + task, + input, + Flags.grpcWebTlsCertificatePath.definition.defaultValue, + 'Enter node alias and path to TLS certificate for gGRPC web (ex. nodeAlias=path )', + null, + Flags.grpcWebTlsCertificatePath.name, + ); + }, + }; + + static readonly grpcTlsKeyPath: CommandFlag = { + constName: 'grpcTlsKeyPath', + name: 'grpc-tls-key', + definition: { + describe: + 'TLS Certificate key path for the gRPC ' + + '(e.g. "node1=/Users/username/node1-grpc.key" ' + + 'with multiple nodes comma seperated)', + defaultValue: '', + type: 'string', + }, + prompt: async function promptGrpcTlsKeyPath(task: ListrTaskWrapper, input: any) { + return await Flags.promptText( + task, + input, + Flags.grpcTlsKeyPath.definition.defaultValue, + 'Enter node alias and path to TLS certificate key for gRPC (ex. nodeAlias=path )', + null, + Flags.grpcTlsKeyPath.name, + ); + }, + }; + + static readonly grpcWebTlsKeyPath: CommandFlag = { + constName: 'grpcWebTlsKeyPath', + name: 'grpc-web-tls-key', + definition: { + describe: + 'TLC Certificate key path for gRPC Web ' + + '(e.g. "node1=/Users/username/node1-grpc-web.key" ' + + 'with multiple nodes comma seperated)', + defaultValue: '', + type: 'string', + }, + prompt: async function promptGrpcWebTlsKeyPath(task: ListrTaskWrapper, input: any) { + return await Flags.promptText( + task, + input, + Flags.grpcWebTlsKeyPath.definition.defaultValue, + 'Enter node alias and path to TLS certificate key for gGRPC Web (ex. nodeAlias=path )', + null, + Flags.grpcWebTlsKeyPath.name, + ); + }, + }; + + static readonly stakeAmounts: CommandFlag = { + constName: 'stakeAmounts', + name: 'stake-amounts', + definition: { + describe: + 'The amount to be staked in the same order you list the node aliases with multiple node staked values comma seperated', + defaultValue: '', + type: 'string', + }, + prompt: undefined, + }; + + static readonly contextClusterUnparsed: CommandFlag = { + constName: 'contextClusterUnparsed', + name: 'context-cluster', + definition: { + describe: + 'Context cluster mapping where context is key = value is cluster and comma delimited if more than one, ' + + '(e.g.: --context-cluster kind-solo=kind-solo,kind-solo-2=kind-solo-2)', + type: 'string', + }, + prompt: async function promptContextCluster(task: ListrTaskWrapper, input: any) { + return await Flags.promptText( + task, + input, + null, + 'Enter context cluster mapping: ', + 'context-cluster cannot be empty', + Flags.contextClusterUnparsed.name, + ); + }, + }; + + static readonly allFlags: CommandFlag[] = [ + Flags.accountId, + Flags.amount, + Flags.apiPermissionProperties, + Flags.app, + Flags.appConfig, + Flags.applicationEnv, + Flags.applicationProperties, + Flags.bootstrapProperties, + Flags.cacheDir, + Flags.chainId, + Flags.chartDirectory, + Flags.clusterName, + Flags.clusterSetupNamespace, + Flags.context, + Flags.deletePvcs, + Flags.deleteSecrets, + Flags.deployCertManager, + Flags.deployCertManagerCrds, + Flags.deployHederaExplorer, + Flags.deployJsonRpcRelay, + Flags.deploymentClusters, + Flags.deployMinio, + Flags.deployPrometheusStack, + Flags.devMode, + Flags.ecdsaPrivateKey, + Flags.ed25519PrivateKey, + Flags.enableHederaExplorerTls, + Flags.enablePrometheusSvcMonitor, + Flags.enableTimeout, + Flags.endpointType, + Flags.soloChartVersion, + Flags.generateGossipKeys, + Flags.generateTlsKeys, + Flags.gossipEndpoints, + Flags.gossipPrivateKey, + Flags.gossipPublicKey, + Flags.grpcEndpoints, + Flags.hederaExplorerTlsHostName, + Flags.hederaExplorerTlsLoadBalancerIp, + Flags.inputDir, + Flags.debugNodeAlias, + Flags.localBuildPath, + Flags.log4j2Xml, + Flags.namespace, + Flags.newAccountNumber, + Flags.newAdminKey, + Flags.nodeAlias, + Flags.nodeAliasesUnparsed, + Flags.operatorId, + Flags.operatorKey, + Flags.outputDir, + Flags.persistentVolumeClaims, + Flags.privateKey, + Flags.profileFile, + Flags.profileName, + Flags.pinger, + Flags.relayReleaseTag, + Flags.releaseTag, + Flags.replicaCount, + Flags.stateFile, + Flags.setAlias, + Flags.settingTxt, + Flags.stakeAmounts, + Flags.tlsClusterIssuerType, + Flags.tlsPrivateKey, + Flags.tlsPublicKey, + Flags.updateAccountKeys, + Flags.userEmailAddress, + Flags.valuesFile, + Flags.mirrorNodeVersion, + Flags.hederaExplorerVersion, + Flags.grpcTlsCertificatePath, + Flags.grpcWebTlsCertificatePath, + Flags.grpcTlsKeyPath, + Flags.grpcWebTlsKeyPath, + Flags.contextClusterUnparsed, + ]; + + /** Resets the definition.disablePrompt for all flags */ + static resetDisabledPrompts() { + Flags.allFlags.forEach(f => { + if (f.definition.disablePrompt) { + delete f.definition.disablePrompt; + } + }); + } + + static readonly allFlagsMap = new Map(Flags.allFlags.map(f => [f.name, f])); + + static readonly nodeConfigFileFlags = new Map( + [ + Flags.apiPermissionProperties, + Flags.applicationProperties, + Flags.bootstrapProperties, + Flags.log4j2Xml, + Flags.settingTxt, + ].map(f => [f.name, f]), + ); + + static readonly integerFlags = new Map([Flags.replicaCount].map(f => [f.name, f])); + + static readonly DEFAULT_FLAGS = { + requiredFlags: [], + requiredFlagsWithDisabledPrompt: [Flags.namespace, Flags.cacheDir, Flags.releaseTag], + optionalFlags: [Flags.devMode], + }; } - -export const allFlagsMap = new Map(allFlags.map(f => [f.name, f])); - -export const nodeConfigFileFlags = new Map( - [apiPermissionProperties, applicationProperties, bootstrapProperties, log4j2Xml, settingTxt].map(f => [f.name, f]), -); - -export const integerFlags = new Map([replicaCount].map(f => [f.name, f])); - -export const DEFAULT_FLAGS = { - requiredFlags: [], - requiredFlagsWithDisabledPrompt: [namespace, cacheDir, releaseTag], - optionalFlags: [devMode], -}; diff --git a/src/commands/index.ts b/src/commands/index.ts index 142723a58..f73e2c0c9 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -14,7 +14,7 @@ * limitations under the License. * */ -import * as flags from './flags.js'; +import {Flags as flags} from './flags.js'; import {ClusterCommand} from './cluster.js'; import {ContextCommand} from './context/index.js'; import {InitCommand} from './init.js'; diff --git a/src/commands/init.ts b/src/commands/init.ts index e224e32d2..6b5d38384 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -21,7 +21,7 @@ import * as core from '../core/index.js'; import {constants} from '../core/index.js'; import * as fs from 'fs'; import {SoloError} from '../core/errors.js'; -import * as flags from './flags.js'; +import {flags} from './index.js'; import chalk from 'chalk'; /** diff --git a/src/commands/mirror_node.ts b/src/commands/mirror_node.ts index 590288c68..5f08ba074 100644 --- a/src/commands/mirror_node.ts +++ b/src/commands/mirror_node.ts @@ -19,8 +19,7 @@ import {Listr} from 'listr2'; import {SoloError, IllegalArgumentError, MissingArgumentError} from '../core/errors.js'; import {constants, type ProfileManager, type AccountManager} from '../core/index.js'; import {BaseCommand} from './base.js'; -import * as flags from './flags.js'; -import * as prompts from './prompts.js'; +import {flags} from './index.js'; import {getFileContents, getEnvValue} from '../core/helpers.js'; import {RemoteConfigTasks} from '../core/config/remote/remote_config_tasks.js'; import {CommandBuilder, type PodName} from '../types/aliases.js'; @@ -180,7 +179,7 @@ export class MirrorNodeCommand extends BaseCommand { self.configManager.update(argv); // disable the prompts that we don't want to prompt the user for - prompts.disablePrompts([ + flags.disablePrompts([ flags.chartDirectory, flags.deployHederaExplorer, flags.enableHederaExplorerTls, @@ -193,7 +192,7 @@ export class MirrorNodeCommand extends BaseCommand { flags.pinger, ]); - await prompts.execute(task, self.configManager, MirrorNodeCommand.DEPLOY_FLAGS_LIST); + await flags.executePrompt(task, self.configManager, MirrorNodeCommand.DEPLOY_FLAGS_LIST); ctx.config = this.getConfig(MirrorNodeCommand.DEPLOY_CONFIGS_NAME, MirrorNodeCommand.DEPLOY_FLAGS_LIST, [ 'chartPath', @@ -470,7 +469,7 @@ export class MirrorNodeCommand extends BaseCommand { } self.configManager.update(argv); - await prompts.execute(task, self.configManager, [flags.namespace]); + await flags.executePrompt(task, self.configManager, [flags.namespace]); // @ts-ignore ctx.config = { diff --git a/src/commands/network.ts b/src/commands/network.ts index 80d66892c..e70055c43 100644 --- a/src/commands/network.ts +++ b/src/commands/network.ts @@ -19,9 +19,8 @@ import chalk from 'chalk'; import {Listr} from 'listr2'; import {SoloError, IllegalArgumentError, MissingArgumentError} from '../core/errors.js'; import {BaseCommand} from './base.js'; -import * as flags from './flags.js'; +import {flags} from './index.js'; import {constants, Templates} from '../core/index.js'; -import * as prompts from './prompts.js'; import * as helpers from '../core/helpers.js'; import path from 'path'; import {addDebugOptions, validatePath} from '../core/helpers.js'; @@ -173,7 +172,7 @@ export class NetworkCommand extends BaseCommand { this.logger.debug('Loaded cached config', {config: this.configManager.config}); // disable the prompts that we don't want to prompt the user for - prompts.disablePrompts([ + flags.disablePrompts([ flags.apiPermissionProperties, flags.app, flags.applicationEnv, @@ -193,7 +192,7 @@ export class NetworkCommand extends BaseCommand { flags.grpcWebTlsKeyPath, ]); - await prompts.execute(task, this.configManager, NetworkCommand.DEPLOY_FLAGS_LIST); + await flags.executePrompt(task, this.configManager, NetworkCommand.DEPLOY_FLAGS_LIST); // create a config object for subsequent steps const config = this.getConfig(NetworkCommand.DEPLOY_CONFIGS_NAME, NetworkCommand.DEPLOY_FLAGS_LIST, [ @@ -524,7 +523,11 @@ export class NetworkCommand extends BaseCommand { } self.configManager.update(argv); - await prompts.execute(task, self.configManager, [flags.deletePvcs, flags.deleteSecrets, flags.namespace]); + await flags.executePrompt(task, self.configManager, [ + flags.deletePvcs, + flags.deleteSecrets, + flags.namespace, + ]); ctx.config = { deletePvcs: self.configManager.getFlag(flags.deletePvcs) as boolean, diff --git a/src/commands/node/configs.ts b/src/commands/node/configs.ts index 6bc513207..53780cc47 100644 --- a/src/commands/node/configs.ts +++ b/src/commands/node/configs.ts @@ -22,7 +22,7 @@ import * as helpers from '../../core/helpers.js'; import path from 'path'; import fs from 'fs'; import {validatePath} from '../../core/helpers.js'; -import * as flags from '../flags.js'; +import {flags} from '../index.js'; import {type NodeAlias, type NodeAliases, type PodName} from '../../types/aliases.js'; import {type NetworkNodeServices} from '../../core/network_node_services.js'; diff --git a/src/commands/node/tasks.ts b/src/commands/node/tasks.ts index bcc01c21e..0cf8b3772 100644 --- a/src/commands/node/tasks.ts +++ b/src/commands/node/tasks.ts @@ -52,7 +52,6 @@ import { Timestamp, } from '@hashgraph/sdk'; import {IllegalArgumentError, MissingArgumentError, SoloError} from '../../core/errors.js'; -import * as prompts from '../prompts.js'; import path from 'path'; import fs from 'fs'; import crypto from 'crypto'; @@ -67,7 +66,7 @@ import { splitFlagInput, } from '../../core/helpers.js'; import chalk from 'chalk'; -import * as flags from '../flags.js'; +import {flags} from '../index.js'; import {type SoloLogger} from '../../core/logging.js'; import type {Listr, ListrTaskWrapper} from 'listr2'; import {ConfigBuilder, type NodeAlias, type NodeAliases, type PodName, SkipCheck} from '../../types/aliases.js'; @@ -1606,7 +1605,7 @@ export class NodeCommandTasks { this.configManager.update(argv); // disable the prompts that we don't want to prompt the user for - prompts.disablePrompts([...requiredFlagsWithDisabledPrompt, ...optionalFlags]); + flags.disablePrompts([...requiredFlagsWithDisabledPrompt, ...optionalFlags]); const flagsToPrompt = []; for (const pFlag of requiredFlags) { @@ -1616,7 +1615,7 @@ export class NodeCommandTasks { } } - await prompts.execute(task, this.configManager, flagsToPrompt); + await flags.executePrompt(task, this.configManager, flagsToPrompt); const config = await configInit(argv, ctx, task); ctx.config = config; diff --git a/src/commands/prompts.ts b/src/commands/prompts.ts deleted file mode 100644 index 0e3ae39bf..000000000 --- a/src/commands/prompts.ts +++ /dev/null @@ -1,818 +0,0 @@ -/** - * Copyright (C) 2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the ""License""); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an ""AS IS"" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ -import {ListrEnquirerPromptAdapter} from '@listr2/prompt-adapter-enquirer'; -import fs from 'fs'; -import {SoloError, IllegalArgumentError} from '../core/errors.js'; -import {ConfigManager, constants} from '../core/index.js'; -import * as flags from './flags.js'; -import * as helpers from '../core/helpers.js'; -import {resetDisabledPrompts} from './flags.js'; -import type {ListrTaskWrapper} from 'listr2'; -import {type CommandFlag} from '../types/index.js'; -import validator from 'validator'; -import {UserPrompt} from '../types/aliases.js'; - -async function prompt( - type: string, - task: ListrTaskWrapper, - input: any, - defaultValue: any, - promptMessage: string, - emptyCheckMessage: string | null, - flagName: string, -) { - try { - let needsPrompt = 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'); - } - - input = await task.prompt(ListrEnquirerPromptAdapter).run({ - type, - default: defaultValue, - message: promptMessage, - }); - } - - if (emptyCheckMessage && !input) { - throw new SoloError(emptyCheckMessage); - } - - return input; - } catch (e: Error | any) { - throw new SoloError(`input failed: ${flagName}: ${e.message}`, e); - } -} - -async function promptText( - task: ListrTaskWrapper, - input: any, - defaultValue: any, - promptMessage: string, - emptyCheckMessage: string | null, - flagName: string, -) { - return await prompt('text', task, input, defaultValue, promptMessage, emptyCheckMessage, flagName); -} - -async function promptToggle( - task: ListrTaskWrapper, - input: any, - defaultValue: any, - promptMessage: string, - emptyCheckMessage: string | null, - flagName: string, -) { - return await prompt('toggle', task, input, defaultValue, promptMessage, emptyCheckMessage, flagName); -} - -export async function promptNamespace(task: ListrTaskWrapper, input: any) { - return await promptText( - task, - input, - 'solo', - 'Enter namespace name: ', - 'namespace cannot be empty', - flags.namespace.name, - ); -} - -export async function promptClusterSetupNamespace(task: ListrTaskWrapper, input: any) { - return await promptText( - task, - input, - 'solo-cluster', - 'Enter cluster setup namespace name: ', - 'cluster setup namespace cannot be empty', - flags.clusterSetupNamespace.name, - ); -} - -export async function promptNodeAliases(task: ListrTaskWrapper, input: any) { - return await prompt( - 'input', - task, - input, - 'node1,node2,node3', - 'Enter list of node IDs (comma separated list): ', - null, - flags.nodeAliasesUnparsed.name, - ); -} - -export async function promptReleaseTag(task: ListrTaskWrapper, input: any) { - return await promptText( - task, - input, - 'v0.42.5', - 'Enter release version: ', - 'release tag cannot be empty', - flags.releaseTag.name, - ); -} - -export async function promptRelayReleaseTag(task: ListrTaskWrapper, input: any) { - return await promptText( - task, - input, - flags.relayReleaseTag.definition.defaultValue, - 'Enter relay release version: ', - 'relay-release-tag cannot be empty', - flags.relayReleaseTag.name, - ); -} - -export async function promptCacheDir(task: ListrTaskWrapper, input: any) { - return await promptText( - task, - input, - constants.SOLO_CACHE_DIR, - 'Enter local cache directory path: ', - null, - flags.cacheDir.name, - ); -} - -export async function promptForce(task: ListrTaskWrapper, input: any) { - return await promptToggle( - task, - input, - flags.force.definition.defaultValue, - 'Would you like to force changes? ', - null, - flags.force.name, - ); -} - -export async function promptChainId(task: ListrTaskWrapper, input: any) { - return await promptText( - task, - input, - flags.chainId.definition.defaultValue, - 'Enter chain ID: ', - null, - flags.chainId.name, - ); -} - -export async function promptChartDir(task: ListrTaskWrapper, input: any) { - try { - if (input === 'false') { - return ''; - } - - if (input && !fs.existsSync(input)) { - input = await task.prompt(ListrEnquirerPromptAdapter).run({ - type: 'text', - default: flags.chartDirectory.definition.defaultValue, - message: 'Enter local charts directory path: ', - }); - - if (!fs.existsSync(input)) { - throw new IllegalArgumentError('Invalid chart directory', input); - } - } - - return input; - } catch (e: Error | any) { - throw new SoloError(`input failed: ${flags.chartDirectory.name}`, e); - } -} - -export async function promptValuesFile(task: ListrTaskWrapper, input: any) { - try { - if (input && !fs.existsSync(input)) { - input = await task.prompt(ListrEnquirerPromptAdapter).run({ - type: 'text', - default: flags.valuesFile.definition.defaultValue, - message: 'Enter path to values.yaml: ', - }); - - if (!fs.existsSync(input)) { - throw new IllegalArgumentError('Invalid values.yaml file', input); - } - } - - return input; - } catch (e: Error | any) { - throw new SoloError(`input failed: ${flags.valuesFile.name}`, e); - } -} - -export async function promptProfileFile(task: ListrTaskWrapper, input: any) { - if (input && !fs.existsSync(input)) { - input = await task.prompt(ListrEnquirerPromptAdapter).run({ - type: 'text', - default: flags.valuesFile.definition.defaultValue, - message: 'Enter path to custom resource profile definition file: ', - }); - } - - if (input && !fs.existsSync(input)) { - throw new IllegalArgumentError(`Invalid profile definition file: ${input}}`, input); - } - - return input; -} - -export async function promptProfile( - task: ListrTaskWrapper, - input: any, - choices = constants.ALL_PROFILES, -) { - try { - const initial = choices.indexOf(input); - if (initial < 0) { - const input = await task.prompt(ListrEnquirerPromptAdapter).run({ - type: 'select', - message: 'Select profile for solo network deployment', - choices: helpers.cloneArray(choices), - }); - - if (!input) { - throw new SoloError('key-format cannot be empty'); - } - - return input; - } - - return input; - } catch (e: Error | any) { - throw new SoloError(`input failed: ${flags.profileName.name}`, e); - } -} - -export async function promptDeployPrometheusStack(task: ListrTaskWrapper, input: any) { - return await promptToggle( - task, - input, - flags.deployPrometheusStack.definition.defaultValue, - 'Would you like to deploy prometheus stack? ', - null, - flags.deployPrometheusStack.name, - ); -} - -export async function promptEnablePrometheusSvcMonitor(task: ListrTaskWrapper, input: any) { - return await promptToggle( - task, - input, - flags.enablePrometheusSvcMonitor.definition.defaultValue, - 'Would you like to enable the Prometheus service monitor for the network nodes? ', - null, - flags.enablePrometheusSvcMonitor.name, - ); -} - -export async function promptDeployMinio(task: ListrTaskWrapper, input: any) { - return await promptToggle( - task, - input, - flags.deployMinio.definition.defaultValue, - 'Would you like to deploy MinIO? ', - null, - flags.deployMinio.name, - ); -} - -export async function promptDeployCertManager(task: ListrTaskWrapper, input: any) { - return await promptToggle( - task, - input, - flags.deployCertManager.definition.defaultValue, - 'Would you like to deploy Cert Manager? ', - null, - flags.deployCertManager.name, - ); -} - -export async function promptDeployCertManagerCrds(task: ListrTaskWrapper, input: any) { - return await promptToggle( - task, - input, - flags.deployCertManagerCrds.definition.defaultValue, - 'Would you like to deploy Cert Manager CRDs? ', - null, - flags.deployCertManagerCrds.name, - ); -} - -export async function promptDeployHederaExplorer(task: ListrTaskWrapper, input: any) { - return await promptToggle( - task, - input, - flags.deployHederaExplorer.definition.defaultValue, - 'Would you like to deploy Hedera Explorer? ', - null, - flags.deployHederaExplorer.name, - ); -} - -export async function promptTlsClusterIssuerType(task: ListrTaskWrapper, input: any) { - try { - if (!input) { - input = await task.prompt(ListrEnquirerPromptAdapter).run({ - type: 'text', - default: flags.tlsClusterIssuerType.definition.defaultValue, - message: 'Enter TLS cluster issuer type, available options are: "acme-staging", "acme-prod", or "self-signed":', - }); - } - - if (!input || !['acme-staging', 'acme-prod', 'self-signed'].includes(input)) { - throw new SoloError('must be one of: "acme-staging", "acme-prod", or "self-signed"'); - } - - return input; - } catch (e: Error | any) { - throw new SoloError(`input failed: ${flags.tlsClusterIssuerType.name}`, e); - } -} - -export async function promptEnableHederaExplorerTls(task: ListrTaskWrapper, input: any) { - return await promptToggle( - task, - input, - flags.enableHederaExplorerTls.definition.defaultValue, - 'Would you like to enable the Hedera Explorer TLS? ', - null, - flags.enableHederaExplorerTls.name, - ); -} - -export async function promptHederaExplorerTlsHostName(task: ListrTaskWrapper, input: any) { - return await promptText( - task, - input, - flags.hederaExplorerTlsHostName.definition.defaultValue, - 'Enter the host name to use for the Hedera Explorer TLS: ', - null, - flags.hederaExplorerTlsHostName.name, - ); -} - -export async function promptOperatorId(task: ListrTaskWrapper, input: any) { - return await promptText( - task, - input, - flags.operatorId.definition.defaultValue, - 'Enter operator ID: ', - null, - flags.operatorId.name, - ); -} - -export async function promptOperatorKey(task: ListrTaskWrapper, input: any) { - return await promptText( - task, - input, - flags.operatorKey.definition.defaultValue, - 'Enter operator private key: ', - null, - flags.operatorKey.name, - ); -} - -export async function promptReplicaCount(task: ListrTaskWrapper, input: any) { - return await prompt( - 'number', - task, - input, - flags.replicaCount.definition.defaultValue, - 'How many replica do you want? ', - null, - flags.replicaCount.name, - ); -} - -export async function promptGenerateGossipKeys(task: ListrTaskWrapper, input: any) { - return await promptToggle( - task, - input, - flags.generateGossipKeys.definition.defaultValue, - `Would you like to generate Gossip keys? ${typeof input} ${input} `, - null, - flags.generateGossipKeys.name, - ); -} - -export async function promptGenerateTLSKeys(task: ListrTaskWrapper, input: any) { - return await promptToggle( - task, - input, - flags.generateTlsKeys.definition.defaultValue, - 'Would you like to generate TLS keys? ', - null, - flags.generateTlsKeys.name, - ); -} - -export async function promptDeletePvcs(task: ListrTaskWrapper, input: any) { - return await promptToggle( - task, - input, - flags.deletePvcs.definition.defaultValue, - 'Would you like to delete persistent volume claims upon uninstall? ', - null, - flags.deletePvcs.name, - ); -} - -export async function promptDeleteSecrets(task: ListrTaskWrapper, input: any) { - return await promptToggle( - task, - input, - flags.deleteSecrets.definition.defaultValue, - 'Would you like to delete secrets upon uninstall? ', - null, - flags.deleteSecrets.name, - ); -} - -export async function promptSoloChartVersion(task: ListrTaskWrapper, input: any) { - return await promptText( - task, - input, - flags.soloChartVersion.definition.defaultValue, - 'Enter solo testing chart version: ', - null, - flags.soloChartVersion.name, - ); -} - -export async function promptUpdateAccountKeys(task: ListrTaskWrapper, input: any) { - return await promptToggle( - task, - input, - flags.updateAccountKeys.definition.defaultValue, - 'Would you like to updates the special account keys to new keys and stores their keys in a corresponding Kubernetes secret? ', - null, - flags.updateAccountKeys.name, - ); -} - -export async function promptUserEmailAddress(task: ListrTaskWrapper, input: any) { - if (input?.length) { - return input; - } - - const promptForInput = async () => { - return await task.prompt(ListrEnquirerPromptAdapter).run({ - type: 'text', - message: 'Please enter your email address:', - }); - }; - - input = await promptForInput(); - while (!validator.isEmail(input)) { - input = await promptForInput(); - } - - return input; -} - -export async function promptDeploymentClusters(task: ListrTaskWrapper, input: any) { - return await promptText( - task, - input, - flags.deploymentClusters.definition.defaultValue, - 'Enter the Solo deployment cluster names (comma separated): ', - null, - flags.deploymentClusters.name, - ); -} - -export async function promptPrivateKey(task: ListrTaskWrapper, input: any) { - return await promptText( - task, - input, - flags.ed25519PrivateKey.definition.defaultValue, - 'Enter the private key: ', - null, - flags.ed25519PrivateKey.name, - ); -} - -export async function promptAccountId(task: ListrTaskWrapper, input: any) { - return await promptText( - task, - input, - flags.accountId.definition.defaultValue, - 'Enter the account id: ', - null, - flags.accountId.name, - ); -} - -export async function promptAmount(task: ListrTaskWrapper, input: any) { - return await prompt( - 'number', - task, - input, - flags.amount.definition.defaultValue, - 'How much HBAR do you want to add? ', - null, - flags.amount.name, - ); -} - -export async function promptNewNodeAlias(task: ListrTaskWrapper, input: any) { - return await promptText( - task, - input, - flags.nodeAlias.definition.defaultValue, - 'Enter the new node id: ', - null, - flags.nodeAlias.name, - ); -} - -export async function promptGossipEndpoints(task: ListrTaskWrapper, input: any) { - return await promptText( - task, - input, - flags.gossipEndpoints.definition.defaultValue, - 'Enter the gossip endpoints(comma separated): ', - null, - flags.gossipEndpoints.name, - ); -} - -export async function promptGrpcEndpoints(task: ListrTaskWrapper, input: any) { - return await promptText( - task, - input, - flags.grpcEndpoints.definition.defaultValue, - 'Enter the gRPC endpoints(comma separated): ', - null, - flags.grpcEndpoints.name, - ); -} - -export async function promptEndpointType(task: ListrTaskWrapper, input: any) { - return await promptText( - task, - input, - flags.endpointType.definition.defaultValue, - 'Enter the endpoint type(IP or FQDN): ', - null, - flags.endpointType.name, - ); -} - -export async function promptContext(task: ListrTaskWrapper, input: string[]) { - return await task.prompt(ListrEnquirerPromptAdapter).run({ - type: 'select', - name: 'context', - message: 'Select kubectl context', - choices: input, - }); -} - -export async function promptClusterName(task: ListrTaskWrapper, input: any) { - return await promptText( - task, - input, - flags.clusterName.definition.defaultValue, - 'Enter the cluster name: ', - null, - flags.clusterName.name, - ); -} - -export async function promptPersistentVolumeClaims(task: ListrTaskWrapper, input: any) { - return await promptToggle( - task, - input, - flags.persistentVolumeClaims.definition.defaultValue, - 'Would you like to enable persistent volume claims to store data outside the pod? ', - null, - flags.persistentVolumeClaims.name, - ); -} - -export async function promptMirrorNodeVersion(task: ListrTaskWrapper, input: any) { - return await promptToggle( - task, - input, - flags.mirrorNodeVersion.definition.defaultValue, - 'Would you like to choose mirror node version? ', - null, - flags.mirrorNodeVersion.name, - ); -} - -export async function promptHederaExplorerVersion(task: ListrTaskWrapper, input: any) { - return await promptToggle( - task, - input, - flags.hederaExplorerVersion.definition.defaultValue, - 'Would you like to choose hedera explorer version? ', - null, - flags.hederaExplorerVersion.name, - ); -} - -export async function promptInputDir(task: ListrTaskWrapper, input: any) { - return await promptToggle( - task, - input, - flags.inputDir.definition.defaultValue, - 'Enter path to directory containing the temporary context file', - null, - flags.inputDir.name, - ); -} - -export async function promptOutputDir(task: ListrTaskWrapper, input: any) { - return await promptToggle( - task, - input, - flags.outputDir.definition.defaultValue, - 'Enter path to directory to store the temporary context file', - null, - flags.outputDir.name, - ); -} - -export async function promptContextCluster(task: ListrTaskWrapper, input: any) { - return await promptText( - task, - input, - null, - 'Enter context cluster mapping: ', - 'context-cluster cannot be empty', - flags.contextClusterUnparsed.name, - ); -} - -//! ------------- Node Proxy Certificates ------------- !// - -export async function promptGrpcTlsCertificatePath(task: ListrTaskWrapper, input: any) { - return await promptText( - task, - input, - flags.grpcTlsCertificatePath.definition.defaultValue, - 'Enter node alias and path to TLS certificate for gRPC (ex. nodeAlias=path )', - null, - flags.grpcTlsCertificatePath.name, - ); -} - -export async function promptGrpcWebTlsCertificatePath(task: ListrTaskWrapper, input: any) { - return await promptText( - task, - input, - flags.grpcWebTlsCertificatePath.definition.defaultValue, - 'Enter node alias and path to TLS certificate for gGRPC web (ex. nodeAlias=path )', - null, - flags.grpcWebTlsCertificatePath.name, - ); -} - -export async function promptGrpcTlsKeyPath(task: ListrTaskWrapper, input: any) { - return await promptText( - task, - input, - flags.grpcTlsKeyPath.definition.defaultValue, - 'Enter node alias and path to TLS certificate key for gRPC (ex. nodeAlias=path )', - null, - flags.grpcTlsKeyPath.name, - ); -} - -export async function promptGrpcWebTlsKeyPath(task: ListrTaskWrapper, input: any) { - return await promptText( - task, - input, - flags.grpcWebTlsKeyPath.definition.defaultValue, - 'Enter node alias and path to TLS certificate key for gGRPC Web (ex. nodeAlias=path )', - null, - flags.grpcWebTlsKeyPath.name, - ); -} - -export function getPromptMap(): Map { - return ( - new Map() - .set(flags.accountId.name, promptAccountId) - .set(flags.amount.name, promptAmount) - .set(flags.cacheDir.name, promptCacheDir) - .set(flags.chainId.name, promptChainId) - .set(flags.chartDirectory.name, promptChartDir) - .set(flags.clusterName.name, promptClusterName) - .set(flags.clusterSetupNamespace.name, promptClusterSetupNamespace) - .set(flags.context.name, promptContext) - .set(flags.deletePvcs.name, promptDeletePvcs) - .set(flags.deleteSecrets.name, promptDeleteSecrets) - .set(flags.deployCertManager.name, promptDeployCertManager) - .set(flags.deployCertManagerCrds.name, promptDeployCertManagerCrds) - .set(flags.deployHederaExplorer.name, promptDeployHederaExplorer) - .set(flags.deployMinio.name, promptDeployMinio) - .set(flags.deployPrometheusStack.name, promptDeployPrometheusStack) - .set(flags.enableHederaExplorerTls.name, promptEnableHederaExplorerTls) - .set(flags.enablePrometheusSvcMonitor.name, promptEnablePrometheusSvcMonitor) - .set(flags.force.name, promptForce) - .set(flags.soloChartVersion.name, promptSoloChartVersion) - .set(flags.generateGossipKeys.name, promptGenerateGossipKeys) - .set(flags.generateTlsKeys.name, promptGenerateTLSKeys) - .set(flags.hederaExplorerTlsHostName.name, promptHederaExplorerTlsHostName) - .set(flags.namespace.name, promptNamespace) - .set(flags.nodeAliasesUnparsed.name, promptNodeAliases) - .set(flags.operatorId.name, promptOperatorId) - .set(flags.operatorKey.name, promptOperatorKey) - .set(flags.persistentVolumeClaims.name, promptPersistentVolumeClaims) - .set(flags.ed25519PrivateKey.name, promptPrivateKey) - .set(flags.profileFile.name, promptProfileFile) - .set(flags.profileName.name, promptProfile) - .set(flags.relayReleaseTag.name, promptRelayReleaseTag) - .set(flags.releaseTag.name, promptReleaseTag) - .set(flags.replicaCount.name, promptReplicaCount) - .set(flags.tlsClusterIssuerType.name, promptTlsClusterIssuerType) - .set(flags.updateAccountKeys.name, promptUpdateAccountKeys) - .set(flags.userEmailAddress.name, promptUserEmailAddress) - .set(flags.valuesFile.name, promptValuesFile) - .set(flags.nodeAlias.name, promptNewNodeAlias) - .set(flags.gossipEndpoints.name, promptGossipEndpoints) - .set(flags.grpcEndpoints.name, promptGrpcEndpoints) - .set(flags.endpointType.name, promptEndpointType) - .set(flags.mirrorNodeVersion.name, promptMirrorNodeVersion) - .set(flags.hederaExplorerVersion, promptHederaExplorerVersion) - .set(flags.inputDir.name, promptInputDir) - .set(flags.outputDir.name, promptOutputDir) - .set(flags.contextClusterUnparsed.name, promptContextCluster) - .set(flags.context.name, promptContext) - .set(flags.deploymentClusters.name, promptDeploymentClusters) - - //! Node Proxy Certificates - .set(flags.grpcTlsCertificatePath.name, promptGrpcTlsCertificatePath) - .set(flags.grpcWebTlsCertificatePath.name, promptGrpcWebTlsCertificatePath) - .set(flags.grpcTlsKeyPath.name, promptGrpcTlsKeyPath) - .set(flags.grpcWebTlsKeyPath.name, promptGrpcWebTlsKeyPath) - ); -} - -// build the prompt registry -/** - * Run prompts for the given set of flags - * @param task task object from listr2 - * @param configManager config manager to store flag values - * @param flagList list of flag objects - */ -export async function execute( - task: ListrTaskWrapper, - configManager: ConfigManager, - flagList: CommandFlag[] = [], -) { - if (!configManager || !(configManager instanceof ConfigManager)) { - throw new IllegalArgumentError('an instance of ConfigManager is required'); - } - const prompts = getPromptMap(); - for (const flag of flagList) { - if (flag.definition.disablePrompt) { - continue; - } - - if (!prompts.has(flag.name)) { - throw new SoloError(`No prompt available for flag: ${flag.name}`); - } - - const prompt = prompts.get(flag.name) as UserPrompt; - if (configManager.getFlag(flags.quiet)) { - return; - } - const input = await prompt(task, configManager.getFlag(flag)); - configManager.setFlag(flag, input); - } -} - -/** - * Disable prompts for the given set of flags - * @param flags list of flags to disable prompts for - */ -export function disablePrompts(flags: CommandFlag[]) { - resetDisabledPrompts(); - for (const flag of flags) { - if (flag.definition) { - flag.definition.disablePrompt = true; - } - } -} diff --git a/src/commands/relay.ts b/src/commands/relay.ts index 3ed4c99d5..e01fd4f48 100644 --- a/src/commands/relay.ts +++ b/src/commands/relay.ts @@ -20,8 +20,7 @@ import * as helpers from '../core/helpers.js'; import type {ProfileManager, AccountManager} from '../core/index.js'; import {constants} from '../core/index.js'; import {BaseCommand} from './base.js'; -import * as flags from './flags.js'; -import * as prompts from './prompts.js'; +import {flags} from './index.js'; import {getNodeAccountMap} from '../core/helpers.js'; import {RemoteConfigTasks} from '../core/config/remote/remote_config_tasks.js'; import {CommandBuilder, type NodeAliases} from '../types/aliases.js'; @@ -200,7 +199,7 @@ export class RelayCommand extends BaseCommand { self.configManager.update(argv); - await prompts.execute(task, self.configManager, RelayCommand.DEPLOY_FLAGS_LIST); + await flags.executePrompt(task, self.configManager, RelayCommand.DEPLOY_FLAGS_LIST); // prompt if inputs are empty and set it in the context ctx.config = this.getConfig(RelayCommand.DEPLOY_CONFIGS_NAME, RelayCommand.DEPLOY_FLAGS_LIST, [ @@ -326,7 +325,7 @@ export class RelayCommand extends BaseCommand { self.configManager.setFlag(flags.nodeAliasesUnparsed, ''); self.configManager.update(argv); - await prompts.execute(task, self.configManager, RelayCommand.DESTROY_FLAGS_LIST); + await flags.executePrompt(task, self.configManager, RelayCommand.DESTROY_FLAGS_LIST); // prompt if inputs are empty and set it in the context ctx.config = { diff --git a/src/core/config/local_config.ts b/src/core/config/local_config.ts index 1022328fe..6127ce928 100644 --- a/src/core/config/local_config.ts +++ b/src/core/config/local_config.ts @@ -21,7 +21,6 @@ import * as yaml from 'yaml'; import {flags} from '../../commands/index.js'; import {type Deployments, DeploymentStructure, type LocalConfigData} from './local_config_data.js'; import {MissingArgumentError, SoloError} from '../errors.js'; -import {promptDeploymentClusters, promptUserEmailAddress} from '../../commands/prompts.js'; import {type SoloLogger} from '../logging.js'; import {IsDeployments} from '../validator_decorators.js'; import type {ConfigManager} from '../config_manager.js'; @@ -145,13 +144,13 @@ export class LocalConfig implements LocalConfigData { skip: this.skipPromptTask, task: async (_: any, task: ListrTaskWrapper): Promise => { let userEmailAddress = self.configManager.getFlag(flags.userEmailAddress); - if (!userEmailAddress) userEmailAddress = await promptUserEmailAddress(task, userEmailAddress); + if (!userEmailAddress) userEmailAddress = await flags.userEmailAddress.prompt(task, userEmailAddress); const deploymentName = self.configManager.getFlag(flags.namespace); if (!deploymentName) throw new SoloError('Namespace was not specified'); let deploymentClusters = self.configManager.getFlag(flags.deploymentClusters); - if (!deploymentClusters) deploymentClusters = await promptDeploymentClusters(task, deploymentClusters); + if (!deploymentClusters) deploymentClusters = await flags.deploymentClusters.prompt(task, deploymentClusters); const deployments: Deployments = { [deploymentName]: {clusters: Templates.parseClusterAliases(deploymentClusters)}, diff --git a/src/core/config_manager.ts b/src/core/config_manager.ts index 2cfdff707..fd9c7820f 100644 --- a/src/core/config_manager.ts +++ b/src/core/config_manager.ts @@ -16,7 +16,7 @@ */ import {SoloError, MissingArgumentError} from './errors.js'; import {SoloLogger} from './logging.js'; -import * as flags from '../commands/flags.js'; +import {flags} from '../commands/index.js'; import * as paths from 'path'; import * as helpers from './helpers.js'; import type * as yargs from 'yargs'; diff --git a/src/core/yargs_command.ts b/src/core/yargs_command.ts index 71ab53556..26852aad9 100644 --- a/src/core/yargs_command.ts +++ b/src/core/yargs_command.ts @@ -14,7 +14,7 @@ * limitations under the License. * */ -import * as commandFlags from '../commands/flags.js'; +import {Flags as commandFlags} from '../commands/flags.js'; import {IllegalArgumentError} from './errors.js'; import {type BaseCommand} from '../commands/base.js'; import {type CommandFlag} from '../types/index.js'; diff --git a/src/types/aliases.ts b/src/types/aliases.ts index 94d0c9ce1..b3ea7fe9b 100644 --- a/src/types/aliases.ts +++ b/src/types/aliases.ts @@ -31,3 +31,4 @@ export type TaskFunction = ( task: ListrTaskWrapper, ) => Promise> | Listr | Promise | void; export type ConfigBuilder = (argv, ctx, task) => Promise; +export type PromptFunction = (task: ListrTaskWrapper, input: any) => Promise; diff --git a/src/types/index.ts b/src/types/index.ts index 1a5076b22..9255a8642 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -35,9 +35,10 @@ import type { RemoteConfigManager, LocalConfig, } from '../core/index.js'; -import {Cluster, Context} from '../core/config/remote/types.js'; -import {type BaseCommand} from '../commands/base.js'; +import type {Cluster, Context} from '../core/config/remote/types.js'; +import type {BaseCommand} from '../commands/base.js'; import type {ListrTask} from 'listr2'; +import type {PromptFunction} from './aliases.js'; export interface NodeKeyObject { privateKey: crypto.webcrypto.CryptoKey; @@ -71,6 +72,7 @@ export interface CommandFlag { constName: string; name: string; definition: Definition; + prompt: PromptFunction; } export interface Definition { diff --git a/test/unit/commands/base.test.ts b/test/unit/commands/base.test.ts index 94ba68e8a..432d582db 100644 --- a/test/unit/commands/base.test.ts +++ b/test/unit/commands/base.test.ts @@ -30,7 +30,7 @@ import { RemoteConfigManager, } from '../../../src/core/index.js'; import {BaseCommand} from '../../../src/commands/base.js'; -import * as flags from '../../../src/commands/flags.js'; +import {flags} from '../../../src/commands/index.js'; import sinon from 'sinon'; import path from 'path'; import {BASE_TEST_DIR} from '../../test_util.js'; diff --git a/test/unit/commands/context.test.ts b/test/unit/commands/context.test.ts index f9da88c35..3362cdb21 100644 --- a/test/unit/commands/context.test.ts +++ b/test/unit/commands/context.test.ts @@ -39,26 +39,24 @@ import {getTestCacheDir, testLocalConfigData} from '../../test_util.js'; import {BaseCommand} from '../../../src/commands/base.js'; import {flags} from '../../../src/commands/index.js'; import {SoloLogger} from '../../../src/core/logging.js'; -import type Sinon from 'sinon'; import {type Opts} from '../../../src/types/index.js'; import fs from 'fs'; import {stringify} from 'yaml'; import {type Cluster, KubeConfig} from '@kubernetes/client-node'; -import {UserPrompt} from '../../../src/types/aliases.js'; import {ListrTaskWrapper} from 'listr2'; describe('ContextCommandTasks unit tests', () => { const filePath = `${getTestCacheDir('ContextCommandTasks')}/localConfig.yaml`; - const getBaseCommandOpts = () => { - const loggerStub = sinon.createStubInstance(SoloLogger); - const k8Stub = sinon.createStubInstance(K8); + const getBaseCommandOpts = (sandbox: sinon.SinonSandbox) => { + const loggerStub = sandbox.createStubInstance(SoloLogger); + const k8Stub = sandbox.createStubInstance(K8); k8Stub.getContexts.returns([ {cluster: 'cluster-1', user: 'user-1', name: 'context-1', namespace: 'deployment-1'}, {cluster: 'cluster-2', user: 'user-2', name: 'context-2', namespace: 'deployment-2'}, {cluster: 'cluster-3', user: 'user-3', name: 'context-3', namespace: 'deployment-3'}, ]); - const kubeConfigStub = sinon.createStubInstance(KubeConfig); + const kubeConfigStub = sandbox.createStubInstance(KubeConfig); kubeConfigStub.getCurrentContext.returns('context-3'); kubeConfigStub.getCurrentContext.returns('context-3'); kubeConfigStub.getCurrentCluster.returns({ @@ -72,77 +70,69 @@ describe('ContextCommandTasks unit tests', () => { k8Stub.getKubeConfig.returns(kubeConfigStub); - const configManager = sinon.createStubInstance(ConfigManager); + const configManager = sandbox.createStubInstance(ConfigManager); return { logger: loggerStub, - helm: sinon.createStubInstance(Helm), + helm: sandbox.createStubInstance(Helm), k8: k8Stub, - chartManager: sinon.createStubInstance(ChartManager), + chartManager: sandbox.createStubInstance(ChartManager), configManager, - depManager: sinon.createStubInstance(DependencyManager), + depManager: sandbox.createStubInstance(DependencyManager), localConfig: new LocalConfig(filePath, loggerStub, configManager), - downloader: sinon.createStubInstance(PackageDownloader), - keyManager: sinon.createStubInstance(KeyManager), - accountManager: sinon.createStubInstance(AccountManager), - platformInstaller: sinon.createStubInstance(PlatformInstaller), - profileManager: sinon.createStubInstance(ProfileManager), - leaseManager: sinon.createStubInstance(LeaseManager), - certificateManager: sinon.createStubInstance(CertificateManager), - remoteConfigManager: sinon.createStubInstance(RemoteConfigManager), + downloader: sandbox.createStubInstance(PackageDownloader), + keyManager: sandbox.createStubInstance(KeyManager), + accountManager: sandbox.createStubInstance(AccountManager), + platformInstaller: sandbox.createStubInstance(PlatformInstaller), + profileManager: sandbox.createStubInstance(ProfileManager), + leaseManager: sandbox.createStubInstance(LeaseManager), + certificateManager: sandbox.createStubInstance(CertificateManager), + remoteConfigManager: sandbox.createStubInstance(RemoteConfigManager), } as Opts; }; describe('updateLocalConfig', () => { + const sandbox = sinon.createSandbox(); + let namespacePromptStub: sinon.SinonStub; + let clusterNamePromptStub: sinon.SinonStub; + let contextPromptStub: sinon.SinonStub; let tasks: ContextCommandTasks; let command: BaseCommand; - let loggerStub: Sinon.SinonStubbedInstance; + let loggerStub: sinon.SinonStubbedInstance; let localConfig: LocalConfig; - let promptMap: Map; async function runUpdateLocalConfigTask(argv) { const taskObj = tasks.updateLocalConfig(argv); - return taskObj.task({}, sinon.stub() as unknown as ListrTaskWrapper); - } - - function getPromptMap(): Map { - return new Map() - .set( - flags.namespace.name, - sinon.stub().callsFake(() => { - return new Promise(resolve => { - resolve('deployment-3'); - }); - }), - ) - .set( - flags.clusterName.name, - sinon.stub().callsFake(() => { - return new Promise(resolve => { - resolve('cluster-3'); - }); - }), - ) - .set( - flags.context.name, - sinon.stub().callsFake(() => { - return new Promise(resolve => { - resolve('context-3'); - }); - }), - ); + return taskObj.task({}, sandbox.stub() as unknown as ListrTaskWrapper); } afterEach(async () => { await fs.promises.unlink(filePath); + sandbox.restore(); }); + after(() => {}); + beforeEach(async () => { - promptMap = getPromptMap(); - loggerStub = sinon.createStubInstance(SoloLogger); + namespacePromptStub = sandbox.stub(flags.namespace, 'prompt').callsFake(() => { + return new Promise(resolve => { + resolve('deployment-3'); + }); + }); + clusterNamePromptStub = sandbox.stub(flags.clusterName, 'prompt').callsFake(() => { + return new Promise(resolve => { + resolve('cluster-3'); + }); + }); + contextPromptStub = sandbox.stub(flags.context, 'prompt').callsFake(() => { + return new Promise(resolve => { + resolve('context-3'); + }); + }); + loggerStub = sandbox.createStubInstance(SoloLogger); await fs.promises.writeFile(filePath, stringify(testLocalConfigData)); - command = new BaseCommand(getBaseCommandOpts()); - tasks = new ContextCommandTasks(command, promptMap); + command = new BaseCommand(getBaseCommandOpts(sandbox)); + tasks = new ContextCommandTasks(command); }); it('should update local configuration with provided values', async () => { @@ -168,9 +158,9 @@ describe('ContextCommandTasks unit tests', () => { expect(localConfig.currentDeploymentName).to.equal('deployment-3'); expect(localConfig.getCurrentDeployment().clusters).to.deep.equal(['cluster-3']); expect(command.getK8().getKubeConfig().setCurrentContext).to.have.been.calledWith('context-3'); - expect(promptMap.get(flags.namespace.name)).to.have.been.calledOnce; - expect(promptMap.get(flags.clusterName.name)).to.have.been.calledOnce; - expect(promptMap.get(flags.context.name)).to.have.been.calledOnce; + expect(namespacePromptStub).to.have.been.calledOnce; + expect(clusterNamePromptStub).to.have.been.calledOnce; + expect(contextPromptStub).to.have.been.calledOnce; }); it('should prompt for namespace if no value is provided', async () => { @@ -185,9 +175,9 @@ describe('ContextCommandTasks unit tests', () => { expect(localConfig.currentDeploymentName).to.equal('deployment-3'); expect(localConfig.getCurrentDeployment().clusters).to.deep.equal(['cluster-2']); expect(command.getK8().getKubeConfig().setCurrentContext).to.have.been.calledWith('context-2'); - expect(promptMap.get(flags.namespace.name)).to.have.been.calledOnce; - expect(promptMap.get(flags.clusterName.name)).to.not.have.been.called; - expect(promptMap.get(flags.context.name)).to.not.have.been.called; + expect(namespacePromptStub).to.have.been.calledOnce; + expect(clusterNamePromptStub).to.have.been.not.called; + expect(contextPromptStub).to.have.been.not.called; }); it('should prompt for cluster if no value is provided', async () => { @@ -202,9 +192,9 @@ describe('ContextCommandTasks unit tests', () => { expect(localConfig.currentDeploymentName).to.equal('deployment-2'); expect(localConfig.getCurrentDeployment().clusters).to.deep.equal(['cluster-3']); expect(command.getK8().getKubeConfig().setCurrentContext).to.have.been.calledWith('context-2'); - expect(promptMap.get(flags.namespace.name)).to.not.have.been.called; - expect(promptMap.get(flags.clusterName.name)).to.have.been.calledOnce; - expect(promptMap.get(flags.context.name)).to.not.have.been.called; + expect(namespacePromptStub).to.have.been.not.called; + expect(clusterNamePromptStub).to.have.been.calledOnce; + expect(contextPromptStub).to.have.been.not.called; }); it('should prompt for context if no value is provided', async () => { @@ -219,9 +209,9 @@ describe('ContextCommandTasks unit tests', () => { expect(localConfig.currentDeploymentName).to.equal('deployment-2'); expect(localConfig.getCurrentDeployment().clusters).to.deep.equal(['cluster-2']); expect(command.getK8().getKubeConfig().setCurrentContext).to.have.been.calledWith('context-3'); - expect(promptMap.get(flags.namespace.name)).to.not.have.been.called; - expect(promptMap.get(flags.clusterName.name)).to.not.have.been.called; - expect(promptMap.get(flags.context.name)).to.have.been.calledOnce; + expect(namespacePromptStub).to.have.been.not.called; + expect(clusterNamePromptStub).to.have.been.not.called; + expect(contextPromptStub).to.have.been.calledOnce; }); it('should use cluster from kubectl if no value is provided and quiet=true', async () => { @@ -237,9 +227,9 @@ describe('ContextCommandTasks unit tests', () => { expect(localConfig.currentDeploymentName).to.equal('deployment-2'); expect(localConfig.getCurrentDeployment().clusters).to.deep.equal(['cluster-3']); expect(command.getK8().getKubeConfig().setCurrentContext).to.have.been.calledWith('context-2'); - expect(promptMap.get(flags.namespace.name)).to.not.have.been.called; - expect(promptMap.get(flags.clusterName.name)).to.not.have.been.called; - expect(promptMap.get(flags.context.name)).to.not.have.been.called; + expect(namespacePromptStub).to.have.been.not.called; + expect(clusterNamePromptStub).to.have.been.not.called; + expect(contextPromptStub).to.have.been.not.called; }); it('should use namespace from kubectl if no value is provided and quiet=true', async () => { @@ -255,9 +245,9 @@ describe('ContextCommandTasks unit tests', () => { expect(localConfig.currentDeploymentName).to.equal('deployment-2'); expect(localConfig.getCurrentDeployment().clusters).to.deep.equal(['cluster-2']); expect(command.getK8().getKubeConfig().setCurrentContext).to.have.been.calledWith('context-2'); - expect(promptMap.get(flags.namespace.name)).to.not.have.been.called; - expect(promptMap.get(flags.clusterName.name)).to.not.have.been.called; - expect(promptMap.get(flags.context.name)).to.not.have.been.called; + expect(namespacePromptStub).to.have.been.not.called; + expect(clusterNamePromptStub).to.have.been.not.called; + expect(contextPromptStub).to.have.been.not.called; }); }); }); diff --git a/test/unit/core/config_manager.test.ts b/test/unit/core/config_manager.test.ts index 652c33195..94c141693 100644 --- a/test/unit/core/config_manager.test.ts +++ b/test/unit/core/config_manager.test.ts @@ -18,7 +18,7 @@ import {expect} from 'chai'; import {describe, it} from 'mocha'; import {ConfigManager} from '../../../src/core/index.js'; -import * as flags from '../../../src/commands/flags.js'; +import {flags} from '../../../src/commands/index.js'; import {testLogger} from '../../test_util.js'; describe('ConfigManager', () => {