diff --git a/cli/package.json b/cli/package.json index a7f2798..a841932 100644 --- a/cli/package.json +++ b/cli/package.json @@ -1,6 +1,6 @@ { "name": "@gfusee/xnetwork", - "version": "0.0.2", + "version": "0.1.1", "description": "An all-in-one tool for creating and managing your own MultiversX network", "type": "module", "scripts": { diff --git a/cli/src/index.ts b/cli/src/index.ts index 89bd445..6a2c32b 100644 --- a/cli/src/index.ts +++ b/cli/src/index.ts @@ -1,38 +1,58 @@ #!/usr/bin/env node -import {getDefaultConfig} from "./config/config.js" -import {DockerPrerequisites} from "./prerequisites/dockerPrerequisites.js" +import {CLIConfig, getDefaultConfig} from "./config/config.js" import {dontIndent} from "./utils/strings/dontIndent.js"; import chalk from "chalk"; -import {GitRepoPrerequisites} from "./prerequisites/gitRepoPrerequisites.js" import { program } from 'commander' import {StartQuestion} from "./questions/startQuestion.js"; +import fs from "fs/promises"; +import {checkPrerequisites} from "./utils/host/checkPrerequisites.js"; +import {createNetwork} from "./utils/docker/createNetwork.js"; +import {removeExistingNetwork} from "./utils/docker/removeExistingNetwork.js"; +import {readLatestConfig} from "./utils/config/readLatestConfig.js"; async function main() { - program + const defaultCommand = program .name('xnetwork') .version('0.0.1') .description('An all-in-one tool for creating and managing your own MultiversX network') .option('--no-cache', 'Do not use cache when downloading files') .option('--custom-repo-path ', 'Fetch files from a custom repository') .option('--custom-repo-branch ', 'Fetch files from a custom repository branch') + .action(startInteractiveSetup) - await program.parse(process.argv) + defaultCommand + .command('create') + .description('Create a new network, non-interactive') + .argument('', 'Path to the config file') + .action((configPath) => {createNetworkAction(configPath)}) - console.log('Checking prerequisites...') + defaultCommand + .command('remove') + .description('Remove a network, non-interactive') + .action(removeNetworkAction) - try { - await (new DockerPrerequisites()).check() - await (new GitRepoPrerequisites()).check() - } catch (e) { - if (typeof e === 'string') { - console.log(e) - process.exit(1) - } else { - throw e - } - } + const configCommand = defaultCommand + .command('config') + .description('Config utils') + + configCommand + .command('generate') + .description('Generate a config file') + .argument('', 'Path to the output file') + .action((outputPath) => {generateConfigAction(outputPath)}) + + configCommand + .command('latest') + .description('Get the latest config file used to create a network, if exists') + .action(getLatestConfigAction) + + await program.parse(process.argv) +} + +async function startInteractiveSetup() { + await checkPrerequisites() const welcomeMessage = ` Welcome to ${chalk.bold('xNetwork')} CLI! 🔥 @@ -47,4 +67,35 @@ async function main() { await (new StartQuestion()).process(config) } -main().then(() => console.log('\nDone')) +async function createNetworkAction(configPath: string) { + await checkPrerequisites() + + const configRaw = await fs.readFile(configPath, 'utf-8') + const config = JSON.parse(configRaw) as CLIConfig + + await createNetwork(config) +} + +async function removeNetworkAction() { + await checkPrerequisites() + + await removeExistingNetwork() +} + +async function generateConfigAction(outputPath: string) { + const config = getDefaultConfig() + const configString = JSON.stringify(config, null, 4) + + await fs.writeFile(outputPath, configString) +} + +async function getLatestConfigAction() { + try { + const latestConfig = await readLatestConfig() + console.log(JSON.stringify(latestConfig, null, 4)) + } catch (e) { + console.log('No latest config found') + } +} + +main().then(() => console.log('Done')) diff --git a/cli/src/questions/runner/runnerQuestion.ts b/cli/src/questions/runner/runnerQuestion.ts index 35cbcf1..e45ec13 100644 --- a/cli/src/questions/runner/runnerQuestion.ts +++ b/cli/src/questions/runner/runnerQuestion.ts @@ -1,15 +1,7 @@ import {CLIQuestion} from "../question.js" import {Answers, ListQuestion, Question} from "inquirer" import {CLIConfig} from "../../config/config.js" -import {execCustomInRepo, ExecError} from "../../utils/exec.js" -import ora from "ora" -import {waitForVMQueryToBeReady} from "../../utils/healthchecks/waitForVMQueryToBeReady.js"; -import {waitForAPIToBeReady} from "../../utils/healthchecks/waitForAPIToBeReady.js"; -import {removeExistingNetwork} from "../../utils/docker/removeExistingNetwork.js"; -import {ResultLogger} from "../../result/resultLogger.js"; -import {saveLatestConfig} from "../../utils/config/saveLatestConfig.js"; -import {upContainer} from "../../utils/docker/upContainer.js"; -import {Constants} from "../../config/constants.js"; +import {createNetwork} from "../../utils/docker/createNetwork.js"; export class RunnerQuestion extends CLIQuestion { @@ -29,88 +21,9 @@ export class RunnerQuestion extends CLIQuestion { override async handleAnswer(answers: Answers, config: CLIConfig): Promise { if (answers.choice === RunnerQuestion.yesChoice) { - await this.run(config) + await createNetwork(config) } return [] } - - private async run(config: CLIConfig) { - try { - await removeExistingNetwork() - - if (config.shouldHaveElasticSearch) { - const startingElasticSearchSpinner = ora('Starting ElasticSearch container...').start() - await upContainer(Constants.ELASTIC_CONTAINER.name) - startingElasticSearchSpinner.succeed('Started ElasticSearch container') - } - - if (config.shouldHaveMySQL) { - const startingMySQLSpinner = ora('Starting MySQL container...').start() - await upContainer(Constants.MYSQL_CONTAINER.name) - startingMySQLSpinner.succeed('Started MySQL container') - } - - if (config.shouldHaveRedis) { - const startingRedisSpinner = ora('Starting Redis container...').start() - await upContainer(Constants.REDIS_CONTAINER.name) - startingRedisSpinner.succeed('Started Redis container') - } - - if (config.shouldHaveRabbitMQ) { - const startingRabbitMQSpinner = ora('Starting RabbitMQ container...').start() - await upContainer(Constants.RABBITMQ_CONTAINER.name) - startingRabbitMQSpinner.succeed('Started RabbitMQ container') - } - - const startingNetworkSpinner = ora('Starting network...').start() - await upContainer(Constants.TESTNET_CONTAINER.name, { - ...process.env, - "MX_LT_NUM_SHARDS": config.numberOfShards.toString(), - "MX_LT_ELASTIC_ENABLED": config.shouldHaveElasticSearch.toString(), - "MX_LT_CUSTOM_EGLD_ADDRESS": config.initialEGLDAddress ?? "" - }) - startingNetworkSpinner.succeed('Started network successfully') - - await saveLatestConfig(config) - - await waitForVMQueryToBeReady() - - if (config.shouldHaveApi) { - const startingApiSpinner = ora('Starting API container...').start() - await upContainer(Constants.API_CONTAINER.name) - startingApiSpinner.succeed('Started API container') - - const startingApiHealthCheckSpinner = ora('Waiting for API to be ready').start() - await waitForAPIToBeReady() - startingApiHealthCheckSpinner.succeed('API is ready') - } - - if (config.mxOpsScenesPath) { - const copyingScenesSpinner = ora('Copying mxops scenes...').start() - await execCustomInRepo(`docker-compose cp ${config.mxOpsScenesPath} testnet:/home/ubuntu/mxops`) - copyingScenesSpinner.succeed('Copied mxops scenes') - - const runningScenesSpinner = ora('Running mxops scenes...').start() - await execCustomInRepo(`docker-compose exec testnet python3 run_mxops.py`) - runningScenesSpinner.succeed('Ran mxops scenes') - } - - const resultLogger = new ResultLogger() - await resultLogger.printResults(config) - } catch (e) { - try { - const error = e as ExecError - console.log("Error while running network...") - console.log("Command : ", error.error.cmd) - console.log("Exit code : ", error.error.code) - console.log("Message : ", error.stderr) - } catch (e2) { - console.log("Error while running network...") - console.log(e) - } - - throw e - } - } } diff --git a/cli/src/utils/docker/createNetwork.ts b/cli/src/utils/docker/createNetwork.ts new file mode 100644 index 0000000..5180fec --- /dev/null +++ b/cli/src/utils/docker/createNetwork.ts @@ -0,0 +1,89 @@ +import {CLIConfig} from "../../config/config.js"; +import {removeExistingNetwork} from "./removeExistingNetwork.js"; +import ora from "ora"; +import {upContainer} from "./upContainer.js"; +import {Constants} from "../../config/constants.js"; +import {saveLatestConfig} from "../config/saveLatestConfig.js"; +import {waitForVMQueryToBeReady} from "../healthchecks/waitForVMQueryToBeReady.js"; +import {waitForAPIToBeReady} from "../healthchecks/waitForAPIToBeReady.js"; +import {execCustomInRepo, ExecError} from "../exec.js"; +import {ResultLogger} from "../../result/resultLogger.js"; + +export async function createNetwork(config: CLIConfig) { + try { + await removeExistingNetwork() + + if (config.shouldHaveElasticSearch) { + const startingElasticSearchSpinner = ora('Starting ElasticSearch container...').start() + await upContainer(Constants.ELASTIC_CONTAINER.name) + startingElasticSearchSpinner.succeed('Started ElasticSearch container') + } + + if (config.shouldHaveMySQL) { + const startingMySQLSpinner = ora('Starting MySQL container...').start() + await upContainer(Constants.MYSQL_CONTAINER.name) + startingMySQLSpinner.succeed('Started MySQL container') + } + + if (config.shouldHaveRedis) { + const startingRedisSpinner = ora('Starting Redis container...').start() + await upContainer(Constants.REDIS_CONTAINER.name) + startingRedisSpinner.succeed('Started Redis container') + } + + if (config.shouldHaveRabbitMQ) { + const startingRabbitMQSpinner = ora('Starting RabbitMQ container...').start() + await upContainer(Constants.RABBITMQ_CONTAINER.name) + startingRabbitMQSpinner.succeed('Started RabbitMQ container') + } + + const startingNetworkSpinner = ora('Starting network...').start() + await upContainer(Constants.TESTNET_CONTAINER.name, { + ...process.env, + "MX_LT_NUM_SHARDS": config.numberOfShards.toString(), + "MX_LT_ELASTIC_ENABLED": config.shouldHaveElasticSearch.toString(), + "MX_LT_CUSTOM_EGLD_ADDRESS": config.initialEGLDAddress ?? "" + }) + startingNetworkSpinner.succeed('Started network successfully') + + await saveLatestConfig(config) + + await waitForVMQueryToBeReady() + + if (config.shouldHaveApi) { + const startingApiSpinner = ora('Starting API container...').start() + await upContainer(Constants.API_CONTAINER.name) + startingApiSpinner.succeed('Started API container') + + const startingApiHealthCheckSpinner = ora('Waiting for API to be ready').start() + await waitForAPIToBeReady() + startingApiHealthCheckSpinner.succeed('API is ready') + } + + if (config.mxOpsScenesPath) { + const copyingScenesSpinner = ora('Copying mxops scenes...').start() + await execCustomInRepo(`docker-compose cp ${config.mxOpsScenesPath} testnet:/home/ubuntu/mxops`) + copyingScenesSpinner.succeed('Copied mxops scenes') + + const runningScenesSpinner = ora('Running mxops scenes...').start() + await execCustomInRepo(`docker-compose exec testnet python3 run_mxops.py`) + runningScenesSpinner.succeed('Ran mxops scenes') + } + + const resultLogger = new ResultLogger() + await resultLogger.printResults(config) + } catch (e) { + try { + const error = e as ExecError + console.log("Error while running network...") + console.log("Command : ", error.error.cmd) + console.log("Exit code : ", error.error.code) + console.log("Message : ", error.stderr) + } catch (e2) { + console.log("Error while running network...") + console.log(e) + } + + throw e + } +} diff --git a/cli/src/utils/host/checkPrerequisites.ts b/cli/src/utils/host/checkPrerequisites.ts new file mode 100644 index 0000000..dcd619d --- /dev/null +++ b/cli/src/utils/host/checkPrerequisites.ts @@ -0,0 +1,18 @@ +import {DockerPrerequisites} from "../../prerequisites/dockerPrerequisites.js"; +import {GitRepoPrerequisites} from "../../prerequisites/gitRepoPrerequisites.js"; + +export async function checkPrerequisites() { + console.log('Checking prerequisites...') + + try { + await (new DockerPrerequisites()).check() + await (new GitRepoPrerequisites()).check() + } catch (e) { + if (typeof e === 'string') { + console.log(e) + process.exit(1) + } else { + throw e + } + } +}