From 486b9bd2b0f7785e14295535cb3d98bd78de6660 Mon Sep 17 00:00:00 2001 From: Kirk Haines Date: Wed, 16 Aug 2023 19:52:22 -0600 Subject: [PATCH 01/37] WIP --- errors.notes | 2 + package-lock.json | 12 +-- package.json | 2 +- src/app.module.ts | 5 +- src/commands/clean.command.ts | 122 ++++++++++++++---------- src/commands/newRoot.command.ts | 55 +++++++++++ src/commands/start.command.ts | 159 ++++++++++++++++---------------- src/commands/version.command.ts | 24 +++++ src/globals.ts | 49 ++++++++++ src/loggers.ts | 31 +++++++ src/main.ts | 3 +- src/utility.ts | 63 +++++++++++++ 12 files changed, 385 insertions(+), 142 deletions(-) create mode 100644 errors.notes create mode 100644 src/commands/newRoot.command.ts create mode 100644 src/commands/version.command.ts create mode 100644 src/globals.ts create mode 100644 src/utility.ts diff --git a/errors.notes b/errors.notes new file mode 100644 index 0000000..8787922 --- /dev/null +++ b/errors.notes @@ -0,0 +1,2 @@ +error: docker: Error response from daemon: driver failed programming external connectivity on endpoint redis-stack-server (662867da5745dd786f75213af8f91365caab5c5ecd38370fcd4c1a8542c5432c): Error starting userland proxy: listen tcp4 0.0.0.0:6379: bind: address already in use. + diff --git a/package-lock.json b/package-lock.json index 9941d48..378aad4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,13 +1,13 @@ { - "name": "topos-playground", - "version": "0.0.1", + "name": "@topos-protocol/topos-playground", + "version": "0.0.2", "lockfileVersion": 2, "requires": true, "packages": { "": { - "name": "topos-playground", - "version": "0.0.1", - "license": "UNLICENSED", + "name": "@topos-protocol/topos-playground", + "version": "0.0.2", + "license": "MIT", "dependencies": { "@nestjs/common": "^9.0.0", "@nestjs/core": "^9.0.0", @@ -16,7 +16,7 @@ "winston": "^3.8.2" }, "bin": { - "topos-playground": "dist/main" + "topos-playground": "dist/main.js" }, "devDependencies": { "@nestjs/cli": "^9.0.0", diff --git a/package.json b/package.json index a20f6e9..838093e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@topos-protocol/topos-playground", "version": "0.0.2", - "description": "CLI to run local Topos devnets with subnets, a TCE network, and apps", + "description": "topos-playground is a CLI tool which handles all of the orchestration necessary to run local Topos devnets with subnets, a TCE network, and apps.", "author": "SΓ©bastien Dan ", "license": "MIT", "bin": { diff --git a/src/app.module.ts b/src/app.module.ts index b69c34e..9d0fb2f 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -2,9 +2,12 @@ import { Module } from '@nestjs/common' import { CleanCommand } from './commands/clean.command' import { StartCommand } from './commands/start.command' +import { VersionCommand } from './commands/version.command' +import { NewRootCommand } from './commands/newRoot.command' + import { ReactiveSpawn } from './ReactiveSpawn' @Module({ - providers: [ReactiveSpawn, CleanCommand, StartCommand], + providers: [ReactiveSpawn, CleanCommand, StartCommand, VersionCommand, NewRootCommand], }) export class AppModule {} diff --git a/src/commands/clean.command.ts b/src/commands/clean.command.ts index 5563f7d..df4420d 100644 --- a/src/commands/clean.command.ts +++ b/src/commands/clean.command.ts @@ -1,52 +1,44 @@ import { stat } from 'fs' +import { join } from 'path' + import { Command, CommandRunner } from 'nest-commander' import { concatAll } from 'rxjs/operators' import { defer, EMPTY, Observable, of } from 'rxjs' import { homedir } from 'os' import { Next, ReactiveSpawn } from '../ReactiveSpawn' -import { workingDir } from 'src/constants' -import { createLoggerFile, loggerConsole } from 'src/loggers' -import { randomUUID } from 'crypto' +import { log, logError } from 'src/loggers' @Command({ name: 'clean', description: 'Clean artifacts from a previous start', }) export class CleanCommand extends CommandRunner { - private _workingDir = workingDir - private _loggerConsole = loggerConsole - private _logFilePath = `logs/log-${randomUUID()}.log` - private _loggerFile = createLoggerFile(this._logFilePath) - constructor(private _spawn: ReactiveSpawn) { super() } async run(): Promise { - this._log(`Welcome to Topos-Playground!`) - this._log(``) + log(`Cleaning up Topos-Playground!`) + log(``) of( + this._verifyExecutionPathExistence(), this._verifyWorkingDirectoryExistence(), this._shutdownFullMsgProtocolInfra(), this._shutdownRedis(), - this._removeWorkingDir() + this._removeworkingDir() ) .pipe(concatAll()) .subscribe({ complete: () => { - this._log(`🧹 Everything is clean! 🧹`) - this._log(`Logs were written to ${this._logFilePath}`) + log(`🧹 Everything is clean! 🧹`) + log(`Logs were written to ${globalThis.logFilePath}`) }, error: () => {}, next: (data: Next) => { - if (data && data.hasOwnProperty('origin')) { - if (data.origin === 'stderr') { - this._loggerFile.error(data.output) - } else if (data.origin === 'stdout') { - this._loggerFile.info(data.output) - } + if (data && data.hasOwnProperty('output')) { + log(`${data.output}`) } }, }) @@ -54,13 +46,31 @@ export class CleanCommand extends CommandRunner { private _verifyWorkingDirectoryExistence() { return new Observable((subscriber) => { - stat(this._workingDir, (error) => { + stat(globalThis.workingDir, (error) => { + console.log("verify working directory existence") if (error) { - this._logError( - `Working directory have not been found, nothing to clean!` + logError( + `The working directory (${globalThis.workingDir}) can not been found; nothing to clean!` ) subscriber.error() } else { + log(`Cleaning working directory (${globalThis.workingDir})...`) + subscriber.complete() + } + }) + }) + } + + private _verifyExecutionPathExistence() { + console.log(`verify execution path ${globalThis.executionPath}`) + return new Observable((subscriber) => { + stat(globalThis.executionPath, (error) => { + console.log("verify execution path existence") + if (error) { + globalThis.executionPath_exists = false + subscriber.complete() + } else { + globalThis.executionPath_exists = true subscriber.complete() } }) @@ -68,38 +78,58 @@ export class CleanCommand extends CommandRunner { } private _shutdownFullMsgProtocolInfra() { - const executionPath = `${this._workingDir}/local-erc20-messaging-infra` + console.log("do shutdown stuff") - return of( - defer(() => of(this._log(`Shutting down the ERC20 messaging infra...`))), - this._spawn.reactify(`cd ${executionPath} && docker compose down -v`), - defer(() => of(this._log(`βœ… subnets & TCE are down`), this._log(``))) + console.log("do return blah blah shutoff") + return of( + defer(() => of(console.log("A*"))), + defer(() => of(!globalThis.executionPath_exists ? log(`βœ… ERC20 messaging infra is not running; subnets & TCE are down`) : null)), + defer(() => of(globalThis.executionPath_exists ? log(`Shutting down the ERC20 messaging infra...`) : null)), + defer(() => this._spawn.reactify(`cd ${globalThis.executionPath} && docker compose down -v`)), + defer(() => of(log(`βœ… subnets & TCE are down`), log(``))) ).pipe(concatAll()) } private _shutdownRedis() { const containerName = 'redis-stack-server' + let container_running = false return of( - defer(() => of(this._log(`Shutting down the redis server...`))), - this._spawn.reactify(`docker rm -f ${containerName}`), - defer(() => of(this._log(`βœ… redis is down`), this._log(``))) - ).pipe(concatAll()) + new Observable((subscriber) => { + this._spawn.reactify(`docker ps --format '{{.Names}}' | grep ${containerName}`).subscribe({ + next: (data: Next) => { + if (data && data.output && `${data.output}`.indexOf(containerName) !== -1) { + container_running = true + } + }, + error: () => { subscriber.error() }, + complete: () => { subscriber.complete() } + }) + }), + defer(() => of(log(`Container running: ${container_running}`))), + defer(() => of(log(container_running ? `βœ… redis is not running; nothing to shut down` : `Shutting down the redis server...`))), + new Observable((subscriber) => { + this._spawn.reactify(`docker rm -f ${containerName}`).subscribe({ + next: (data: Next) => { subscriber.next(data) }, + error: (data) => { subscriber.error(data) }, + complete: () => { subscriber.complete() } + }) + }), + defer(() => of(log(container_running ? `βœ… redis is down\n` : ``)))).pipe(concatAll()) } - private _removeWorkingDir() { + private _removeworkingDir() { const homeDir = homedir() return of( - defer(() => of(this._log(`Removing the working directory...`))), - // Let's make sure we're not removing something we shouldn't - this._workingDir.indexOf(homeDir) !== -1 && this._workingDir !== homeDir + defer(() => of(log(`Removing the working directory...`))), + globalThis.workingDir.indexOf(homeDir) !== -1 && globalThis.workingDir !== homeDir ? of( - this._spawn.reactify(`rm -rf ${this._workingDir}`), + this._spawn.reactify(`rm -rf ${globalThis.workingDir}`), defer(() => of( - this._log('βœ… Working directory has been removed'), - this._log(``) + log('βœ… Working directory has been removed'), + log(``) ) ) ).pipe(concatAll()) @@ -107,24 +137,14 @@ export class CleanCommand extends CommandRunner { EMPTY, defer(() => of( - this._logError( - `Working directory (${this._workingDir}) is not safe for removal!` + logError( + `Working directory (${globalThis.workingDir}) is not safe for removal!` ), - this._log(``) + log(``) ) ) ).pipe(concatAll()) ).pipe(concatAll()) } - private _log(logMessage: string) { - this._loggerConsole.info(logMessage) - this._loggerFile.info(logMessage) - } - - private _logError(errorMessage: string) { - this._loggerConsole.error(errorMessage) - this._loggerFile.error(errorMessage) - this._loggerConsole.error(`πŸ‘‰ Find more details in ${this._logFilePath}`) - } } diff --git a/src/commands/newRoot.command.ts b/src/commands/newRoot.command.ts new file mode 100644 index 0000000..776979d --- /dev/null +++ b/src/commands/newRoot.command.ts @@ -0,0 +1,55 @@ +const { version, description } = require('../../package.json'); + +import { breakText } from 'src/utility' +import { RootCommand, Option, CommandRunner } from 'nest-commander' + +import { ReactiveSpawn } from '../ReactiveSpawn' +import { log } from 'src/loggers' + +@RootCommand({ + description: `${breakText(description)}\n`, +}) + +export class NewRootCommand extends CommandRunner { + constructor(private _spawn: ReactiveSpawn) { + + super() + } + + async run(): Promise { + } + + @Option({ + flags: '--version', + description: `Show topos-playground version (v${version})`, + }) + doVersion() { + log((globalThis.quiet ? '' : 'topos-playground version ') + version, true) + log(``) + } + + @Option({ + flags: '-v, --verbose', + description: breakText(`Show more information about the execution of a command`, 39), + }) + doVerbose() { + globalThis.verbose = true + } + + @Option({ + flags: '-q, --quiet', + description: breakText(`Show minimal onscreen information about the execution of a command`, 39), + }) + doQuiet() { + globalThis.quiet = true + } + + @Option({ + flags: '-n, --no-log', + description: `Do not write a log file`, + }) + doNoLog() { + globalThis.no_log = true + } + +} diff --git a/src/commands/start.command.ts b/src/commands/start.command.ts index 7b2d90c..42e338a 100644 --- a/src/commands/start.command.ts +++ b/src/commands/start.command.ts @@ -1,12 +1,10 @@ -import { randomUUID } from 'crypto' import { readFile, stat } from 'fs' import { Command, CommandRunner, InquirerService } from 'nest-commander' import { concatAll, tap } from 'rxjs/operators' import { defer, Observable, of } from 'rxjs' import { Next, ReactiveSpawn } from '../ReactiveSpawn' -import { workingDir } from 'src/constants' -import { createLoggerFile, loggerConsole } from 'src/loggers' +import { log, logError } from 'src/loggers' const INFRA_REF = 'v0.1.5' const FRONTEND_REF = 'v0.1.0-alpha3' @@ -14,10 +12,6 @@ const EXECUTOR_SERVICE_REF = 'v0.1.1' @Command({ name: 'start', description: 'Run everything' }) export class StartCommand extends CommandRunner { - private _workingDir = workingDir - private _loggerConsole = loggerConsole - private _logFilePath = `logs/log-${randomUUID()}.log` - private _loggerFile = createLoggerFile(this._logFilePath) constructor( private _spawn: ReactiveSpawn, @@ -27,8 +21,8 @@ export class StartCommand extends CommandRunner { } async run(): Promise { - this._log(`Welcome to Topos-Playground!`) - this._log(``) + log(`Starting Topos-Playground!`) + log(``) of( this._verifyDependencyInstallation(), @@ -44,27 +38,23 @@ export class StartCommand extends CommandRunner { .pipe(concatAll()) .subscribe({ complete: () => { - this._log(`πŸ”₯ Everything is done! πŸ”₯`) - this._log(``) - this._log( + log(`πŸ”₯ Everything is done! πŸ”₯`) + log(``) + log( `πŸš€ Start sending ERC20 tokens across subnet by accessing the dApp Frontend at http://localhost:3001` ) - this._log(``) - this._log( + log(``) + log( `ℹ️ Ctrl/cmd-c will shut down the dApp Frontend and the Executor Service BUT will keep subnets and the TCE running (use the clean command to shut them down)` ) - this._log(`ℹ️ Logs were written to ${this._logFilePath}`) + log(`ℹ️ Logs were written to ${logFilePath}`) }, error: () => { - this._logError(`❌ Error`) + logError(`❌ Error`) }, next: (data: Next) => { - if (data && data.hasOwnProperty('origin')) { - if (data.origin === 'stderr') { - this._loggerFile.error(data.output) - } else if (data.origin === 'stdout') { - this._loggerFile.info(data.output) - } + if (data && data.hasOwnProperty('output')) { + log(`${data.output}`) } }, }) @@ -72,60 +62,74 @@ export class StartCommand extends CommandRunner { private _verifyDependencyInstallation() { return of( - defer(() => of(this._log('Verifying dependency installation...'))), + defer(() => of(log('Verifying dependency installation...'))), this._verifyDockerInstallation(), this._verifyGitInstallation(), this._verifyNodeJSInstallation(), - defer(() => of(this._log(''))) + defer(() => of(log(''))) ).pipe(concatAll()) } private _verifyDockerInstallation() { + let output = null + return of( - this._spawn.reactify('docker --version'), - defer(() => of(this._log('βœ… Docker'))) + new Observable((subscriber) => { + this._spawn.reactify('docker --version').subscribe({ + next: (data: Next) => { + output = data + subscriber.next('') + }, + error: () => { subscriber.error() }, + complete: () => { subscriber.complete() }, + }) + }), + defer(() => of( + output.orign === 'stderr' ? + log(`❌ Docker is not installed!`) : + log(`βœ… Docker`), log(` ${output.output}`))), ).pipe(concatAll()) } private _verifyGitInstallation() { return of( this._spawn.reactify('git --version'), - defer(() => of(this._log('βœ… Git'))) + defer(() => of(log('βœ… Git'))) ).pipe(concatAll()) } private _verifyNodeJSInstallation() { return of( this._spawn.reactify('node --version'), - defer(() => of(this._log('βœ… NodeJS'))) + defer(() => of(log('βœ… NodeJS'))) ).pipe(concatAll()) } private _createWorkingDirectoryIfInexistant() { return new Observable((subscriber) => { - this._log(`Verifying working directory: [${this._workingDir}]...`) + log(`Verifying working directory: [${globalThis.workingDir}]...`) - stat(this._workingDir, (error) => { + stat(globalThis.workingDir, (error) => { if (error) { this._spawn - .reactify(`mkdir -p ${this._workingDir}`) + .reactify(`mkdir -p ${globalThis.workingDir}`) .pipe( tap({ complete: () => { - this._log(`βœ… Working directory was successfully created`) + log(`βœ… Working directory was successfully created`) }, }) ) .subscribe(subscriber) } else { - this._log(`βœ… Working directory exists`) + log(`βœ… Working directory exists`) subscriber.complete() } }) }).pipe( tap({ complete: () => { - this._log('') + log('') }, }) ) @@ -133,7 +137,7 @@ export class StartCommand extends CommandRunner { private _cloneGitRepositories() { return of( - defer(() => of(this._log('Cloning repositories...'))), + defer(() => of(log('Cloning repositories...'))), this._cloneGitRepository( 'topos-protocol', 'local-erc20-messaging-infra', @@ -149,7 +153,7 @@ export class StartCommand extends CommandRunner { 'executor-service', EXECUTOR_SERVICE_REF ), - defer(() => of(this._log(''))) + defer(() => of(log(''))) ).pipe(concatAll()) } @@ -159,7 +163,7 @@ export class StartCommand extends CommandRunner { branch?: string ) { return new Observable((subscriber) => { - const path = `${this._workingDir}/${repositoryName}` + const path = `${globalThis.workingDir}/${repositoryName}` stat(path, (error) => { if (error) { @@ -168,13 +172,13 @@ export class StartCommand extends CommandRunner { `git clone --depth 1 ${ branch ? `--branch ${branch}` : '' } git@github.com:${organizationName}/${repositoryName}.git ${ - this._workingDir + globalThis.workingDir }/${repositoryName}` ) .pipe( tap({ complete: () => { - this._log( + log( `βœ… ${repositoryName}${ branch ? ` | ${branch}` : '' } successfully cloned` @@ -184,7 +188,7 @@ export class StartCommand extends CommandRunner { ) .subscribe(subscriber) } else { - this._log( + log( `βœ… ${repositoryName}${branch ? ` | ${branch}` : ''} already cloned` ) subscriber.complete() @@ -195,21 +199,21 @@ export class StartCommand extends CommandRunner { private _copyEnvFiles() { return of( - defer(() => of(this._log('Copying .env files across repositories...'))), + defer(() => of(log('Copying .env files across repositories...'))), this._copyEnvFile( '.env.dapp-frontend', - `${this._workingDir}/dapp-frontend-erc20-messaging/packages/frontend` + `${globalThis.workingDir}/dapp-frontend-erc20-messaging/packages/frontend` ), this._copyEnvFile( '.env.dapp-backend', - `${this._workingDir}/dapp-frontend-erc20-messaging/packages/backend` + `${globalThis.workingDir}/dapp-frontend-erc20-messaging/packages/backend` ), this._copyEnvFile( '.env.executor-service', - `${this._workingDir}/executor-service` + `${globalThis.workingDir}/executor-service` ), - this._copyEnvFile('.env.secrets', `${this._workingDir}`, `.env.secrets`), - defer(() => of(this._log(''))) + this._copyEnvFile('.env.secrets', `${globalThis.workingDir}`, `.env.secrets`), + defer(() => of(log(''))) ).pipe(concatAll()) } @@ -233,7 +237,7 @@ export class StartCommand extends CommandRunner { .pipe( tap({ complete: () => { - this._log(`βœ… ${localEnvFileName} copied`) + log(`βœ… ${localEnvFileName} copied`) }, }) ) @@ -241,7 +245,7 @@ export class StartCommand extends CommandRunner { } ) } else { - this._log(`βœ… ${localEnvFileName} already existing`) + log(`βœ… ${localEnvFileName} already existing`) subscriber.complete() } }) @@ -249,29 +253,29 @@ export class StartCommand extends CommandRunner { } private _runLocalERC20MessagingInfra() { - const secretsFilePath = `${this._workingDir}/.env.secrets` - const executionPath = `${this._workingDir}/local-erc20-messaging-infra` + const secretsFilePath = `${globalThis.workingDir}/.env.secrets` + const executionPath = `${globalThis.workingDir}/local-erc20-messaging-infra` return of( - defer(() => of(this._log(`Running the ERC20 messaging infra...`))), + defer(() => of(log(`Running the ERC20 messaging infra...`))), this._spawn.reactify( `source ${secretsFilePath} && cd ${executionPath} && docker compose up -d` ), - defer(() => of(this._log(`βœ… Subnets & TCE are running`), this._log(``))) + defer(() => of(log(`βœ… Subnets & TCE are running`), log(``))) ).pipe(concatAll()) } private _retrieveAndWriteContractAddressesToEnv() { - const frontendEnvFilePath = `${this._workingDir}/dapp-frontend-erc20-messaging/packages/frontend/.env` - const executorServiceEnvFilePath = `${this._workingDir}/executor-service/.env` + const frontendEnvFilePath = `${globalThis.workingDir}/dapp-frontend-erc20-messaging/packages/frontend/.env` + const executorServiceEnvFilePath = `${globalThis.workingDir}/executor-service/.env` return of( - defer(() => of(this._log(`Retrieving contract addresses...`))), + defer(() => of(log(`Retrieving contract addresses...`))), this._spawn.reactify( - `docker cp contracts-topos:/contracts/.env ${this._workingDir}/.env.addresses` + `docker cp contracts-topos:/contracts/.env ${globalThis.workingDir}/.env.addresses` ), this._spawn.reactify( - `source ${this._workingDir}/.env.addresses \ + `source ${globalThis.workingDir}/.env.addresses \ && echo "VITE_SUBNET_REGISTRATOR_CONTRACT_ADDRESS=$SUBNET_REGISTRATOR_CONTRACT_ADDRESS" >> ${frontendEnvFilePath} \ && echo "VITE_TOPOS_CORE_CONTRACT_ADDRESS=$TOPOS_CORE_PROXY_CONTRACT_ADDRESS" >> ${frontendEnvFilePath} \ && echo "VITE_ERC20_MESSAGING_CONTRACT_ADDRESS=$ERC20_MESSAGING_CONTRACT_ADDRESS" >> ${frontendEnvFilePath} \ @@ -280,10 +284,10 @@ export class StartCommand extends CommandRunner { ), defer(() => of( - this._log( + log( `βœ… Contract addresses were retrieved and written to env files` ), - this._log(``) + log(``) ) ) ).pipe(concatAll()) @@ -293,23 +297,23 @@ export class StartCommand extends CommandRunner { const containerName = 'redis-stack-server' return of( - defer(() => of(this._log(`Running the redis server...`))), + defer(() => of(log(`Running the redis server...`))), this._spawn.reactify( `docker start ${containerName} 2>/dev/null || docker run -d --name ${containerName} -p 6379:6379 redis/redis-stack-server:latest` ), - defer(() => of(this._log(`βœ… redis is running`), this._log(``))) + defer(() => of(log(`βœ… redis is running`), log(``))) ).pipe(concatAll()) } private _runExecutorService() { - const secretsFilePath = `${this._workingDir}/.env.secrets` - const executionPath = `${this._workingDir}/executor-service` + const secretsFilePath = `${globalThis.workingDir}/.env.secrets` + const executionPath = `${globalThis.workingDir}/executor-service` return of( - defer(() => of(this._log(`Running the Executor Service...`))), + defer(() => of(log(`Running the Executor Service...`))), this._npmInstall(executionPath), this._startExecutorService(secretsFilePath, executionPath), - defer(() => of(this._log(``))) + defer(() => of(log(``))) ).pipe(concatAll()) } @@ -317,7 +321,7 @@ export class StartCommand extends CommandRunner { return this._spawn.reactify(`cd ${executionPath} && npm install`).pipe( tap({ complete: () => { - this._log(`βœ… Deps are installed`) + log(`βœ… Deps are installed`) }, }) ) @@ -335,22 +339,22 @@ export class StartCommand extends CommandRunner { .pipe( tap({ complete: () => { - this._log(`βœ… Web server is running`) + log(`βœ… Web server is running`) }, }) ) } private _rundDappFrontendService() { - const secretsFilePath = `${this._workingDir}/.env.secrets` - const executionPath = `${this._workingDir}/dapp-frontend-erc20-messaging` + const secretsFilePath = `${globalThis.workingDir}/.env.secrets` + const executionPath = `${globalThis.workingDir}/dapp-frontend-erc20-messaging` return of( - defer(() => of(this._log(`Running the dApp Frontend...`))), + defer(() => of(log(`Running the dApp Frontend...`))), this._npmInstall(executionPath), this._buildDappFrontend(secretsFilePath, executionPath), this._startDappFrontend(secretsFilePath, executionPath), - defer(() => of(this._log(``))) + defer(() => of(log(``))) ).pipe(concatAll()) } @@ -362,7 +366,7 @@ export class StartCommand extends CommandRunner { .pipe( tap({ complete: () => { - this._log(`βœ… Static files are built`) + log(`βœ… Static files are built`) }, }) ) @@ -377,19 +381,10 @@ export class StartCommand extends CommandRunner { .pipe( tap({ complete: () => { - this._log(`βœ… Web server is running`) + log(`βœ… Web server is running`) }, }) ) } - private _log(logMessage: string) { - this._loggerConsole.info(logMessage) - this._loggerFile.info(logMessage) - } - - private _logError(errorMessage: string) { - this._loggerConsole.error(errorMessage) - this._loggerConsole.error(`Find more details in ${this._logFilePath}`) - } } diff --git a/src/commands/version.command.ts b/src/commands/version.command.ts new file mode 100644 index 0000000..6828be3 --- /dev/null +++ b/src/commands/version.command.ts @@ -0,0 +1,24 @@ +const { version } = require('../../package.json'); + +import { Command, CommandRunner } from 'nest-commander' + +import { ReactiveSpawn } from '../ReactiveSpawn' +import { log } from 'src/loggers' + + +@Command({ + name: 'version', + description: `Show topos-playground version (v${version})`, +}) + +export class VersionCommand extends CommandRunner { + constructor(private _spawn: ReactiveSpawn) { + super() + } + + async run(): Promise { + log((globalThis.quiet ? '' : 'topos-playground version ') + version, true) + log(``) + } + +} diff --git a/src/globals.ts b/src/globals.ts new file mode 100644 index 0000000..14197a2 --- /dev/null +++ b/src/globals.ts @@ -0,0 +1,49 @@ +import { mkdir } from 'fs' +import { join } from 'path' +import { randomUUID } from 'crypto' +import { loggerConsole, logError } from 'src/loggers' + + +declare global { + var verbose: boolean + var quiet: boolean + var no_log: boolean + var workingDir: string + var logDir: string + var executionPath: string + var executionPath_exists: boolean + var lconsole: typeof loggerConsole + var logFilePath : string + var loggerFile +} + +export function setupGlobals() { + // Setup global configuration + + let home = process.env.HOME || '.' + let data_home = join(home, '.local', 'share') + let state_home = join(home, '.local', 'state') + + globalThis.workingDir = join(data_home, 'topos-playground') + globalThis.logDir = join(state_home, 'topos-playground/logs') + globalThis.lconsole = loggerConsole + globalThis.logFilePath = join(logDir, `log-${randomUUID()}.log`) + globalThis.executionPath = join(globalThis.workingDir, 'local-erc20-messaging-infra') + + globalThis.loggerFile = false + + // Create the working directory if it does not exist + mkdir(globalThis.workingDir, { recursive: true }, (error) => { + if (error) { + logError(`Could not create working directory (${globalThis.workingDir})`) + } + }) + + // Create the log directory if it does not exist + mkdir(globalThis.logDir, { recursive: true }, (error) => { + if (error) { + logError(`Could not create log directory (${globalThis.logDir})`) + } + }) + +} \ No newline at end of file diff --git a/src/loggers.ts b/src/loggers.ts index 05c4dff..1c5eb54 100644 --- a/src/loggers.ts +++ b/src/loggers.ts @@ -28,3 +28,34 @@ export function createLoggerFile(logFilePath: string) { ], }) } + +function logConsole() { + return globalThis.lconsole +} + +function logFile() { + if (!globalThis.loggerFile) globalThis.loggerFile = createLoggerFile(globalThis.no_log ? '/dev/null' : globalThis.logFilePath) + + return globalThis.loggerFile +} + +export function log(logMessage: string, overrideQuiet: boolean = false) { + let lines = logMessage.split('\n') + + for (let line of lines) { + if (overrideQuiet || !globalThis.quiet) logConsole().info(line) + logFile().info(line) + } +} + +export function logError(errorMessage: string) { + let lines = errorMessage.split('\n') + + for (let line of lines) { + logConsole().error(line) + logFile().error(line) + } + + logConsole().error(`Find more details in ${globalThis.logFilePath}`) + logFile().error(`Find more details in ${globalThis.logFilePath}`) +} diff --git a/src/main.ts b/src/main.ts index eb46184..bc2d5ad 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,10 +1,11 @@ #!/usr/bin/env node import { CommandFactory } from 'nest-commander' - +import { setupGlobals } from 'src/globals' import { AppModule } from './app.module' async function bootstrap() { await CommandFactory.run(AppModule) } +setupGlobals() bootstrap() diff --git a/src/utility.ts b/src/utility.ts new file mode 100644 index 0000000..a252079 --- /dev/null +++ b/src/utility.ts @@ -0,0 +1,63 @@ +/* + Breaks a string into lines of length n, respecting word boundaries. + + This code will add line breaks to a long string, breaking it into a + multiline string. It breaks at spaces, unless doing so would result in a + line with an abnormally large gap at the end. In that case, it will + hypenate the last word in the line and continue on the next line. +*/ +export function breakText(str: string, n: number = 60): string { + if (n <= 0) return str + + // Split the string into words to calculate word length statistics + let words = str.split(' '); + let wordLengths = words.map(word => word.length); + + // Calculate average word length + let average = wordLengths.reduce((sum, length) => sum + length, 0) / wordLengths.length; + + // Calculate standard deviation of word length + let sumOfSquaredDifferences = wordLengths.reduce((sum, length) => sum + Math.pow(length - average, 2), 0); + let standardDeviation = Math.sqrt(sumOfSquaredDifferences / wordLengths.length); + + // Determine the maximum word length to allow before breaking + let maxWordLength = Math.min(n, average + standardDeviation); + + + let lines: string[] = [] + let line = "" + let word = "" + + for (let i = 0; i < str.length; i++) { + let char = str[i] + if (char === " " || i === str.length - 1) { + if (line.length + word.length < n || word.length >= n) { + line += word + (i === str.length - 1 && char !== " " ? char : " ") + word = "" + } else { + if ((((line.length + word.length) > n) && ((n - line.length) > maxWordLength)) || i === str.length - 1) { + while(word.length > maxWordLength) { + let part = word.substring(0, n - line.length - 1) + let remaining = word.substring(n - line.length - 1) + lines.push(line + part + (remaining.length > 0 ? "-" : "")) + line = "" + word = remaining + } + line = word + " " + } else { + lines.push(line.trim()) + line = word + " " + } + word = "" + } + if (line.length >= n) { + lines.push(line.trim()) + line = "" + } + } else { + word += char + } + } + if (line) lines.push(line.trim()) + return lines.join("\n") +} \ No newline at end of file From 95a57ac3b09df74f7d145f5e97d49f15f85f732a Mon Sep 17 00:00:00 2001 From: Kirk Haines Date: Tue, 22 Aug 2023 15:51:58 -0600 Subject: [PATCH 02/37] Usability changes There are a variety of changes here. First, there is a reasonable command line help: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ``` ❯ topos-playground -h Usage: topos-playground [options] [command] topos-playground is a CLI tool which handles all of the orchestration necessary to run local Topos devnets with subnets, a TCE network, and apps. Configuration topos-playground follows the XDG Base Directory Specification, which means that data files for use during runs of the playground are store in $XDG_DATA_HOME/topos-playground, which defaults to $HOME/.local/share/topos-playground and log files are stored in $XDG_STATE_HOME/topos-playground/logs, which defaults to $HOME/.local/state/topos-playground/logs. These locations can be overridden by setting the environment variables HOME, XDG_DATA_HOME, and XDG_STATE_HOME. Options: --version Show topos-playground version (v0.0.2) -v, --verbose Show more information about the execution of a command -q, --quiet Show minimal onscreen information about the execution of a command -n, --no-log Do not write a log file -h, --help display help for command Commands: clean Shut down Playground docker containers, and clean up the working directory start Verify that all dependencies are installed, clone any needed repositories, setup the environment, and start all of the docker containers for the Playground version Show topos-playground version (v0.0.2) ``` It intelligently wraps the text to the terminal size. In order for a user to be able to easily know which version of the topos-playground one has installed, topos-playground can be ran with either a `version` command, or a `--version` command line flag. ``` ❯ topos-playground --version topos-playground version 0.0.2 ❯ topos-playground version topos-playground version 0.0.2 ``` The `start` command was modified to give more feedback about what it is doing. The `clean` command was substantially rewritten to give information about what it is doing, and to clean up the working directory. There were significant changes to how the playground reports what it is doing. Instead of _just_ writing to a log file, it now, by default, also outputs to the console. This console output can be turned off with the `--quiet` or `-q` command line flags, and creation of a log file can be turned off with the `--no-log` or `-n` command line flags. ``` topos-playground start --no-log ``` ``` topos-playground clean -q ``` Finally, the command properly honors XDG Base Directory Specification standards. This means that by default, topos-playground follows the XDG Base Directory Specification. Thus data file which the playground creates while running are store in $XDG_DATA_HOME/topos-playground, which defaults to $HOME/.local/share/topos-playground and log files are stored in $XDG_STATE_HOME/topos-playground/logs, which defaults to $HOME/.local/state/topos-playground/logs. This behavior can be changed via environment variables. There is currently no command-line argument support for changing these, though it would not be hard to add. ``` HOME=/tmp topos-playground start ``` ``` XDG_DATA_HOME=/tmp/ topos-playground start ``` It will also read these values from a `.env` file, if one exists. Finally, there has been a small amount added to the README. --- README.md | 25 +++- package-lock.json | 17 +++ package.json | 1 + src/ReactiveSpawn.ts | 4 + src/commands/clean.command.ts | 212 ++++++++++++++++++-------------- src/commands/newRoot.command.ts | 13 +- src/commands/start.command.ts | 4 +- src/globals.ts | 10 +- src/utility.ts | 9 +- 9 files changed, 186 insertions(+), 109 deletions(-) diff --git a/README.md b/README.md index d86089c..b8ea922 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Logo

- Topos Playground is the CLI to run a local devnet to test the Topos ecosystem πŸš€ + Topos Playground is a CLI make it simple to run a local Topos devnet πŸš€


@@ -26,10 +26,29 @@ $ npm install -g @topos-protocol/topos-playground ### Run the CLI -If you installed the package manually, you can run it like so +If you have installed the package manually, you can run it like so: ``` -$ topos-playground [start|clean] +$ topos-playground --help +``` + +The playground respects XDG Base Directory Specifications, so by default, it will store +data used while running in `$HOME/.local/share/topos-playground` and it will store logs +in `$HOME/.state/topos-playground/logs`. + +To override these default locations, you can set your `HOME`, `XDG_DATA_HOME` and `XDG_STATE_HOME` +environment variables, or specify them in a `.env` file. + +``` +$ HOME=/tmp topos-playground start +``` + +By default, topos-playground sends output to both your console and to a log file when it is running. +To disable this, you can use the `--quiet` flag to prevent output from going to the console, or the +`--no-log` flag to prevent output from going to the log file. + +``` +$ topos-playground start --quiet ``` Otherwise, you can use `npx` to abstract the installation diff --git a/package-lock.json b/package-lock.json index 378aad4..3df7d87 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "@nestjs/common": "^9.0.0", "@nestjs/core": "^9.0.0", + "dotenv": "^16.3.1", "ethers": "^5.7.2", "nest-commander": "^3.7.1", "winston": "^3.8.2" @@ -4079,6 +4080,17 @@ "node": ">=6.0.0" } }, + "node_modules/dotenv": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", + "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/motdotla/dotenv?sponsor=1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -12120,6 +12132,11 @@ "esutils": "^2.0.2" } }, + "dotenv": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", + "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==" + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", diff --git a/package.json b/package.json index 838093e..a500994 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "dependencies": { "@nestjs/common": "^9.0.0", "@nestjs/core": "^9.0.0", + "dotenv": "^16.3.1", "ethers": "^5.7.2", "nest-commander": "^3.7.1", "winston": "^3.8.2" diff --git a/src/ReactiveSpawn.ts b/src/ReactiveSpawn.ts index 1abe2a9..57b061d 100644 --- a/src/ReactiveSpawn.ts +++ b/src/ReactiveSpawn.ts @@ -1,6 +1,7 @@ import { ChildProcess, spawn } from 'child_process' import { userInfo } from 'os' import { Observable } from 'rxjs' +import { log, logError } from 'src/loggers' export interface Next { origin: 'stdout' | 'stderr' @@ -12,6 +13,9 @@ export class ReactiveSpawn { reactify(command: string, options?: { runInBackground }) { return new Observable((subscriber) => { + if (globalThis.verbose) { + log(`πŸƒ Running command: ${command}`) + } const childProcess = spawn(command, { ...options, shell: this._shell }) if (options && options.runInBackground) { diff --git a/src/commands/clean.command.ts b/src/commands/clean.command.ts index df4420d..d8a637b 100644 --- a/src/commands/clean.command.ts +++ b/src/commands/clean.command.ts @@ -1,9 +1,7 @@ -import { stat } from 'fs' -import { join } from 'path' +import { stat, readdir } from 'fs' import { Command, CommandRunner } from 'nest-commander' -import { concatAll } from 'rxjs/operators' -import { defer, EMPTY, Observable, of } from 'rxjs' +import { Observable, concat } from 'rxjs' import { homedir } from 'os' import { Next, ReactiveSpawn } from '../ReactiveSpawn' @@ -11,7 +9,7 @@ import { log, logError } from 'src/loggers' @Command({ name: 'clean', - description: 'Clean artifacts from a previous start', + description: 'Shut down Playground docker containers, and clean up the working directory', }) export class CleanCommand extends CommandRunner { constructor(private _spawn: ReactiveSpawn) { @@ -19,132 +17,156 @@ export class CleanCommand extends CommandRunner { } async run(): Promise { - log(`Cleaning up Topos-Playground!`) + log(`Cleaning up Topos-Playground...`) log(``) - of( - this._verifyExecutionPathExistence(), - this._verifyWorkingDirectoryExistence(), - this._shutdownFullMsgProtocolInfra(), - this._shutdownRedis(), - this._removeworkingDir() - ) - .pipe(concatAll()) - .subscribe({ - complete: () => { - log(`🧹 Everything is clean! 🧹`) - log(`Logs were written to ${globalThis.logFilePath}`) - }, - error: () => {}, - next: (data: Next) => { - if (data && data.hasOwnProperty('output')) { - log(`${data.output}`) - } - }, + this._verifyWorkingDirectoryExistence().subscribe((working_dir_flag) => { + if (working_dir_flag) { + log(`Found working directory (${globalThis.workingDir})`) + } else { + log(`Working directory (${globalThis.workingDir}) empty or not found`) + } + + this._verifyExecutionPathExistence().subscribe((execution_path_flag) => { + if (execution_path_flag) { + log(`Found execution path (${globalThis.executionPath})`) + } else { + log(`Execution path (${globalThis.executionPath}) not found`) + } + + // Coordinate the steps to clean up the environment + concat( + this._shutdownFullMsgProtocolInfra(), + this._shutdownRedis(), + this._removeWorkingDirectory() + ).subscribe({ + next: () => { }, + complete: () => { }, + error: () => { } + }) }) + }) } private _verifyWorkingDirectoryExistence() { return new Observable((subscriber) => { - stat(globalThis.workingDir, (error) => { - console.log("verify working directory existence") + stat(globalThis.workingDir, (error, stats) => { if (error) { logError( `The working directory (${globalThis.workingDir}) can not been found; nothing to clean!` ) - subscriber.error() + globalThis.workingDirExists = false + subscriber.next(false) + } else if (!stats.isDirectory()) { + logError( + `The working directory (${globalThis.workingDir}) is not a directory; this is an error!` + ) + globalThis.workingDirExists = false + subscriber.error(); } else { - log(`Cleaning working directory (${globalThis.workingDir})...`) - subscriber.complete() + readdir(globalThis.workingDir, (err, files) => { + if (err) { + globalThis.workingDirExists = false + subscriber.error(); + } + + if (files.length === 0) { + globalThis.workingDirExists = false + subscriber.next(false) + } else { + globalThis.workingDirExists = true + subscriber.next(true) + } + }); + } }) }) } private _verifyExecutionPathExistence() { - console.log(`verify execution path ${globalThis.executionPath}`) return new Observable((subscriber) => { stat(globalThis.executionPath, (error) => { - console.log("verify execution path existence") if (error) { - globalThis.executionPath_exists = false - subscriber.complete() + globalThis.executionPathExists = false + subscriber.next(false) } else { - globalThis.executionPath_exists = true - subscriber.complete() + globalThis.executionPathExists = true + subscriber.next(true) } }) }) } private _shutdownFullMsgProtocolInfra() { - console.log("do shutdown stuff") - - console.log("do return blah blah shutoff") - return of( - defer(() => of(console.log("A*"))), - defer(() => of(!globalThis.executionPath_exists ? log(`βœ… ERC20 messaging infra is not running; subnets & TCE are down`) : null)), - defer(() => of(globalThis.executionPath_exists ? log(`Shutting down the ERC20 messaging infra...`) : null)), - defer(() => this._spawn.reactify(`cd ${globalThis.executionPath} && docker compose down -v`)), - defer(() => of(log(`βœ… subnets & TCE are down`), log(``))) - ).pipe(concatAll()) + return new Observable((subscriber) => { + if (globalThis.executionPathExists) { + log(`\nShutting down the ERC20 messaging infra...`) + this._spawn.reactify(`cd ${globalThis.executionPath} && docker compose down -v`).subscribe({ + next: (data: Next) => { subscriber.next(data) }, + error: (data) => { subscriber.error(data) }, + complete: () => { subscriber.complete() } + }) + log(`βœ… subnets & TCE are down`) + } else { + log(`\nβœ… ERC20 messaging infra is not running; subnets & TCE are down`) + } + + }) } private _shutdownRedis() { const containerName = 'redis-stack-server' let container_running = false - return of( - new Observable((subscriber) => { - this._spawn.reactify(`docker ps --format '{{.Names}}' | grep ${containerName}`).subscribe({ - next: (data: Next) => { - if (data && data.output && `${data.output}`.indexOf(containerName) !== -1) { - container_running = true - } - }, - error: () => { subscriber.error() }, - complete: () => { subscriber.complete() } - }) - }), - defer(() => of(log(`Container running: ${container_running}`))), - defer(() => of(log(container_running ? `βœ… redis is not running; nothing to shut down` : `Shutting down the redis server...`))), - new Observable((subscriber) => { - this._spawn.reactify(`docker rm -f ${containerName}`).subscribe({ - next: (data: Next) => { subscriber.next(data) }, - error: (data) => { subscriber.error(data) }, - complete: () => { subscriber.complete() } + return new Observable((subscriber) => { + concat( + new Observable((inner_subscriber) => { + this._spawn.reactify(`docker ps --format '{{.Names}}' | grep ${containerName}`).subscribe({ + next: (data: Next) => { + if (data && data.output && `${data.output}`.indexOf(containerName) !== -1) { + container_running = true + } + }, + error: () => { container_running = false; inner_subscriber.complete() /* grep returns an error code 1 if a pattern is missing */ }, + complete: () => { inner_subscriber.complete() } + }) + }), + new Observable((inner_subscriber) => { + if (container_running) { + log(`\nShutting down the redis server...`) + + this._spawn.reactify(`docker rm -f ${containerName}`).subscribe({ + next: (data: Next) => { inner_subscriber.next(data) }, + complete: () => { inner_subscriber.complete() } + }) + log(`βœ… redis is down`) + } else { + log(`\n βœ… redis is not running; nothing to shut down`) + } + }), + new Observable((inner_subscriber) => { + inner_subscriber.complete() }) - }), - defer(() => of(log(container_running ? `βœ… redis is down\n` : ``)))).pipe(concatAll()) + ).subscribe({ + next: (data: Next) => { subscriber.next(data) }, + error: () => { subscriber.error() }, + complete: () => { subscriber.complete() } + }) + }) } - private _removeworkingDir() { + private _removeWorkingDirectory() { const homeDir = homedir() - - return of( - defer(() => of(log(`Removing the working directory...`))), - globalThis.workingDir.indexOf(homeDir) !== -1 && globalThis.workingDir !== homeDir - ? of( - this._spawn.reactify(`rm -rf ${globalThis.workingDir}`), - defer(() => - of( - log('βœ… Working directory has been removed'), - log(``) - ) - ) - ).pipe(concatAll()) - : of( - EMPTY, - defer(() => - of( - logError( - `Working directory (${globalThis.workingDir}) is not safe for removal!` - ), - log(``) - ) - ) - ).pipe(concatAll()) - ).pipe(concatAll()) + return new Observable((subscriber) => { + if (globalThis.workingDirExists && globalThis.workingDir.indexOf(homeDir) !== -1 && globalThis.workingDir !== homeDir) { + log(`\nCleaning up the working directory (${globalThis.workingDir})`) + this._spawn.reactify(`rm -rf ${globalThis.workingDir}`).subscribe({ + next: (data: Next) => { subscriber.next(data) }, + complete: () => { subscriber.complete() } + }) + log('βœ… Working directory has been removed') + } + }) } - -} +} \ No newline at end of file diff --git a/src/commands/newRoot.command.ts b/src/commands/newRoot.command.ts index 776979d..b6d3571 100644 --- a/src/commands/newRoot.command.ts +++ b/src/commands/newRoot.command.ts @@ -6,8 +6,19 @@ import { RootCommand, Option, CommandRunner } from 'nest-commander' import { ReactiveSpawn } from '../ReactiveSpawn' import { log } from 'src/loggers' +const helptext = ` + +Configuration + +topos-playground follows the XDG Base Directory Specification, which means that data files for use during runs of the playground are store in $XDG_DATA_HOME/topos-playground, which defaults to $HOME/.local/share/topos-playground and log files are stored in $XDG_STATE_HOME/topos-playground/logs, which defaults to $HOME/.local/state/topos-playground/logs. + +These locations can be overridden by setting the environment variables HOME, XDG_DATA_HOME, and XDG_STATE_HOME. +`.trim() + +const columns = process.stdout.columns || 80 + @RootCommand({ - description: `${breakText(description)}\n`, + description: `${breakText(description, columns)}\n\n${breakText(helptext, columns)}`, }) export class NewRootCommand extends CommandRunner { diff --git a/src/commands/start.command.ts b/src/commands/start.command.ts index 42e338a..181aa8a 100644 --- a/src/commands/start.command.ts +++ b/src/commands/start.command.ts @@ -10,7 +10,7 @@ const INFRA_REF = 'v0.1.5' const FRONTEND_REF = 'v0.1.0-alpha3' const EXECUTOR_SERVICE_REF = 'v0.1.1' -@Command({ name: 'start', description: 'Run everything' }) +@Command({ name: 'start', description: 'Verify that all dependencies are installed, clone any needed repositories, setup the environment, and start all of the docker containers for the Playground' }) export class StartCommand extends CommandRunner { constructor( @@ -21,7 +21,7 @@ export class StartCommand extends CommandRunner { } async run(): Promise { - log(`Starting Topos-Playground!`) + log(`Starting Topos-Playground...`) log(``) of( diff --git a/src/globals.ts b/src/globals.ts index 14197a2..77a0160 100644 --- a/src/globals.ts +++ b/src/globals.ts @@ -2,16 +2,18 @@ import { mkdir } from 'fs' import { join } from 'path' import { randomUUID } from 'crypto' import { loggerConsole, logError } from 'src/loggers' - +import { config } from 'dotenv' +config() declare global { var verbose: boolean var quiet: boolean var no_log: boolean var workingDir: string + var workingDirExists: boolean var logDir: string var executionPath: string - var executionPath_exists: boolean + var executionPathExists: boolean var lconsole: typeof loggerConsole var logFilePath : string var loggerFile @@ -21,8 +23,8 @@ export function setupGlobals() { // Setup global configuration let home = process.env.HOME || '.' - let data_home = join(home, '.local', 'share') - let state_home = join(home, '.local', 'state') + let data_home = process.env.XDG_DATA_HOME || join(home, '.local', 'share') + let state_home = process.env.XDG_STATE_HOME || join(home, '.local', 'state') globalThis.workingDir = join(data_home, 'topos-playground') globalThis.logDir = join(state_home, 'topos-playground/logs') diff --git a/src/utility.ts b/src/utility.ts index a252079..7eab3b8 100644 --- a/src/utility.ts +++ b/src/utility.ts @@ -31,14 +31,15 @@ export function breakText(str: string, n: number = 60): string { for (let i = 0; i < str.length; i++) { let char = str[i] if (char === " " || i === str.length - 1) { - if (line.length + word.length < n || word.length >= n) { + if (line.length + word.length < n) { line += word + (i === str.length - 1 && char !== " " ? char : " ") word = "" } else { if ((((line.length + word.length) > n) && ((n - line.length) > maxWordLength)) || i === str.length - 1) { - while(word.length > maxWordLength) { - let part = word.substring(0, n - line.length - 1) - let remaining = word.substring(n - line.length - 1) + let first_character = word[0] + if (first_character.toLowerCase() != first_character.toUpperCase() && word.length > maxWordLength) { + let part = word.substring(0, maxWordLength - 1) + let remaining = word.substring(maxWordLength - 1) lines.push(line + part + (remaining.length > 0 ? "-" : "")) line = "" word = remaining From fa0f10e95d8daf64cecaac9f67ea2d7285590844 Mon Sep 17 00:00:00 2001 From: Kirk Haines Date: Wed, 23 Aug 2023 06:50:09 -0600 Subject: [PATCH 03/37] Temporary file that had slipped my mind that I had made. It was just some notes that I had made while getting this up and running originally. One stumbling block to running it is that if one is _already_ running Redis, one gets the error recorded here. My intent is to add a short troubleshooting section to the README that helps with a few likely common stumbling blocks to using the Playground, including this issue. Removing this file, though. It wasn't intended to be permanent. --- errors.notes | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 errors.notes diff --git a/errors.notes b/errors.notes deleted file mode 100644 index 8787922..0000000 --- a/errors.notes +++ /dev/null @@ -1,2 +0,0 @@ -error: docker: Error response from daemon: driver failed programming external connectivity on endpoint redis-stack-server (662867da5745dd786f75213af8f91365caab5c5ecd38370fcd4c1a8542c5432c): Error starting userland proxy: listen tcp4 0.0.0.0:6379: bind: address already in use. - From 91efee74f92320b20178fa441e4987daa3f20ad1 Mon Sep 17 00:00:00 2001 From: Kirk Haines Date: Wed, 23 Aug 2023 07:09:04 -0600 Subject: [PATCH 04/37] Make imports follow suggestion of separating local imports by a blank line, and making them all relative paths. --- src/commands/clean.command.ts | 3 +-- src/commands/newRoot.command.ts | 5 ++--- src/commands/start.command.ts | 2 +- src/commands/version.command.ts | 6 ++---- src/globals.ts | 4 +++- 5 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/commands/clean.command.ts b/src/commands/clean.command.ts index d8a637b..35b02c3 100644 --- a/src/commands/clean.command.ts +++ b/src/commands/clean.command.ts @@ -1,11 +1,10 @@ import { stat, readdir } from 'fs' - import { Command, CommandRunner } from 'nest-commander' import { Observable, concat } from 'rxjs' import { homedir } from 'os' import { Next, ReactiveSpawn } from '../ReactiveSpawn' -import { log, logError } from 'src/loggers' +import { log, logError } from '../loggers' @Command({ name: 'clean', diff --git a/src/commands/newRoot.command.ts b/src/commands/newRoot.command.ts index b6d3571..1dc53d2 100644 --- a/src/commands/newRoot.command.ts +++ b/src/commands/newRoot.command.ts @@ -1,10 +1,9 @@ -const { version, description } = require('../../package.json'); - import { breakText } from 'src/utility' import { RootCommand, Option, CommandRunner } from 'nest-commander' import { ReactiveSpawn } from '../ReactiveSpawn' -import { log } from 'src/loggers' +import { log } from '../loggers' +const { version, description } = require('../../package.json'); const helptext = ` diff --git a/src/commands/start.command.ts b/src/commands/start.command.ts index 181aa8a..3bc08e2 100644 --- a/src/commands/start.command.ts +++ b/src/commands/start.command.ts @@ -4,7 +4,7 @@ import { concatAll, tap } from 'rxjs/operators' import { defer, Observable, of } from 'rxjs' import { Next, ReactiveSpawn } from '../ReactiveSpawn' -import { log, logError } from 'src/loggers' +import { log, logError } from '../loggers' const INFRA_REF = 'v0.1.5' const FRONTEND_REF = 'v0.1.0-alpha3' diff --git a/src/commands/version.command.ts b/src/commands/version.command.ts index 6828be3..81e4793 100644 --- a/src/commands/version.command.ts +++ b/src/commands/version.command.ts @@ -1,10 +1,8 @@ -const { version } = require('../../package.json'); - import { Command, CommandRunner } from 'nest-commander' import { ReactiveSpawn } from '../ReactiveSpawn' -import { log } from 'src/loggers' - +import { log } from '../loggers' +const { version } = require('../../package.json'); @Command({ name: 'version', diff --git a/src/globals.ts b/src/globals.ts index 77a0160..9dbd6ff 100644 --- a/src/globals.ts +++ b/src/globals.ts @@ -1,8 +1,10 @@ import { mkdir } from 'fs' import { join } from 'path' import { randomUUID } from 'crypto' -import { loggerConsole, logError } from 'src/loggers' import { config } from 'dotenv' + +import { loggerConsole, logError } from './loggers' + config() declare global { From 6720abb19db24eebee4d064a797e5c5b5e98c089 Mon Sep 17 00:00:00 2001 From: Kirk Haines Date: Wed, 23 Aug 2023 07:19:43 -0600 Subject: [PATCH 05/37] Let prettier do its magic. --- src/commands/clean.command.ts | 104 +++++++++++++++++++++----------- src/commands/newRoot.command.ts | 23 ++++--- src/commands/start.command.ts | 40 +++++++----- src/commands/version.command.ts | 6 +- src/globals.ts | 14 +++-- src/loggers.ts | 5 +- 6 files changed, 124 insertions(+), 68 deletions(-) diff --git a/src/commands/clean.command.ts b/src/commands/clean.command.ts index 35b02c3..f7bdd75 100644 --- a/src/commands/clean.command.ts +++ b/src/commands/clean.command.ts @@ -8,7 +8,8 @@ import { log, logError } from '../loggers' @Command({ name: 'clean', - description: 'Shut down Playground docker containers, and clean up the working directory', + description: + 'Shut down Playground docker containers, and clean up the working directory', }) export class CleanCommand extends CommandRunner { constructor(private _spawn: ReactiveSpawn) { @@ -39,9 +40,9 @@ export class CleanCommand extends CommandRunner { this._shutdownRedis(), this._removeWorkingDirectory() ).subscribe({ - next: () => { }, - complete: () => { }, - error: () => { } + next: () => {}, + complete: () => {}, + error: () => {}, }) }) }) @@ -49,7 +50,7 @@ export class CleanCommand extends CommandRunner { private _verifyWorkingDirectoryExistence() { return new Observable((subscriber) => { - stat(globalThis.workingDir, (error, stats) => { + stat(globalThis.workingDir, (error, stats) => { if (error) { logError( `The working directory (${globalThis.workingDir}) can not been found; nothing to clean!` @@ -61,14 +62,14 @@ export class CleanCommand extends CommandRunner { `The working directory (${globalThis.workingDir}) is not a directory; this is an error!` ) globalThis.workingDirExists = false - subscriber.error(); + subscriber.error() } else { readdir(globalThis.workingDir, (err, files) => { if (err) { globalThis.workingDirExists = false - subscriber.error(); + subscriber.error() } - + if (files.length === 0) { globalThis.workingDirExists = false subscriber.next(false) @@ -76,8 +77,7 @@ export class CleanCommand extends CommandRunner { globalThis.workingDirExists = true subscriber.next(true) } - }); - + }) } }) }) @@ -101,16 +101,23 @@ export class CleanCommand extends CommandRunner { return new Observable((subscriber) => { if (globalThis.executionPathExists) { log(`\nShutting down the ERC20 messaging infra...`) - this._spawn.reactify(`cd ${globalThis.executionPath} && docker compose down -v`).subscribe({ - next: (data: Next) => { subscriber.next(data) }, - error: (data) => { subscriber.error(data) }, - complete: () => { subscriber.complete() } - }) + this._spawn + .reactify(`cd ${globalThis.executionPath} && docker compose down -v`) + .subscribe({ + next: (data: Next) => { + subscriber.next(data) + }, + error: (data) => { + subscriber.error(data) + }, + complete: () => { + subscriber.complete() + }, + }) log(`βœ… subnets & TCE are down`) } else { log(`\nβœ… ERC20 messaging infra is not running; subnets & TCE are down`) } - }) } @@ -121,23 +128,38 @@ export class CleanCommand extends CommandRunner { return new Observable((subscriber) => { concat( new Observable((inner_subscriber) => { - this._spawn.reactify(`docker ps --format '{{.Names}}' | grep ${containerName}`).subscribe({ - next: (data: Next) => { - if (data && data.output && `${data.output}`.indexOf(containerName) !== -1) { - container_running = true - } - }, - error: () => { container_running = false; inner_subscriber.complete() /* grep returns an error code 1 if a pattern is missing */ }, - complete: () => { inner_subscriber.complete() } - }) + this._spawn + .reactify(`docker ps --format '{{.Names}}' | grep ${containerName}`) + .subscribe({ + next: (data: Next) => { + if ( + data && + data.output && + `${data.output}`.indexOf(containerName) !== -1 + ) { + container_running = true + } + }, + error: () => { + container_running = false + inner_subscriber.complete() /* grep returns an error code 1 if a pattern is missing */ + }, + complete: () => { + inner_subscriber.complete() + }, + }) }), new Observable((inner_subscriber) => { if (container_running) { log(`\nShutting down the redis server...`) this._spawn.reactify(`docker rm -f ${containerName}`).subscribe({ - next: (data: Next) => { inner_subscriber.next(data) }, - complete: () => { inner_subscriber.complete() } + next: (data: Next) => { + inner_subscriber.next(data) + }, + complete: () => { + inner_subscriber.complete() + }, }) log(`βœ… redis is down`) } else { @@ -148,9 +170,15 @@ export class CleanCommand extends CommandRunner { inner_subscriber.complete() }) ).subscribe({ - next: (data: Next) => { subscriber.next(data) }, - error: () => { subscriber.error() }, - complete: () => { subscriber.complete() } + next: (data: Next) => { + subscriber.next(data) + }, + error: () => { + subscriber.error() + }, + complete: () => { + subscriber.complete() + }, }) }) } @@ -158,14 +186,22 @@ export class CleanCommand extends CommandRunner { private _removeWorkingDirectory() { const homeDir = homedir() return new Observable((subscriber) => { - if (globalThis.workingDirExists && globalThis.workingDir.indexOf(homeDir) !== -1 && globalThis.workingDir !== homeDir) { + if ( + globalThis.workingDirExists && + globalThis.workingDir.indexOf(homeDir) !== -1 && + globalThis.workingDir !== homeDir + ) { log(`\nCleaning up the working directory (${globalThis.workingDir})`) this._spawn.reactify(`rm -rf ${globalThis.workingDir}`).subscribe({ - next: (data: Next) => { subscriber.next(data) }, - complete: () => { subscriber.complete() } + next: (data: Next) => { + subscriber.next(data) + }, + complete: () => { + subscriber.complete() + }, }) log('βœ… Working directory has been removed') } }) } -} \ No newline at end of file +} diff --git a/src/commands/newRoot.command.ts b/src/commands/newRoot.command.ts index 1dc53d2..5f2146e 100644 --- a/src/commands/newRoot.command.ts +++ b/src/commands/newRoot.command.ts @@ -3,7 +3,7 @@ import { RootCommand, Option, CommandRunner } from 'nest-commander' import { ReactiveSpawn } from '../ReactiveSpawn' import { log } from '../loggers' -const { version, description } = require('../../package.json'); +const { version, description } = require('../../package.json') const helptext = ` @@ -17,17 +17,17 @@ These locations can be overridden by setting the environment variables HOME, XDG const columns = process.stdout.columns || 80 @RootCommand({ - description: `${breakText(description, columns)}\n\n${breakText(helptext, columns)}`, + description: `${breakText(description, columns)}\n\n${breakText( + helptext, + columns + )}`, }) - export class NewRootCommand extends CommandRunner { constructor(private _spawn: ReactiveSpawn) { - super() } - async run(): Promise { - } + async run(): Promise {} @Option({ flags: '--version', @@ -40,7 +40,10 @@ export class NewRootCommand extends CommandRunner { @Option({ flags: '-v, --verbose', - description: breakText(`Show more information about the execution of a command`, 39), + description: breakText( + `Show more information about the execution of a command`, + 39 + ), }) doVerbose() { globalThis.verbose = true @@ -48,7 +51,10 @@ export class NewRootCommand extends CommandRunner { @Option({ flags: '-q, --quiet', - description: breakText(`Show minimal onscreen information about the execution of a command`, 39), + description: breakText( + `Show minimal onscreen information about the execution of a command`, + 39 + ), }) doQuiet() { globalThis.quiet = true @@ -61,5 +67,4 @@ export class NewRootCommand extends CommandRunner { doNoLog() { globalThis.no_log = true } - } diff --git a/src/commands/start.command.ts b/src/commands/start.command.ts index 3bc08e2..ef99afc 100644 --- a/src/commands/start.command.ts +++ b/src/commands/start.command.ts @@ -10,9 +10,12 @@ const INFRA_REF = 'v0.1.5' const FRONTEND_REF = 'v0.1.0-alpha3' const EXECUTOR_SERVICE_REF = 'v0.1.1' -@Command({ name: 'start', description: 'Verify that all dependencies are installed, clone any needed repositories, setup the environment, and start all of the docker containers for the Playground' }) +@Command({ + name: 'start', + description: + 'Verify that all dependencies are installed, clone any needed repositories, setup the environment, and start all of the docker containers for the Playground', +}) export class StartCommand extends CommandRunner { - constructor( private _spawn: ReactiveSpawn, private readonly inquirer: InquirerService @@ -72,7 +75,7 @@ export class StartCommand extends CommandRunner { private _verifyDockerInstallation() { let output = null - + return of( new Observable((subscriber) => { this._spawn.reactify('docker --version').subscribe({ @@ -80,14 +83,22 @@ export class StartCommand extends CommandRunner { output = data subscriber.next('') }, - error: () => { subscriber.error() }, - complete: () => { subscriber.complete() }, + error: () => { + subscriber.error() + }, + complete: () => { + subscriber.complete() + }, }) }), - defer(() => of( - output.orign === 'stderr' ? - log(`❌ Docker is not installed!`) : - log(`βœ… Docker`), log(` ${output.output}`))), + defer(() => + of( + output.orign === 'stderr' + ? log(`❌ Docker is not installed!`) + : log(`βœ… Docker`), + log(` ${output.output}`) + ) + ) ).pipe(concatAll()) } @@ -212,7 +223,11 @@ export class StartCommand extends CommandRunner { '.env.executor-service', `${globalThis.workingDir}/executor-service` ), - this._copyEnvFile('.env.secrets', `${globalThis.workingDir}`, `.env.secrets`), + this._copyEnvFile( + '.env.secrets', + `${globalThis.workingDir}`, + `.env.secrets` + ), defer(() => of(log(''))) ).pipe(concatAll()) } @@ -284,9 +299,7 @@ export class StartCommand extends CommandRunner { ), defer(() => of( - log( - `βœ… Contract addresses were retrieved and written to env files` - ), + log(`βœ… Contract addresses were retrieved and written to env files`), log(``) ) ) @@ -386,5 +399,4 @@ export class StartCommand extends CommandRunner { }) ) } - } diff --git a/src/commands/version.command.ts b/src/commands/version.command.ts index 81e4793..b6a3cd2 100644 --- a/src/commands/version.command.ts +++ b/src/commands/version.command.ts @@ -2,13 +2,12 @@ import { Command, CommandRunner } from 'nest-commander' import { ReactiveSpawn } from '../ReactiveSpawn' import { log } from '../loggers' -const { version } = require('../../package.json'); - +const { version } = require('../../package.json') + @Command({ name: 'version', description: `Show topos-playground version (v${version})`, }) - export class VersionCommand extends CommandRunner { constructor(private _spawn: ReactiveSpawn) { super() @@ -18,5 +17,4 @@ export class VersionCommand extends CommandRunner { log((globalThis.quiet ? '' : 'topos-playground version ') + version, true) log(``) } - } diff --git a/src/globals.ts b/src/globals.ts index 9dbd6ff..ef933d9 100644 --- a/src/globals.ts +++ b/src/globals.ts @@ -17,7 +17,7 @@ declare global { var executionPath: string var executionPathExists: boolean var lconsole: typeof loggerConsole - var logFilePath : string + var logFilePath: string var loggerFile } @@ -27,12 +27,15 @@ export function setupGlobals() { let home = process.env.HOME || '.' let data_home = process.env.XDG_DATA_HOME || join(home, '.local', 'share') let state_home = process.env.XDG_STATE_HOME || join(home, '.local', 'state') - + globalThis.workingDir = join(data_home, 'topos-playground') globalThis.logDir = join(state_home, 'topos-playground/logs') globalThis.lconsole = loggerConsole globalThis.logFilePath = join(logDir, `log-${randomUUID()}.log`) - globalThis.executionPath = join(globalThis.workingDir, 'local-erc20-messaging-infra') + globalThis.executionPath = join( + globalThis.workingDir, + 'local-erc20-messaging-infra' + ) globalThis.loggerFile = false @@ -41,7 +44,7 @@ export function setupGlobals() { if (error) { logError(`Could not create working directory (${globalThis.workingDir})`) } - }) + }) // Create the log directory if it does not exist mkdir(globalThis.logDir, { recursive: true }, (error) => { @@ -49,5 +52,4 @@ export function setupGlobals() { logError(`Could not create log directory (${globalThis.logDir})`) } }) - -} \ No newline at end of file +} diff --git a/src/loggers.ts b/src/loggers.ts index 1c5eb54..3915d2f 100644 --- a/src/loggers.ts +++ b/src/loggers.ts @@ -34,7 +34,10 @@ function logConsole() { } function logFile() { - if (!globalThis.loggerFile) globalThis.loggerFile = createLoggerFile(globalThis.no_log ? '/dev/null' : globalThis.logFilePath) + if (!globalThis.loggerFile) + globalThis.loggerFile = createLoggerFile( + globalThis.no_log ? '/dev/null' : globalThis.logFilePath + ) return globalThis.loggerFile } From abf7a6513bb631df1e32301e59773ef9a29ba642 Mon Sep 17 00:00:00 2001 From: Kirk Haines Date: Wed, 23 Aug 2023 07:30:35 -0600 Subject: [PATCH 06/37] Convert uses of snake_case to camelCase. --- src/commands/clean.command.ts | 32 ++++++++++++++++---------------- src/commands/newRoot.command.ts | 2 +- src/globals.ts | 10 +++++----- src/loggers.ts | 2 +- src/utility.ts | 4 ++-- 5 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/commands/clean.command.ts b/src/commands/clean.command.ts index f7bdd75..8cd1f3b 100644 --- a/src/commands/clean.command.ts +++ b/src/commands/clean.command.ts @@ -20,15 +20,15 @@ export class CleanCommand extends CommandRunner { log(`Cleaning up Topos-Playground...`) log(``) - this._verifyWorkingDirectoryExistence().subscribe((working_dir_flag) => { - if (working_dir_flag) { + this._verifyWorkingDirectoryExistence().subscribe((workingDirFlag) => { + if (workingDirFlag) { log(`Found working directory (${globalThis.workingDir})`) } else { log(`Working directory (${globalThis.workingDir}) empty or not found`) } - this._verifyExecutionPathExistence().subscribe((execution_path_flag) => { - if (execution_path_flag) { + this._verifyExecutionPathExistence().subscribe((executionPathFlag) => { + if (executionPathFlag) { log(`Found execution path (${globalThis.executionPath})`) } else { log(`Execution path (${globalThis.executionPath}) not found`) @@ -123,11 +123,11 @@ export class CleanCommand extends CommandRunner { private _shutdownRedis() { const containerName = 'redis-stack-server' - let container_running = false + let containerRunning = false return new Observable((subscriber) => { concat( - new Observable((inner_subscriber) => { + new Observable((innerSubscriber) => { this._spawn .reactify(`docker ps --format '{{.Names}}' | grep ${containerName}`) .subscribe({ @@ -137,28 +137,28 @@ export class CleanCommand extends CommandRunner { data.output && `${data.output}`.indexOf(containerName) !== -1 ) { - container_running = true + containerRunning = true } }, error: () => { - container_running = false - inner_subscriber.complete() /* grep returns an error code 1 if a pattern is missing */ + containerRunning = false + innerSubscriber.complete() /* grep returns an error code 1 if a pattern is missing */ }, complete: () => { - inner_subscriber.complete() + innerSubscriber.complete() }, }) }), - new Observable((inner_subscriber) => { - if (container_running) { + new Observable((innerSubscriber) => { + if (containerRunning) { log(`\nShutting down the redis server...`) this._spawn.reactify(`docker rm -f ${containerName}`).subscribe({ next: (data: Next) => { - inner_subscriber.next(data) + innerSubscriber.next(data) }, complete: () => { - inner_subscriber.complete() + innerSubscriber.complete() }, }) log(`βœ… redis is down`) @@ -166,8 +166,8 @@ export class CleanCommand extends CommandRunner { log(`\n βœ… redis is not running; nothing to shut down`) } }), - new Observable((inner_subscriber) => { - inner_subscriber.complete() + new Observable((innerSubscriber) => { + innerSubscriber.complete() }) ).subscribe({ next: (data: Next) => { diff --git a/src/commands/newRoot.command.ts b/src/commands/newRoot.command.ts index 5f2146e..aba4805 100644 --- a/src/commands/newRoot.command.ts +++ b/src/commands/newRoot.command.ts @@ -65,6 +65,6 @@ export class NewRootCommand extends CommandRunner { description: `Do not write a log file`, }) doNoLog() { - globalThis.no_log = true + globalThis.noLog = true } } diff --git a/src/globals.ts b/src/globals.ts index ef933d9..f66dd06 100644 --- a/src/globals.ts +++ b/src/globals.ts @@ -10,7 +10,7 @@ config() declare global { var verbose: boolean var quiet: boolean - var no_log: boolean + var noLog: boolean var workingDir: string var workingDirExists: boolean var logDir: string @@ -25,11 +25,11 @@ export function setupGlobals() { // Setup global configuration let home = process.env.HOME || '.' - let data_home = process.env.XDG_DATA_HOME || join(home, '.local', 'share') - let state_home = process.env.XDG_STATE_HOME || join(home, '.local', 'state') + let dataHome = process.env.XDG_DATA_HOME || join(home, '.local', 'share') + let stateHome = process.env.XDG_STATE_HOME || join(home, '.local', 'state') - globalThis.workingDir = join(data_home, 'topos-playground') - globalThis.logDir = join(state_home, 'topos-playground/logs') + globalThis.workingDir = join(dataHome, 'topos-playground') + globalThis.logDir = join(stateHome, 'topos-playground/logs') globalThis.lconsole = loggerConsole globalThis.logFilePath = join(logDir, `log-${randomUUID()}.log`) globalThis.executionPath = join( diff --git a/src/loggers.ts b/src/loggers.ts index 3915d2f..be11e2a 100644 --- a/src/loggers.ts +++ b/src/loggers.ts @@ -36,7 +36,7 @@ function logConsole() { function logFile() { if (!globalThis.loggerFile) globalThis.loggerFile = createLoggerFile( - globalThis.no_log ? '/dev/null' : globalThis.logFilePath + globalThis.noLog ? '/dev/null' : globalThis.logFilePath ) return globalThis.loggerFile diff --git a/src/utility.ts b/src/utility.ts index 7eab3b8..cdeda45 100644 --- a/src/utility.ts +++ b/src/utility.ts @@ -36,8 +36,8 @@ export function breakText(str: string, n: number = 60): string { word = "" } else { if ((((line.length + word.length) > n) && ((n - line.length) > maxWordLength)) || i === str.length - 1) { - let first_character = word[0] - if (first_character.toLowerCase() != first_character.toUpperCase() && word.length > maxWordLength) { + let firstCharacter = word[0] + if (firstCharacter.toLowerCase() != firstCharacter.toUpperCase() && word.length > maxWordLength) { let part = word.substring(0, maxWordLength - 1) let remaining = word.substring(maxWordLength - 1) lines.push(line + part + (remaining.length > 0 ? "-" : "")) From c5708ccb952fe32a3707e4d3a19afbfd8c7181ab Mon Sep 17 00:00:00 2001 From: Kirk Haines Date: Wed, 23 Aug 2023 07:49:53 -0600 Subject: [PATCH 07/37] Update src/commands/clean.command.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: SΓ©bastien Dan --- src/commands/clean.command.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/clean.command.ts b/src/commands/clean.command.ts index 8cd1f3b..5462165 100644 --- a/src/commands/clean.command.ts +++ b/src/commands/clean.command.ts @@ -53,7 +53,7 @@ export class CleanCommand extends CommandRunner { stat(globalThis.workingDir, (error, stats) => { if (error) { logError( - `The working directory (${globalThis.workingDir}) can not been found; nothing to clean!` + `The working directory (${globalThis.workingDir}) cannot be found; nothing to clean!` ) globalThis.workingDirExists = false subscriber.next(false) From d02b2541536bf9f54e72e463638a460e6928e271 Mon Sep 17 00:00:00 2001 From: Kirk Haines Date: Wed, 23 Aug 2023 08:03:01 -0600 Subject: [PATCH 08/37] Update src/commands/clean.command.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: SΓ©bastien Dan --- src/commands/clean.command.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/clean.command.ts b/src/commands/clean.command.ts index 5462165..47316cf 100644 --- a/src/commands/clean.command.ts +++ b/src/commands/clean.command.ts @@ -169,7 +169,7 @@ export class CleanCommand extends CommandRunner { new Observable((innerSubscriber) => { innerSubscriber.complete() }) - ).subscribe({ + ).subscribe(subscriber) next: (data: Next) => { subscriber.next(data) }, From 0d5d35768d26b5b25be84260867f75441ab18688 Mon Sep 17 00:00:00 2001 From: Kirk Haines Date: Wed, 23 Aug 2023 14:54:53 -0600 Subject: [PATCH 09/37] Simplify subscribe without callbacks. --- src/commands/clean.command.ts | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/src/commands/clean.command.ts b/src/commands/clean.command.ts index 47316cf..cd1e57a 100644 --- a/src/commands/clean.command.ts +++ b/src/commands/clean.command.ts @@ -39,11 +39,7 @@ export class CleanCommand extends CommandRunner { this._shutdownFullMsgProtocolInfra(), this._shutdownRedis(), this._removeWorkingDirectory() - ).subscribe({ - next: () => {}, - complete: () => {}, - error: () => {}, - }) + ).subscribe() }) }) } @@ -170,16 +166,6 @@ export class CleanCommand extends CommandRunner { innerSubscriber.complete() }) ).subscribe(subscriber) - next: (data: Next) => { - subscriber.next(data) - }, - error: () => { - subscriber.error() - }, - complete: () => { - subscriber.complete() - }, - }) }) } From f94c3137a0795bb3264c08c2b509723a6388f01c Mon Sep 17 00:00:00 2001 From: Kirk Haines Date: Wed, 23 Aug 2023 15:17:28 -0600 Subject: [PATCH 10/37] Cleaned up the directory verification methods and messaging. --- src/commands/clean.command.ts | 43 +++++++++++++++++------------------ 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/src/commands/clean.command.ts b/src/commands/clean.command.ts index cd1e57a..f6fef1c 100644 --- a/src/commands/clean.command.ts +++ b/src/commands/clean.command.ts @@ -20,19 +20,8 @@ export class CleanCommand extends CommandRunner { log(`Cleaning up Topos-Playground...`) log(``) - this._verifyWorkingDirectoryExistence().subscribe((workingDirFlag) => { - if (workingDirFlag) { - log(`Found working directory (${globalThis.workingDir})`) - } else { - log(`Working directory (${globalThis.workingDir}) empty or not found`) - } - - this._verifyExecutionPathExistence().subscribe((executionPathFlag) => { - if (executionPathFlag) { - log(`Found execution path (${globalThis.executionPath})`) - } else { - log(`Execution path (${globalThis.executionPath}) not found`) - } + this._verifyWorkingDirectoryExistence().subscribe(() => { + this._verifyExecutionPathExistence().subscribe(() => { // Coordinate the steps to clean up the environment concat( @@ -48,11 +37,10 @@ export class CleanCommand extends CommandRunner { return new Observable((subscriber) => { stat(globalThis.workingDir, (error, stats) => { if (error) { - logError( - `The working directory (${globalThis.workingDir}) cannot be found; nothing to clean!` - ) globalThis.workingDirExists = false - subscriber.next(false) + log(`Working directory (${globalThis.workingDir}) is not found; nothing to clean.`) + subscriber.next() + subscriber.complete() } else if (!stats.isDirectory()) { logError( `The working directory (${globalThis.workingDir}) is not a directory; this is an error!` @@ -63,15 +51,22 @@ export class CleanCommand extends CommandRunner { readdir(globalThis.workingDir, (err, files) => { if (err) { globalThis.workingDirExists = false + logError( + `Error while trying to read the working directory (${globalThis.workingDir})` + ) subscriber.error() } if (files.length === 0) { globalThis.workingDirExists = false - subscriber.next(false) + log(`Working directory (${globalThis.workingDir}) is empty; nothing to clean.`) + subscriber.next() + subscriber.complete() } else { globalThis.workingDirExists = true - subscriber.next(true) + log(`Found working directory (${globalThis.workingDir})`) + subscriber.next() + subscriber.complete() } }) } @@ -84,10 +79,14 @@ export class CleanCommand extends CommandRunner { stat(globalThis.executionPath, (error) => { if (error) { globalThis.executionPathExists = false - subscriber.next(false) + log(`Execution path (${globalThis.executionPath}) not found`) + subscriber.next() + subscriber.complete() } else { globalThis.executionPathExists = true - subscriber.next(true) + log(`Found execution path (${globalThis.executionPath})`) + subscriber.next() + subscriber.complete() } }) }) @@ -159,7 +158,7 @@ export class CleanCommand extends CommandRunner { }) log(`βœ… redis is down`) } else { - log(`\n βœ… redis is not running; nothing to shut down`) + log(`\nβœ… redis is not running; nothing to shut down`) } }), new Observable((innerSubscriber) => { From 1f17453d0fc33cb6e487630dcfc16d75579d7598 Mon Sep 17 00:00:00 2001 From: Kirk Haines Date: Wed, 23 Aug 2023 17:03:53 -0600 Subject: [PATCH 11/37] Remove the \n in strings and simplify a tiny bit. --- src/commands/clean.command.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/commands/clean.command.ts b/src/commands/clean.command.ts index f6fef1c..68069bd 100644 --- a/src/commands/clean.command.ts +++ b/src/commands/clean.command.ts @@ -94,8 +94,9 @@ export class CleanCommand extends CommandRunner { private _shutdownFullMsgProtocolInfra() { return new Observable((subscriber) => { + log('') if (globalThis.executionPathExists) { - log(`\nShutting down the ERC20 messaging infra...`) + log(`Shutting down the ERC20 messaging infra...`) this._spawn .reactify(`cd ${globalThis.executionPath} && docker compose down -v`) .subscribe({ @@ -111,7 +112,7 @@ export class CleanCommand extends CommandRunner { }) log(`βœ… subnets & TCE are down`) } else { - log(`\nβœ… ERC20 messaging infra is not running; subnets & TCE are down`) + log(`βœ… ERC20 messaging infra is not running; subnets & TCE are down`) } }) } @@ -145,8 +146,9 @@ export class CleanCommand extends CommandRunner { }) }), new Observable((innerSubscriber) => { + log('') if (containerRunning) { - log(`\nShutting down the redis server...`) + log(`Shutting down the redis server...`) this._spawn.reactify(`docker rm -f ${containerName}`).subscribe({ next: (data: Next) => { @@ -158,7 +160,7 @@ export class CleanCommand extends CommandRunner { }) log(`βœ… redis is down`) } else { - log(`\nβœ… redis is not running; nothing to shut down`) + log(`βœ… redis is not running; nothing to shut down`) } }), new Observable((innerSubscriber) => { @@ -176,7 +178,8 @@ export class CleanCommand extends CommandRunner { globalThis.workingDir.indexOf(homeDir) !== -1 && globalThis.workingDir !== homeDir ) { - log(`\nCleaning up the working directory (${globalThis.workingDir})`) + log('') + log(`Cleaning up the working directory (${globalThis.workingDir})`) this._spawn.reactify(`rm -rf ${globalThis.workingDir}`).subscribe({ next: (data: Next) => { subscriber.next(data) From b4923021a73b91960424c2ab6b4105a86bf9d79c Mon Sep 17 00:00:00 2001 From: Kirk Haines Date: Wed, 23 Aug 2023 17:34:19 -0600 Subject: [PATCH 12/37] Little cleanups. --- src/commands/clean.command.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/commands/clean.command.ts b/src/commands/clean.command.ts index 68069bd..a990881 100644 --- a/src/commands/clean.command.ts +++ b/src/commands/clean.command.ts @@ -22,7 +22,6 @@ export class CleanCommand extends CommandRunner { this._verifyWorkingDirectoryExistence().subscribe(() => { this._verifyExecutionPathExistence().subscribe(() => { - // Coordinate the steps to clean up the environment concat( this._shutdownFullMsgProtocolInfra(), @@ -38,7 +37,9 @@ export class CleanCommand extends CommandRunner { stat(globalThis.workingDir, (error, stats) => { if (error) { globalThis.workingDirExists = false - log(`Working directory (${globalThis.workingDir}) is not found; nothing to clean.`) + log( + `Working directory (${globalThis.workingDir}) is not found; nothing to clean.` + ) subscriber.next() subscriber.complete() } else if (!stats.isDirectory()) { @@ -59,7 +60,9 @@ export class CleanCommand extends CommandRunner { if (files.length === 0) { globalThis.workingDirExists = false - log(`Working directory (${globalThis.workingDir}) is empty; nothing to clean.`) + log( + `Working directory (${globalThis.workingDir}) is empty; nothing to clean.` + ) subscriber.next() subscriber.complete() } else { @@ -113,6 +116,7 @@ export class CleanCommand extends CommandRunner { log(`βœ… subnets & TCE are down`) } else { log(`βœ… ERC20 messaging infra is not running; subnets & TCE are down`) + subscriber.complete() } }) } @@ -162,8 +166,6 @@ export class CleanCommand extends CommandRunner { } else { log(`βœ… redis is not running; nothing to shut down`) } - }), - new Observable((innerSubscriber) => { innerSubscriber.complete() }) ).subscribe(subscriber) From 05eebcbd9e0e25c2ac3c8d5f9bdd2b2dd55c6484 Mon Sep 17 00:00:00 2001 From: Kirk Haines Date: Wed, 23 Aug 2023 22:09:43 -0600 Subject: [PATCH 13/37] Minor cleanup of quiet + version handling. --- src/commands/newRoot.command.ts | 3 ++- src/commands/version.command.ts | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/commands/newRoot.command.ts b/src/commands/newRoot.command.ts index aba4805..1314a90 100644 --- a/src/commands/newRoot.command.ts +++ b/src/commands/newRoot.command.ts @@ -15,6 +15,7 @@ These locations can be overridden by setting the environment variables HOME, XDG `.trim() const columns = process.stdout.columns || 80 +const overrideQuiet = true @RootCommand({ description: `${breakText(description, columns)}\n\n${breakText( @@ -34,7 +35,7 @@ export class NewRootCommand extends CommandRunner { description: `Show topos-playground version (v${version})`, }) doVersion() { - log((globalThis.quiet ? '' : 'topos-playground version ') + version, true) + log((globalThis.quiet ? '' : 'topos-playground version ') + version, overrideQuiet) log(``) } diff --git a/src/commands/version.command.ts b/src/commands/version.command.ts index b6a3cd2..e13040c 100644 --- a/src/commands/version.command.ts +++ b/src/commands/version.command.ts @@ -4,6 +4,8 @@ import { ReactiveSpawn } from '../ReactiveSpawn' import { log } from '../loggers' const { version } = require('../../package.json') +const overrideQuiet = true + @Command({ name: 'version', description: `Show topos-playground version (v${version})`, @@ -14,7 +16,7 @@ export class VersionCommand extends CommandRunner { } async run(): Promise { - log((globalThis.quiet ? '' : 'topos-playground version ') + version, true) + log((globalThis.quiet ? '' : 'topos-playground version ') + version, overrideQuiet) log(``) } } From 38e89d8dc1b32388f3e17735625b8498344fc846 Mon Sep 17 00:00:00 2001 From: Kirk Haines Date: Wed, 23 Aug 2023 22:14:37 -0600 Subject: [PATCH 14/37] Clean up a subscribe call. --- src/commands/clean.command.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/commands/clean.command.ts b/src/commands/clean.command.ts index a990881..b982976 100644 --- a/src/commands/clean.command.ts +++ b/src/commands/clean.command.ts @@ -182,14 +182,7 @@ export class CleanCommand extends CommandRunner { ) { log('') log(`Cleaning up the working directory (${globalThis.workingDir})`) - this._spawn.reactify(`rm -rf ${globalThis.workingDir}`).subscribe({ - next: (data: Next) => { - subscriber.next(data) - }, - complete: () => { - subscriber.complete() - }, - }) + this._spawn.reactify(`rm -rf ${globalThis.workingDir}`).subscribe() log('βœ… Working directory has been removed') } }) From 63caa0d073c890118e1fafbc3be00f59f8d535fc Mon Sep 17 00:00:00 2001 From: Kirk Haines Date: Wed, 23 Aug 2023 22:20:50 -0600 Subject: [PATCH 15/37] Added a little more self-explanatory name than globalThis.lconsole. --- src/globals.ts | 4 ++-- src/loggers.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/globals.ts b/src/globals.ts index f66dd06..0d9f18a 100644 --- a/src/globals.ts +++ b/src/globals.ts @@ -16,7 +16,7 @@ declare global { var logDir: string var executionPath: string var executionPathExists: boolean - var lconsole: typeof loggerConsole + var loggerConsoleVar: typeof loggerConsole var logFilePath: string var loggerFile } @@ -30,7 +30,7 @@ export function setupGlobals() { globalThis.workingDir = join(dataHome, 'topos-playground') globalThis.logDir = join(stateHome, 'topos-playground/logs') - globalThis.lconsole = loggerConsole + globalThis.loggerConsoleVar = loggerConsole globalThis.logFilePath = join(logDir, `log-${randomUUID()}.log`) globalThis.executionPath = join( globalThis.workingDir, diff --git a/src/loggers.ts b/src/loggers.ts index be11e2a..87f9f78 100644 --- a/src/loggers.ts +++ b/src/loggers.ts @@ -30,7 +30,7 @@ export function createLoggerFile(logFilePath: string) { } function logConsole() { - return globalThis.lconsole + return globalThis.loggerConsoleVar } function logFile() { From 850690fb4a57470e1661d64537755ce35acd6e73 Mon Sep 17 00:00:00 2001 From: Kirk Haines Date: Wed, 23 Aug 2023 22:40:02 -0600 Subject: [PATCH 16/37] Refactored globals.ts -> initializers.ts --- src/{globals.ts => initializers.ts} | 6 ++++-- src/main.ts | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) rename src/{globals.ts => initializers.ts} (95%) diff --git a/src/globals.ts b/src/initializers.ts similarity index 95% rename from src/globals.ts rename to src/initializers.ts index 0d9f18a..a1ff86d 100644 --- a/src/globals.ts +++ b/src/initializers.ts @@ -21,7 +21,7 @@ declare global { var loggerFile } -export function setupGlobals() { +export function initializeGlobals() { // Setup global configuration let home = process.env.HOME || '.' @@ -38,7 +38,9 @@ export function setupGlobals() { ) globalThis.loggerFile = false +} +export function initializeDirectories() { // Create the working directory if it does not exist mkdir(globalThis.workingDir, { recursive: true }, (error) => { if (error) { @@ -52,4 +54,4 @@ export function setupGlobals() { logError(`Could not create log directory (${globalThis.logDir})`) } }) -} +} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index bc2d5ad..d44e544 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,11 +1,13 @@ #!/usr/bin/env node import { CommandFactory } from 'nest-commander' -import { setupGlobals } from 'src/globals' + +import { initializeGlobals, initializeDirectories } from './initializers' import { AppModule } from './app.module' async function bootstrap() { await CommandFactory.run(AppModule) } -setupGlobals() +initializeGlobals() +initializeDirectories() bootstrap() From d9d72f47269c2654ac63a23962b1b890915fa95f Mon Sep 17 00:00:00 2001 From: Kirk Haines Date: Wed, 23 Aug 2023 22:45:52 -0600 Subject: [PATCH 17/37] Renamed the getters for the logging console and the logging file to more clearly indicate that those are getter methods. --- src/loggers.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/loggers.ts b/src/loggers.ts index 87f9f78..caecefb 100644 --- a/src/loggers.ts +++ b/src/loggers.ts @@ -29,11 +29,11 @@ export function createLoggerFile(logFilePath: string) { }) } -function logConsole() { +function getLogConsole() { return globalThis.loggerConsoleVar } -function logFile() { +function getLogFile() { if (!globalThis.loggerFile) globalThis.loggerFile = createLoggerFile( globalThis.noLog ? '/dev/null' : globalThis.logFilePath @@ -46,8 +46,8 @@ export function log(logMessage: string, overrideQuiet: boolean = false) { let lines = logMessage.split('\n') for (let line of lines) { - if (overrideQuiet || !globalThis.quiet) logConsole().info(line) - logFile().info(line) + if (overrideQuiet || !globalThis.quiet) getLogConsole().info(line) + getLogFile().info(line) } } @@ -55,10 +55,10 @@ export function logError(errorMessage: string) { let lines = errorMessage.split('\n') for (let line of lines) { - logConsole().error(line) - logFile().error(line) + getLogConsole().error(line) + getLogFile().error(line) } - logConsole().error(`Find more details in ${globalThis.logFilePath}`) - logFile().error(`Find more details in ${globalThis.logFilePath}`) + getLogConsole().error(`Find more details in ${globalThis.logFilePath}`) + getLogFile().error(`Find more details in ${globalThis.logFilePath}`) } From ef5cec59596402ff87053dc519f1e7bea0f16098 Mon Sep 17 00:00:00 2001 From: Kirk Haines Date: Thu, 24 Aug 2023 06:39:36 -0600 Subject: [PATCH 18/37] Simplify another subscribe() call. --- src/commands/clean.command.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/commands/clean.command.ts b/src/commands/clean.command.ts index b982976..eb983ba 100644 --- a/src/commands/clean.command.ts +++ b/src/commands/clean.command.ts @@ -154,14 +154,7 @@ export class CleanCommand extends CommandRunner { if (containerRunning) { log(`Shutting down the redis server...`) - this._spawn.reactify(`docker rm -f ${containerName}`).subscribe({ - next: (data: Next) => { - innerSubscriber.next(data) - }, - complete: () => { - innerSubscriber.complete() - }, - }) + this._spawn.reactify(`docker rm -f ${containerName}`).subscribe() log(`βœ… redis is down`) } else { log(`βœ… redis is not running; nothing to shut down`) From 77dbf39d9e77227d7c5b11f838de8dfe6c5d911a Mon Sep 17 00:00:00 2001 From: Kirk Haines Date: Thu, 24 Aug 2023 06:47:26 -0600 Subject: [PATCH 19/37] Simplify _shutdownRedis(). --- src/commands/clean.command.ts | 59 ++++++++++++++--------------------- 1 file changed, 24 insertions(+), 35 deletions(-) diff --git a/src/commands/clean.command.ts b/src/commands/clean.command.ts index eb983ba..7766846 100644 --- a/src/commands/clean.command.ts +++ b/src/commands/clean.command.ts @@ -123,45 +123,34 @@ export class CleanCommand extends CommandRunner { private _shutdownRedis() { const containerName = 'redis-stack-server' - let containerRunning = false return new Observable((subscriber) => { - concat( - new Observable((innerSubscriber) => { - this._spawn - .reactify(`docker ps --format '{{.Names}}' | grep ${containerName}`) - .subscribe({ - next: (data: Next) => { - if ( - data && - data.output && - `${data.output}`.indexOf(containerName) !== -1 - ) { - containerRunning = true - } - }, - error: () => { - containerRunning = false - innerSubscriber.complete() /* grep returns an error code 1 if a pattern is missing */ - }, - complete: () => { - innerSubscriber.complete() - }, - }) - }), - new Observable((innerSubscriber) => { - log('') - if (containerRunning) { - log(`Shutting down the redis server...`) + this._spawn + .reactify(`docker ps --format '{{.Names}}' | grep ${containerName}`) + .subscribe({ + next: (data: Next) => { + if ( + data && + data.output && + `${data.output}`.indexOf(containerName) !== -1 + ) { + log('') + log(`Shutting down the redis server...`) - this._spawn.reactify(`docker rm -f ${containerName}`).subscribe() - log(`βœ… redis is down`) - } else { - log(`βœ… redis is not running; nothing to shut down`) - } - innerSubscriber.complete() + this._spawn.reactify(`docker rm -f ${containerName}`).subscribe() + log(`βœ… redis is down`) + } else { + log(`βœ… redis is not running; nothing to shut down`) + } + subscriber.complete() + }, + error: () => { + subscriber.complete() /* grep returns an error code 1 if a pattern is missing */ + }, + complete: () => { + subscriber.complete() + }, }) - ).subscribe(subscriber) }) } From 147b3898a8d3cb460bf6b1a443b59e7bfaca8c6d Mon Sep 17 00:00:00 2001 From: Kirk Haines Date: Thu, 24 Aug 2023 07:10:58 -0600 Subject: [PATCH 20/37] Rename the new root command from NewRootCommand to just root, per PR request. --- src/app.module.ts | 4 ++-- src/commands/{newRoot.command.ts => root.command.ts} | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename src/commands/{newRoot.command.ts => root.command.ts} (97%) diff --git a/src/app.module.ts b/src/app.module.ts index 9d0fb2f..11ba750 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -3,11 +3,11 @@ import { Module } from '@nestjs/common' import { CleanCommand } from './commands/clean.command' import { StartCommand } from './commands/start.command' import { VersionCommand } from './commands/version.command' -import { NewRootCommand } from './commands/newRoot.command' +import { Root } from './commands/root.command' import { ReactiveSpawn } from './ReactiveSpawn' @Module({ - providers: [ReactiveSpawn, CleanCommand, StartCommand, VersionCommand, NewRootCommand], + providers: [ReactiveSpawn, CleanCommand, StartCommand, VersionCommand, Root], }) export class AppModule {} diff --git a/src/commands/newRoot.command.ts b/src/commands/root.command.ts similarity index 97% rename from src/commands/newRoot.command.ts rename to src/commands/root.command.ts index 1314a90..cc6c33d 100644 --- a/src/commands/newRoot.command.ts +++ b/src/commands/root.command.ts @@ -23,7 +23,7 @@ const overrideQuiet = true columns )}`, }) -export class NewRootCommand extends CommandRunner { +export class Root extends CommandRunner { constructor(private _spawn: ReactiveSpawn) { super() } From 27b78580be47bb915cf914864afa6b489290da0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Dan?= Date: Fri, 25 Aug 2023 10:18:03 +0200 Subject: [PATCH 21/37] chore: clean and refactor a few things --- src/ReactiveSpawn.ts | 5 +- src/commands/clean.command.ts | 39 ++++++----- src/commands/root.command.ts | 13 ++-- src/commands/start.command.ts | 118 +++++++++++++------------------- src/commands/version.command.ts | 10 +-- src/initializers.ts | 6 +- src/loggers.ts | 5 +- 7 files changed, 89 insertions(+), 107 deletions(-) diff --git a/src/ReactiveSpawn.ts b/src/ReactiveSpawn.ts index 57b061d..5ef8a82 100644 --- a/src/ReactiveSpawn.ts +++ b/src/ReactiveSpawn.ts @@ -1,7 +1,8 @@ import { ChildProcess, spawn } from 'child_process' import { userInfo } from 'os' import { Observable } from 'rxjs' -import { log, logError } from 'src/loggers' + +import { log } from './loggers' export interface Next { origin: 'stdout' | 'stderr' @@ -26,7 +27,7 @@ export class ReactiveSpawn { const output = data.toString() subscriber.next({ origin: 'stdout', output }) }) - + childProcess.stderr.on('data', (data: Buffer) => { const output = data.toString() subscriber.next({ origin: 'stderr', output }) diff --git a/src/commands/clean.command.ts b/src/commands/clean.command.ts index 7766846..54ee973 100644 --- a/src/commands/clean.command.ts +++ b/src/commands/clean.command.ts @@ -16,7 +16,7 @@ export class CleanCommand extends CommandRunner { super() } - async run(): Promise { + async run() { log(`Cleaning up Topos-Playground...`) log(``) @@ -24,7 +24,7 @@ export class CleanCommand extends CommandRunner { this._verifyExecutionPathExistence().subscribe(() => { // Coordinate the steps to clean up the environment concat( - this._shutdownFullMsgProtocolInfra(), + this._shutdownERC20MessagingProtocolInfra(), this._shutdownRedis(), this._removeWorkingDirectory() ).subscribe() @@ -95,24 +95,14 @@ export class CleanCommand extends CommandRunner { }) } - private _shutdownFullMsgProtocolInfra() { + private _shutdownERC20MessagingProtocolInfra() { return new Observable((subscriber) => { log('') if (globalThis.executionPathExists) { log(`Shutting down the ERC20 messaging infra...`) this._spawn .reactify(`cd ${globalThis.executionPath} && docker compose down -v`) - .subscribe({ - next: (data: Next) => { - subscriber.next(data) - }, - error: (data) => { - subscriber.error(data) - }, - complete: () => { - subscriber.complete() - }, - }) + .subscribe(subscriber) log(`βœ… subnets & TCE are down`) } else { log(`βœ… ERC20 messaging infra is not running; subnets & TCE are down`) @@ -137,15 +127,18 @@ export class CleanCommand extends CommandRunner { log('') log(`Shutting down the redis server...`) - this._spawn.reactify(`docker rm -f ${containerName}`).subscribe() - log(`βœ… redis is down`) + this._spawn.reactify(`docker rm -f ${containerName}`).subscribe({ + complete: () => { + log(`βœ… redis is down`) + }, + }) } else { log(`βœ… redis is not running; nothing to shut down`) } subscriber.complete() }, error: () => { - subscriber.complete() /* grep returns an error code 1 if a pattern is missing */ + subscriber.complete() }, complete: () => { subscriber.complete() @@ -164,8 +157,16 @@ export class CleanCommand extends CommandRunner { ) { log('') log(`Cleaning up the working directory (${globalThis.workingDir})`) - this._spawn.reactify(`rm -rf ${globalThis.workingDir}`).subscribe() - log('βœ… Working directory has been removed') + this._spawn.reactify(`rm -rf ${globalThis.workingDir}`).subscribe({ + complete: () => { + log('βœ… Working directory has been removed') + subscriber.complete() + }, + }) + } else { + log('') + log(`βœ… Working direction does not exist; nothing to clean`) + subscriber.complete() } }) } diff --git a/src/commands/root.command.ts b/src/commands/root.command.ts index cc6c33d..9ee6dfc 100644 --- a/src/commands/root.command.ts +++ b/src/commands/root.command.ts @@ -1,9 +1,8 @@ -import { breakText } from 'src/utility' import { RootCommand, Option, CommandRunner } from 'nest-commander' -import { ReactiveSpawn } from '../ReactiveSpawn' +const { description, version } = require('../../package.json') import { log } from '../loggers' -const { version, description } = require('../../package.json') +import { breakText } from '../utility' const helptext = ` @@ -13,7 +12,6 @@ topos-playground follows the XDG Base Directory Specification, which means that These locations can be overridden by setting the environment variables HOME, XDG_DATA_HOME, and XDG_STATE_HOME. `.trim() - const columns = process.stdout.columns || 80 const overrideQuiet = true @@ -24,7 +22,7 @@ const overrideQuiet = true )}`, }) export class Root extends CommandRunner { - constructor(private _spawn: ReactiveSpawn) { + constructor() { super() } @@ -35,7 +33,10 @@ export class Root extends CommandRunner { description: `Show topos-playground version (v${version})`, }) doVersion() { - log((globalThis.quiet ? '' : 'topos-playground version ') + version, overrideQuiet) + log( + (globalThis.quiet ? '' : 'topos-playground version ') + version, + overrideQuiet + ) log(``) } diff --git a/src/commands/start.command.ts b/src/commands/start.command.ts index ef99afc..6f8a127 100644 --- a/src/commands/start.command.ts +++ b/src/commands/start.command.ts @@ -1,10 +1,10 @@ import { readFile, stat } from 'fs' import { Command, CommandRunner, InquirerService } from 'nest-commander' +import { concat, defer, Observable, of } from 'rxjs' import { concatAll, tap } from 'rxjs/operators' -import { defer, Observable, of } from 'rxjs' -import { Next, ReactiveSpawn } from '../ReactiveSpawn' import { log, logError } from '../loggers' +import { Next, ReactiveSpawn } from '../ReactiveSpawn' const INFRA_REF = 'v0.1.5' const FRONTEND_REF = 'v0.1.0-alpha3' @@ -27,7 +27,7 @@ export class StartCommand extends CommandRunner { log(`Starting Topos-Playground...`) log(``) - of( + concat( this._verifyDependencyInstallation(), this._createWorkingDirectoryIfInexistant(), this._cloneGitRepositories(), @@ -37,76 +37,52 @@ export class StartCommand extends CommandRunner { this._runRedis(), this._runExecutorService(), this._rundDappFrontendService() - ) - .pipe(concatAll()) - .subscribe({ - complete: () => { - log(`πŸ”₯ Everything is done! πŸ”₯`) - log(``) - log( - `πŸš€ Start sending ERC20 tokens across subnet by accessing the dApp Frontend at http://localhost:3001` - ) - log(``) - log( - `ℹ️ Ctrl/cmd-c will shut down the dApp Frontend and the Executor Service BUT will keep subnets and the TCE running (use the clean command to shut them down)` - ) - log(`ℹ️ Logs were written to ${logFilePath}`) - }, - error: () => { - logError(`❌ Error`) - }, - next: (data: Next) => { - if (data && data.hasOwnProperty('output')) { - log(`${data.output}`) - } - }, - }) + ).subscribe({ + complete: () => { + log(`πŸ”₯ Everything is done! πŸ”₯`) + log(``) + log( + `πŸš€ Start sending ERC20 tokens across subnet by accessing the dApp Frontend at http://localhost:3001` + ) + log(``) + log( + `ℹ️ Ctrl/cmd-c will shut down the dApp Frontend and the Executor Service BUT will keep subnets and the TCE running (use the clean command to shut them down)` + ) + log(`ℹ️ Logs were written to ${logFilePath}`) + }, + error: () => { + logError(`❌ Error`) + }, + next: (data: Next) => { + if (data && data.hasOwnProperty('output')) { + log(`${data.output}`) + } + }, + }) } private _verifyDependencyInstallation() { - return of( + return concat( defer(() => of(log('Verifying dependency installation...'))), this._verifyDockerInstallation(), this._verifyGitInstallation(), this._verifyNodeJSInstallation(), defer(() => of(log(''))) - ).pipe(concatAll()) + ) } private _verifyDockerInstallation() { - let output = null - - return of( - new Observable((subscriber) => { - this._spawn.reactify('docker --version').subscribe({ - next: (data: Next) => { - output = data - subscriber.next('') - }, - error: () => { - subscriber.error() - }, - complete: () => { - subscriber.complete() - }, - }) - }), - defer(() => - of( - output.orign === 'stderr' - ? log(`❌ Docker is not installed!`) - : log(`βœ… Docker`), - log(` ${output.output}`) - ) - ) - ).pipe(concatAll()) + return concat( + this._spawn.reactify('docker --version'), + defer(() => of(log('βœ… Docker'))) + ) } private _verifyGitInstallation() { - return of( + return concat( this._spawn.reactify('git --version'), defer(() => of(log('βœ… Git'))) - ).pipe(concatAll()) + ) } private _verifyNodeJSInstallation() { @@ -147,7 +123,7 @@ export class StartCommand extends CommandRunner { } private _cloneGitRepositories() { - return of( + return concat( defer(() => of(log('Cloning repositories...'))), this._cloneGitRepository( 'topos-protocol', @@ -165,7 +141,7 @@ export class StartCommand extends CommandRunner { EXECUTOR_SERVICE_REF ), defer(() => of(log(''))) - ).pipe(concatAll()) + ) } private _cloneGitRepository( @@ -209,7 +185,7 @@ export class StartCommand extends CommandRunner { } private _copyEnvFiles() { - return of( + return concat( defer(() => of(log('Copying .env files across repositories...'))), this._copyEnvFile( '.env.dapp-frontend', @@ -229,7 +205,7 @@ export class StartCommand extends CommandRunner { `.env.secrets` ), defer(() => of(log(''))) - ).pipe(concatAll()) + ) } private _copyEnvFile( @@ -271,20 +247,20 @@ export class StartCommand extends CommandRunner { const secretsFilePath = `${globalThis.workingDir}/.env.secrets` const executionPath = `${globalThis.workingDir}/local-erc20-messaging-infra` - return of( + return concat( defer(() => of(log(`Running the ERC20 messaging infra...`))), this._spawn.reactify( `source ${secretsFilePath} && cd ${executionPath} && docker compose up -d` ), defer(() => of(log(`βœ… Subnets & TCE are running`), log(``))) - ).pipe(concatAll()) + ) } private _retrieveAndWriteContractAddressesToEnv() { const frontendEnvFilePath = `${globalThis.workingDir}/dapp-frontend-erc20-messaging/packages/frontend/.env` const executorServiceEnvFilePath = `${globalThis.workingDir}/executor-service/.env` - return of( + return concat( defer(() => of(log(`Retrieving contract addresses...`))), this._spawn.reactify( `docker cp contracts-topos:/contracts/.env ${globalThis.workingDir}/.env.addresses` @@ -303,31 +279,31 @@ export class StartCommand extends CommandRunner { log(``) ) ) - ).pipe(concatAll()) + ) } private _runRedis() { const containerName = 'redis-stack-server' - return of( + return concat( defer(() => of(log(`Running the redis server...`))), this._spawn.reactify( `docker start ${containerName} 2>/dev/null || docker run -d --name ${containerName} -p 6379:6379 redis/redis-stack-server:latest` ), defer(() => of(log(`βœ… redis is running`), log(``))) - ).pipe(concatAll()) + ) } private _runExecutorService() { const secretsFilePath = `${globalThis.workingDir}/.env.secrets` const executionPath = `${globalThis.workingDir}/executor-service` - return of( + return concat( defer(() => of(log(`Running the Executor Service...`))), this._npmInstall(executionPath), this._startExecutorService(secretsFilePath, executionPath), defer(() => of(log(``))) - ).pipe(concatAll()) + ) } private _npmInstall(executionPath: string) { @@ -362,13 +338,13 @@ export class StartCommand extends CommandRunner { const secretsFilePath = `${globalThis.workingDir}/.env.secrets` const executionPath = `${globalThis.workingDir}/dapp-frontend-erc20-messaging` - return of( + return concat( defer(() => of(log(`Running the dApp Frontend...`))), this._npmInstall(executionPath), this._buildDappFrontend(secretsFilePath, executionPath), this._startDappFrontend(secretsFilePath, executionPath), defer(() => of(log(``))) - ).pipe(concatAll()) + ) } private _buildDappFrontend(secretsFilePath: string, executionPath: string) { diff --git a/src/commands/version.command.ts b/src/commands/version.command.ts index e13040c..b710636 100644 --- a/src/commands/version.command.ts +++ b/src/commands/version.command.ts @@ -1,8 +1,7 @@ import { Command, CommandRunner } from 'nest-commander' -import { ReactiveSpawn } from '../ReactiveSpawn' -import { log } from '../loggers' const { version } = require('../../package.json') +import { log } from '../loggers' const overrideQuiet = true @@ -11,12 +10,15 @@ const overrideQuiet = true description: `Show topos-playground version (v${version})`, }) export class VersionCommand extends CommandRunner { - constructor(private _spawn: ReactiveSpawn) { + constructor() { super() } async run(): Promise { - log((globalThis.quiet ? '' : 'topos-playground version ') + version, overrideQuiet) + log( + (globalThis.quiet ? '' : 'topos-playground version ') + version, + overrideQuiet + ) log(``) } } diff --git a/src/initializers.ts b/src/initializers.ts index a1ff86d..6e52dcf 100644 --- a/src/initializers.ts +++ b/src/initializers.ts @@ -1,7 +1,7 @@ -import { mkdir } from 'fs' -import { join } from 'path' import { randomUUID } from 'crypto' import { config } from 'dotenv' +import { mkdir } from 'fs' +import { join } from 'path' import { loggerConsole, logError } from './loggers' @@ -54,4 +54,4 @@ export function initializeDirectories() { logError(`Could not create log directory (${globalThis.logDir})`) } }) -} \ No newline at end of file +} diff --git a/src/loggers.ts b/src/loggers.ts index caecefb..ab41d76 100644 --- a/src/loggers.ts +++ b/src/loggers.ts @@ -1,4 +1,3 @@ -import { randomUUID } from 'crypto' import * as winston from 'winston' export const loggerConsole = winston.createLogger({ @@ -46,7 +45,9 @@ export function log(logMessage: string, overrideQuiet: boolean = false) { let lines = logMessage.split('\n') for (let line of lines) { - if (overrideQuiet || !globalThis.quiet) getLogConsole().info(line) + if (overrideQuiet || !globalThis.quiet) { + getLogConsole().info(line) + } getLogFile().info(line) } } From 25dd6a06a07bc517cd1e8e31b260775cf43f5baa Mon Sep 17 00:00:00 2001 From: Kirk Haines Date: Fri, 25 Aug 2023 19:46:11 -0600 Subject: [PATCH 22/37] Fixed bugs and layout. Resolved some longstanding bugs in the code that does the intelligent line wrapping. Made changes that resolve the excessive wordiness to the console. Fixed some orchestration problems that were resulting in lines being out of order. Added the version checks back, cleaner, and checking both docker and nodejs. There is certainly a minimum version for git, too, but determining that is not straightforward, and unlike the other two dependencies, it is not likely to be an issue. --- package-lock.json | 24 +++---- package.json | 2 + src/commands/root.command.ts | 32 +++++++-- src/commands/start.command.ts | 125 ++++++++++++++++++++++++++++------ src/loggers.ts | 15 ++-- src/utility.ts | 105 +++++++++++++++++++--------- 6 files changed, 224 insertions(+), 79 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3df7d87..97c67ef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,9 +11,11 @@ "dependencies": { "@nestjs/common": "^9.0.0", "@nestjs/core": "^9.0.0", + "chalk": "^4.1.2", "dotenv": "^16.3.1", "ethers": "^5.7.2", "nest-commander": "^3.7.1", + "semver": "^7.5.4", "winston": "^3.8.2" }, "bin": { @@ -7826,10 +7828,9 @@ "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==" }, "node_modules/semver": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", - "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", - "dev": true, + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -7844,7 +7845,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, "dependencies": { "yallist": "^4.0.0" }, @@ -7855,8 +7855,7 @@ "node_modules/semver/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/send": { "version": "0.18.0", @@ -14957,10 +14956,9 @@ "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==" }, "semver": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", - "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", - "dev": true, + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "requires": { "lru-cache": "^6.0.0" }, @@ -14969,7 +14967,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, "requires": { "yallist": "^4.0.0" } @@ -14977,8 +14974,7 @@ "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" } } }, diff --git a/package.json b/package.json index a500994..76cc5a3 100644 --- a/package.json +++ b/package.json @@ -18,9 +18,11 @@ "dependencies": { "@nestjs/common": "^9.0.0", "@nestjs/core": "^9.0.0", + "chalk": "^4.1.2", "dotenv": "^16.3.1", "ethers": "^5.7.2", "nest-commander": "^3.7.1", + "semver": "^7.5.4", "winston": "^3.8.2" }, "devDependencies": { diff --git a/src/commands/root.command.ts b/src/commands/root.command.ts index 9ee6dfc..11225de 100644 --- a/src/commands/root.command.ts +++ b/src/commands/root.command.ts @@ -1,17 +1,39 @@ +import * as chalk from 'chalk' import { RootCommand, Option, CommandRunner } from 'nest-commander' const { description, version } = require('../../package.json') import { log } from '../loggers' import { breakText } from '../utility' +const example = chalk.green + const helptext = ` +Example Usage + + $ ${example('topos-playground start')} + Start the Topos-Playground. This command will output the status of the playground creation to the terminal as it runs, and will log a more detailed status to a log file. + + $ ${example('topos-playground start --verbose')} + This will also start the topos playground, but the terminal output as well as the log file output will contain more information. This is useful for debugging if there are errors starting the playground. + + $ ${example('topos-playground start -q')} + This will start the topos playground quietly. Most output will be suppressed. + + $ ${example('topos-playground clean')} + This will clean the topos playground. It will shut down all containers, and remove all filesystem artifacts except for log files. + + $ ${example('topos-playground version')} + This will print the version of the topos playground. + $ ${example('topos-playground version -q')}') + This will print only the numbers of the topos-playground version, with no other output. + Configuration -topos-playground follows the XDG Base Directory Specification, which means that data files for use during runs of the playground are store in $XDG_DATA_HOME/topos-playground, which defaults to $HOME/.local/share/topos-playground and log files are stored in $XDG_STATE_HOME/topos-playground/logs, which defaults to $HOME/.local/state/topos-playground/logs. + topos-playground follows the XDG Base Directory Specification, which means that data files for use during runs of the playground are stored in $XDG_DATA_HOME/topos-playground, which defaults to $HOME/.local/share/topos-playground and log files are stored in $XDG_STATE_HOME/topos-playground/logs, which defaults to $HOME/.local/state/topos-playground/logs. -These locations can be overridden by setting the environment variables HOME, XDG_DATA_HOME, and XDG_STATE_HOME. -`.trim() + These locations can be overridden by setting the environment variables HOME, XDG_DATA_HOME, and XDG_STATE_HOME. +` const columns = process.stdout.columns || 80 const overrideQuiet = true @@ -44,7 +66,7 @@ export class Root extends CommandRunner { flags: '-v, --verbose', description: breakText( `Show more information about the execution of a command`, - 39 + columns - 18 ), }) doVerbose() { @@ -55,7 +77,7 @@ export class Root extends CommandRunner { flags: '-q, --quiet', description: breakText( `Show minimal onscreen information about the execution of a command`, - 39 + columns - 18 ), }) doQuiet() { diff --git a/src/commands/start.command.ts b/src/commands/start.command.ts index 6f8a127..547f069 100644 --- a/src/commands/start.command.ts +++ b/src/commands/start.command.ts @@ -1,9 +1,9 @@ import { readFile, stat } from 'fs' import { Command, CommandRunner, InquirerService } from 'nest-commander' -import { concat, defer, Observable, of } from 'rxjs' -import { concatAll, tap } from 'rxjs/operators' +import { concat, defer, Observable, of, tap } from 'rxjs' +import { satisfies } from 'semver' -import { log, logError } from '../loggers' +import { log, logError, logToFile } from '../loggers' import { Next, ReactiveSpawn } from '../ReactiveSpawn' const INFRA_REF = 'v0.1.5' @@ -50,12 +50,12 @@ export class StartCommand extends CommandRunner { ) log(`ℹ️ Logs were written to ${logFilePath}`) }, - error: () => { - logError(`❌ Error`) + error: (error) => { + logError(`❗ ${error}`) }, next: (data: Next) => { - if (data && data.hasOwnProperty('output')) { - log(`${data.output}`) + if (globalThis.verbose && data && data.hasOwnProperty('output')) { + logToFile(`${data.output}`) } }, }) @@ -63,33 +63,109 @@ export class StartCommand extends CommandRunner { private _verifyDependencyInstallation() { return concat( - defer(() => of(log('Verifying dependency installation...'))), + of(log('Verifying dependency installation...')), this._verifyDockerInstallation(), this._verifyGitInstallation(), - this._verifyNodeJSInstallation(), - defer(() => of(log(''))) + this._verifyNodeJSInstallation() + ).pipe( + tap({ + complete: () => { + log('βœ… Dependency checks completed!') + log('') + }, + }) ) } private _verifyDockerInstallation() { - return concat( - this._spawn.reactify('docker --version'), - defer(() => of(log('βœ… Docker'))) + let version = '' + + return this._spawn.reactify('docker --version').pipe( + tap({ + next: (data: Next) => { + if (data && data.hasOwnProperty('output')) { + let match = RegExp(/Docker version ([0-9]+\.[0-9]+\.[0-9]+)/).exec( + `${data.output}` + ) + if (match) { + version = match[1] + } + } + }, + complete: () => { + if (satisfies(version, '>=17.6.0')) { + log(`βœ… Docker -- Version: ${version}`) + } else { + log(`❌ Docker -- Version: ${version}`) + throw new Error( + `Docker ${version} is not supported\n` + + 'Please upgrade Docker to version 17.06.0 or higher.' + ) + } + }, + error: () => { + logError(`❌ Docker Not Installed!`) + }, + }) ) } private _verifyGitInstallation() { - return concat( - this._spawn.reactify('git --version'), - defer(() => of(log('βœ… Git'))) + let version = '' + + return this._spawn.reactify('git --version').pipe( + tap({ + next: (data: Next) => { + if (data && data.hasOwnProperty('output')) { + let match = RegExp(/git version ([0-9]+\.[0-9]+\.[0-9]+)/).exec( + `${data.output}` + ) + if (match) { + version = match[1] + } + } + }, + complete: () => { + log(`βœ… Git -- Version: ${version}`) + }, + error: () => { + logError(`❌ Git Not Intalled!`) + }, + }) ) } private _verifyNodeJSInstallation() { - return of( - this._spawn.reactify('node --version'), - defer(() => of(log('βœ… NodeJS'))) - ).pipe(concatAll()) + let version = '' + + return this._spawn.reactify('node --version').pipe( + tap({ + next: (data: Next) => { + if (data && data.hasOwnProperty('output')) { + let match = RegExp(/v([0-9]+\.[0-9]+\.[0-9]+)/).exec( + `${data.output}` + ) + if (match) { + version = match[1] + } + } + }, + complete: () => { + if (satisfies(version, '>=16.0.0')) { + log(`βœ… Node.js -- Version: ${version}`) + } else { + log(`❌ Node.js -- Version: ${version}`) + throw new Error( + `Node.js ${version} is not supported\n` + + 'Please upgrade Node.js to version 16.0.0 or higher.' + ) + } + }, + error: () => { + logError(`❌ Node.js Not Installed!`) + }, + }) + ) } private _createWorkingDirectoryIfInexistant() { @@ -154,6 +230,10 @@ export class StartCommand extends CommandRunner { stat(path, (error) => { if (error) { + if (globalThis.verbose) { + log(`Cloning ${organizationName}/${repositoryName}...`) + } + this._spawn .reactify( `git clone --depth 1 ${ @@ -170,6 +250,9 @@ export class StartCommand extends CommandRunner { branch ? ` | ${branch}` : '' } successfully cloned` ) + if (globalThis.verbose) { + log('') + } }, }) ) @@ -236,7 +319,7 @@ export class StartCommand extends CommandRunner { } ) } else { - log(`βœ… ${localEnvFileName} already existing`) + log(`βœ… ${localEnvFileName} already exists`) subscriber.complete() } }) diff --git a/src/loggers.ts b/src/loggers.ts index ab41d76..eea4e5f 100644 --- a/src/loggers.ts +++ b/src/loggers.ts @@ -42,9 +42,7 @@ function getLogFile() { } export function log(logMessage: string, overrideQuiet: boolean = false) { - let lines = logMessage.split('\n') - - for (let line of lines) { + for (let line of logMessage.split('\n')) { if (overrideQuiet || !globalThis.quiet) { getLogConsole().info(line) } @@ -52,6 +50,12 @@ export function log(logMessage: string, overrideQuiet: boolean = false) { } } +export function logToFile(logMessage: string) { + for (let line of logMessage.split('\n')) { + getLogFile().info(line) + } +} + export function logError(errorMessage: string) { let lines = errorMessage.split('\n') @@ -60,6 +64,7 @@ export function logError(errorMessage: string) { getLogFile().error(line) } - getLogConsole().error(`Find more details in ${globalThis.logFilePath}`) - getLogFile().error(`Find more details in ${globalThis.logFilePath}`) + const message = `Find the full log file in ${globalThis.logFilePath}` + getLogConsole().error(message) + getLogFile().error(message) } diff --git a/src/utility.ts b/src/utility.ts index cdeda45..4b44b9d 100644 --- a/src/utility.ts +++ b/src/utility.ts @@ -6,59 +6,96 @@ line with an abnormally large gap at the end. In that case, it will hypenate the last word in the line and continue on the next line. */ -export function breakText(str: string, n: number = 60): string { - if (n <= 0) return str +export function breakText(str: string, maxLineLength: number = 60): string { + if (maxLineLength <= 0) return str // Split the string into words to calculate word length statistics - let words = str.split(' '); - let wordLengths = words.map(word => word.length); + let words = str.split(' ') + let wordLengths = words.map((word) => word.length) // Calculate average word length - let average = wordLengths.reduce((sum, length) => sum + length, 0) / wordLengths.length; + let average = + wordLengths.reduce((sum, length) => sum + length, 0) / wordLengths.length // Calculate standard deviation of word length - let sumOfSquaredDifferences = wordLengths.reduce((sum, length) => sum + Math.pow(length - average, 2), 0); - let standardDeviation = Math.sqrt(sumOfSquaredDifferences / wordLengths.length); + let sumOfSquaredDifferences = wordLengths.reduce( + (sum, length) => sum + Math.pow(length - average, 2), + 0 + ) + let standardDeviation = Math.sqrt( + sumOfSquaredDifferences / wordLengths.length + ) // Determine the maximum word length to allow before breaking - let maxWordLength = Math.min(n, average + standardDeviation); - + let maxWordLength = Math.min(maxLineLength, average + standardDeviation) let lines: string[] = [] - let line = "" - let word = "" + let line = '' + let word = '' + let indentation = '' + let hasDeterminedIndentation = false + const whitespace = /[\s\n]/ for (let i = 0; i < str.length; i++) { let char = str[i] - if (char === " " || i === str.length - 1) { - if (line.length + word.length < n) { - line += word + (i === str.length - 1 && char !== " " ? char : " ") - word = "" + word += char + + if (whitespace.exec(char) || i === str.length - 1) { + if (!hasDeterminedIndentation) { + indentation += char + } + if (line.length + word.length < maxLineLength) { + line += word + word = '' } else { - if ((((line.length + word.length) > n) && ((n - line.length) > maxWordLength)) || i === str.length - 1) { - let firstCharacter = word[0] - if (firstCharacter.toLowerCase() != firstCharacter.toUpperCase() && word.length > maxWordLength) { - let part = word.substring(0, maxWordLength - 1) - let remaining = word.substring(maxWordLength - 1) - lines.push(line + part + (remaining.length > 0 ? "-" : "")) - line = "" - word = remaining + if ( + line.length + word.length > maxLineLength && + word.length > maxWordLength /* || i === str.length - 1 */ + ) { + const firstCharacter = word[0] + const minsplit = Math.max(2, Math.floor(word.length * 0.3)) + const maxsplit = Math.min( + word.length - 3, + Math.ceil(word.length * 0.7) + ) + const middle = maxLineLength - line.length - 1 + if ( + firstCharacter.toLowerCase() != firstCharacter.toUpperCase() && + word.length > maxWordLength && + middle > minsplit && + middle < maxsplit + ) { + let part = word.substring(0, middle) + let remaining = word.substring(middle) + lines.push(line + part + (remaining.length > 0 ? '-' : '')) + line = indentation + remaining + word = '' + } else { + lines.push(line.trimEnd()) + line = indentation + word + word = '' } - line = word + " " } else { - lines.push(line.trim()) - line = word + " " + lines.push(line.trimEnd()) + line = indentation + word + word = '' } - word = "" } - if (line.length >= n) { - lines.push(line.trim()) - line = "" + if (char === '\n') { + lines.push(line.trimEnd()) + line = '' + indentation = '' + hasDeterminedIndentation = false + } else { + if (line.length >= maxLineLength || i === str.length - 1) { + lines.push(line.trimEnd()) + line = '' + } } } else { - word += char + hasDeterminedIndentation = true } } - if (line) lines.push(line.trim()) - return lines.join("\n") -} \ No newline at end of file + if (line) lines.push(line) + return lines.join('\n') +} From 0b938db1dae42dbdaf9d7a58d0a907cd0f78f0b5 Mon Sep 17 00:00:00 2001 From: Kirk Haines Date: Mon, 28 Aug 2023 05:50:17 -0600 Subject: [PATCH 23/37] Refactored version checks to all happen within next(), without using a variable. --- src/commands/start.command.ts | 61 ++++++++++++++--------------------- 1 file changed, 24 insertions(+), 37 deletions(-) diff --git a/src/commands/start.command.ts b/src/commands/start.command.ts index 547f069..419321a 100644 --- a/src/commands/start.command.ts +++ b/src/commands/start.command.ts @@ -78,8 +78,6 @@ export class StartCommand extends CommandRunner { } private _verifyDockerInstallation() { - let version = '' - return this._spawn.reactify('docker --version').pipe( tap({ next: (data: Next) => { @@ -87,22 +85,17 @@ export class StartCommand extends CommandRunner { let match = RegExp(/Docker version ([0-9]+\.[0-9]+\.[0-9]+)/).exec( `${data.output}` ) - if (match) { - version = match[1] + if (match && satisfies(match[1], '>=17.6.0')) { + log(`βœ… Docker -- Version: ${match[1]}`) + } else { + log(`❌ Docker -- Version: ${match[1]}`) + throw new Error( + `Docker ${match[1]} is not supported\n` + + 'Please upgrade Docker to version 17.06.0 or higher.' + ) } } }, - complete: () => { - if (satisfies(version, '>=17.6.0')) { - log(`βœ… Docker -- Version: ${version}`) - } else { - log(`❌ Docker -- Version: ${version}`) - throw new Error( - `Docker ${version} is not supported\n` + - 'Please upgrade Docker to version 17.06.0 or higher.' - ) - } - }, error: () => { logError(`❌ Docker Not Installed!`) }, @@ -111,8 +104,6 @@ export class StartCommand extends CommandRunner { } private _verifyGitInstallation() { - let version = '' - return this._spawn.reactify('git --version').pipe( tap({ next: (data: Next) => { @@ -120,14 +111,17 @@ export class StartCommand extends CommandRunner { let match = RegExp(/git version ([0-9]+\.[0-9]+\.[0-9]+)/).exec( `${data.output}` ) - if (match) { - version = match[1] + if (match && satisfies(match[1], '>=2.0.0')) { + log(`βœ… Git -- Version: ${match[1]}`) + } else { + logError(`❌ Git -- Version: ${match[1]}`) + throw new Error( + `Git ${match[1]} is not supported\n` + + 'Please upgrade Git to version 2.0.0 or higher.' + ) } } }, - complete: () => { - log(`βœ… Git -- Version: ${version}`) - }, error: () => { logError(`❌ Git Not Intalled!`) }, @@ -136,8 +130,6 @@ export class StartCommand extends CommandRunner { } private _verifyNodeJSInstallation() { - let version = '' - return this._spawn.reactify('node --version').pipe( tap({ next: (data: Next) => { @@ -145,22 +137,17 @@ export class StartCommand extends CommandRunner { let match = RegExp(/v([0-9]+\.[0-9]+\.[0-9]+)/).exec( `${data.output}` ) - if (match) { - version = match[1] + if (match && satisfies(match[1], '>=16.0.0')) { + log(`βœ… Node.js -- Version: ${match[1]}`) + } else { + log(`❌ Node.js -- Version: ${match[1]}`) + throw new Error( + `Node.js ${match[1]} is not supported\n` + + 'Please upgrade Node.js to version 16.0.0 or higher.' + ) } } }, - complete: () => { - if (satisfies(version, '>=16.0.0')) { - log(`βœ… Node.js -- Version: ${version}`) - } else { - log(`❌ Node.js -- Version: ${version}`) - throw new Error( - `Node.js ${version} is not supported\n` + - 'Please upgrade Node.js to version 16.0.0 or higher.' - ) - } - }, error: () => { logError(`❌ Node.js Not Installed!`) }, From d822dfd45708e7a4ecddfa1a81d01a96c0de0fca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Dan?= Date: Tue, 29 Aug 2023 10:02:00 +0200 Subject: [PATCH 24/37] fix: use concatMap to fix redis down logging --- src/ReactiveSpawn.ts | 2 +- src/commands/clean.command.ts | 56 ++++++++++++++------------- src/commands/start.command.ts | 71 ++++++++++++++++++++--------------- 3 files changed, 70 insertions(+), 59 deletions(-) diff --git a/src/ReactiveSpawn.ts b/src/ReactiveSpawn.ts index 5ef8a82..fb255c2 100644 --- a/src/ReactiveSpawn.ts +++ b/src/ReactiveSpawn.ts @@ -15,7 +15,7 @@ export class ReactiveSpawn { reactify(command: string, options?: { runInBackground }) { return new Observable((subscriber) => { if (globalThis.verbose) { - log(`πŸƒ Running command: ${command}`) + log(`πŸƒ Running command: ${command}`) } const childProcess = spawn(command, { ...options, shell: this._shell }) diff --git a/src/commands/clean.command.ts b/src/commands/clean.command.ts index 54ee973..ef5d633 100644 --- a/src/commands/clean.command.ts +++ b/src/commands/clean.command.ts @@ -1,6 +1,14 @@ import { stat, readdir } from 'fs' import { Command, CommandRunner } from 'nest-commander' -import { Observable, concat } from 'rxjs' +import { + Observable, + catchError, + concat, + concatMap, + of, + tap, + throwError, +} from 'rxjs' import { homedir } from 'os' import { Next, ReactiveSpawn } from '../ReactiveSpawn' @@ -114,37 +122,31 @@ export class CleanCommand extends CommandRunner { private _shutdownRedis() { const containerName = 'redis-stack-server' - return new Observable((subscriber) => { - this._spawn - .reactify(`docker ps --format '{{.Names}}' | grep ${containerName}`) - .subscribe({ - next: (data: Next) => { - if ( - data && - data.output && - `${data.output}`.indexOf(containerName) !== -1 - ) { - log('') - log(`Shutting down the redis server...`) + return this._spawn + .reactify(`docker ps --format '{{.Names}}' | grep ${containerName}`) + .pipe( + concatMap((data) => { + if ( + data && + data.output && + `${data.output}`.indexOf(containerName) !== -1 + ) { + log('') + log(`Shutting down the redis server...`) - this._spawn.reactify(`docker rm -f ${containerName}`).subscribe({ + return this._spawn.reactify(`docker rm -f ${containerName}`).pipe( + tap({ complete: () => { log(`βœ… redis is down`) }, }) - } else { - log(`βœ… redis is not running; nothing to shut down`) - } - subscriber.complete() - }, - error: () => { - subscriber.complete() - }, - complete: () => { - subscriber.complete() - }, - }) - }) + ) + } else { + of(log(`βœ… redis is not running; nothing to shut down`)) + } + }), + catchError((error) => of(error)) + ) } private _removeWorkingDirectory() { diff --git a/src/commands/start.command.ts b/src/commands/start.command.ts index 419321a..342cdfc 100644 --- a/src/commands/start.command.ts +++ b/src/commands/start.command.ts @@ -1,14 +1,17 @@ import { readFile, stat } from 'fs' -import { Command, CommandRunner, InquirerService } from 'nest-commander' +import { Command, CommandRunner } from 'nest-commander' import { concat, defer, Observable, of, tap } from 'rxjs' import { satisfies } from 'semver' import { log, logError, logToFile } from '../loggers' import { Next, ReactiveSpawn } from '../ReactiveSpawn' -const INFRA_REF = 'v0.1.5' -const FRONTEND_REF = 'v0.1.0-alpha3' const EXECUTOR_SERVICE_REF = 'v0.1.1' +const FRONTEND_REF = 'v0.1.0-alpha3' +const INFRA_REF = 'v0.1.5' +const MIN_VERSION_DOCKER = '17.6.0' +const MIN_VERSION_GIT = '2.0.0' +const MIN_VERSION_NODE = '16.0.0' @Command({ name: 'start', @@ -16,10 +19,7 @@ const EXECUTOR_SERVICE_REF = 'v0.1.1' 'Verify that all dependencies are installed, clone any needed repositories, setup the environment, and start all of the docker containers for the Playground', }) export class StartCommand extends CommandRunner { - constructor( - private _spawn: ReactiveSpawn, - private readonly inquirer: InquirerService - ) { + constructor(private _spawn: ReactiveSpawn) { super() } @@ -85,14 +85,17 @@ export class StartCommand extends CommandRunner { let match = RegExp(/Docker version ([0-9]+\.[0-9]+\.[0-9]+)/).exec( `${data.output}` ) - if (match && satisfies(match[1], '>=17.6.0')) { - log(`βœ… Docker -- Version: ${match[1]}`) - } else { - log(`❌ Docker -- Version: ${match[1]}`) - throw new Error( - `Docker ${match[1]} is not supported\n` + - 'Please upgrade Docker to version 17.06.0 or higher.' - ) + + if (match && match.length > 1) { + if (satisfies(match[1], `>=${MIN_VERSION_DOCKER}`)) { + log(`βœ… Docker -- Version: ${match[1]}`) + } else { + log(`❌ Docker -- Version: ${match[1]}`) + throw new Error( + `Docker ${match[1]} is not supported\n` + + 'Please upgrade Docker to version 17.06.0 or higher.' + ) + } } } }, @@ -111,14 +114,17 @@ export class StartCommand extends CommandRunner { let match = RegExp(/git version ([0-9]+\.[0-9]+\.[0-9]+)/).exec( `${data.output}` ) - if (match && satisfies(match[1], '>=2.0.0')) { - log(`βœ… Git -- Version: ${match[1]}`) - } else { - logError(`❌ Git -- Version: ${match[1]}`) - throw new Error( - `Git ${match[1]} is not supported\n` + - 'Please upgrade Git to version 2.0.0 or higher.' - ) + + if (match && match.length > 1) { + if (satisfies(match[1], `>=${MIN_VERSION_GIT}`)) { + log(`βœ… Git -- Version: ${match[1]}`) + } else { + logError(`❌ Git -- Version: ${match[1]}`) + throw new Error( + `Git ${match[1]} is not supported\n` + + 'Please upgrade Git to version 2.0.0 or higher.' + ) + } } } }, @@ -137,14 +143,17 @@ export class StartCommand extends CommandRunner { let match = RegExp(/v([0-9]+\.[0-9]+\.[0-9]+)/).exec( `${data.output}` ) - if (match && satisfies(match[1], '>=16.0.0')) { - log(`βœ… Node.js -- Version: ${match[1]}`) - } else { - log(`❌ Node.js -- Version: ${match[1]}`) - throw new Error( - `Node.js ${match[1]} is not supported\n` + - 'Please upgrade Node.js to version 16.0.0 or higher.' - ) + + if (match && match.length > 1) { + if (satisfies(match[1], `>=${MIN_VERSION_NODE}`)) { + log(`βœ… Node.js -- Version: ${match[1]}`) + } else { + log(`❌ Node.js -- Version: ${match[1]}`) + throw new Error( + `Node.js ${match[1]} is not supported\n` + + 'Please upgrade Node.js to version 16.0.0 or higher.' + ) + } } } }, From 6d5d6937e801ad10beffbff1e3c231b0fa77f2af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Dan?= Date: Tue, 29 Aug 2023 10:02:08 +0200 Subject: [PATCH 25/37] ci: add smoke test workflow --- .github/workflows/test:smoke.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .github/workflows/test:smoke.yml diff --git a/.github/workflows/test:smoke.yml b/.github/workflows/test:smoke.yml new file mode 100644 index 0000000..4134dfc --- /dev/null +++ b/.github/workflows/test:smoke.yml @@ -0,0 +1,18 @@ +name: Smoke test +on: + pull_request: + branches: + - main +jobs: + smoke-test: + runs-on: ubuntu-latest-16-core + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: '18' + - run: npm ci + - run: npm run build + - run: node dist/main --help + - run: node dist/main start + - run: node dist/main clean From 19aca19a907357ff86b22b32c9cec768ab0278d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Dan?= Date: Tue, 29 Aug 2023 10:05:27 +0200 Subject: [PATCH 26/37] ci: merge release pipeline workflow with npm package --- .github/workflows/npm-package.yml | 17 +++++++++++++++++ .github/workflows/release-pipeline.yml | 20 -------------------- 2 files changed, 17 insertions(+), 20 deletions(-) delete mode 100644 .github/workflows/release-pipeline.yml diff --git a/.github/workflows/npm-package.yml b/.github/workflows/npm-package.yml index ca5f711..3c17817 100644 --- a/.github/workflows/npm-package.yml +++ b/.github/workflows/npm-package.yml @@ -1,7 +1,9 @@ name: Publish Package to NPM Registry + on: release: types: [published] + jobs: npm-package: runs-on: ubuntu-latest-16-core @@ -17,3 +19,18 @@ jobs: - run: npm publish --access public env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + notification: + runs-on: ubuntu-latest-16-core + needs: npm-package + steps: + - name: Send Slack notification + uses: slackapi/slack-github-action@v1.23.0 + with: + payload: | + { + "repository": "${{ github.repository }}", + "version": "${{ github.ref }}" + } + env: + SLACK_WEBHOOK_URL: ${{ vars.RELEASE_PIPELINE_SLACK_WEBHOOK_URL }} diff --git a/.github/workflows/release-pipeline.yml b/.github/workflows/release-pipeline.yml deleted file mode 100644 index 96957aa..0000000 --- a/.github/workflows/release-pipeline.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: Release Pipeline - -on: - release: - types: [created] - -jobs: - notification: - runs-on: ubuntu-latest-16-core - steps: - - name: Send Slack notification - uses: slackapi/slack-github-action@v1.23.0 - with: - payload: | - { - "repository": "${{ github.repository }}", - "version": "${{ github.ref }}" - } - env: - SLACK_WEBHOOK_URL: ${{ vars.RELEASE_PIPELINE_SLACK_WEBHOOK_URL }} From 9ceea961e7111b2d25ec1388ad6a970356388003 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Dan?= Date: Tue, 29 Aug 2023 10:06:49 +0200 Subject: [PATCH 27/37] ci: add verbose flag --- .github/workflows/test:smoke.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test:smoke.yml b/.github/workflows/test:smoke.yml index 4134dfc..871f236 100644 --- a/.github/workflows/test:smoke.yml +++ b/.github/workflows/test:smoke.yml @@ -14,5 +14,5 @@ jobs: - run: npm ci - run: npm run build - run: node dist/main --help - - run: node dist/main start - - run: node dist/main clean + - run: node dist/main start -v + - run: node dist/main clean -v From b9b1c988b30f8afe805a36f5c0faee07640be9b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Dan?= Date: Tue, 29 Aug 2023 10:23:47 +0200 Subject: [PATCH 28/37] fix: revert start error logging --- src/commands/start.command.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/commands/start.command.ts b/src/commands/start.command.ts index 342cdfc..5712ce0 100644 --- a/src/commands/start.command.ts +++ b/src/commands/start.command.ts @@ -23,7 +23,7 @@ export class StartCommand extends CommandRunner { super() } - async run(): Promise { + async run() { log(`Starting Topos-Playground...`) log(``) @@ -50,12 +50,17 @@ export class StartCommand extends CommandRunner { ) log(`ℹ️ Logs were written to ${logFilePath}`) }, - error: (error) => { - logError(`❗ ${error}`) + error: () => { + logError('❗ Error') + process.exit(1) }, next: (data: Next) => { if (globalThis.verbose && data && data.hasOwnProperty('output')) { - logToFile(`${data.output}`) + if (data.origin === 'stderr') { + logError(`${data.output}`) + } else { + logToFile(`${data.output}`) + } } }, }) From 6a713b3495670b202dd010020d6c61170a502ae6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Dan?= Date: Tue, 29 Aug 2023 10:57:50 +0200 Subject: [PATCH 29/37] fix: keep errors for file loggers after all --- src/commands/start.command.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/commands/start.command.ts b/src/commands/start.command.ts index 5712ce0..bf8d15c 100644 --- a/src/commands/start.command.ts +++ b/src/commands/start.command.ts @@ -56,11 +56,7 @@ export class StartCommand extends CommandRunner { }, next: (data: Next) => { if (globalThis.verbose && data && data.hasOwnProperty('output')) { - if (data.origin === 'stderr') { - logError(`${data.output}`) - } else { - logToFile(`${data.output}`) - } + logToFile(`${data.output}`) } }, }) @@ -204,7 +200,7 @@ export class StartCommand extends CommandRunner { defer(() => of(log('Cloning repositories...'))), this._cloneGitRepository( 'topos-protocol', - 'local-erc20-messaging-infra', + 'local-erc20-messaging-infraa', INFRA_REF ), this._cloneGitRepository( From 18748cb63d43e8f9d22406ea756376d2607ce524 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Dan?= Date: Tue, 29 Aug 2023 10:57:57 +0200 Subject: [PATCH 30/37] ci: upload logs --- .github/workflows/test:smoke.yml | 36 +++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/.github/workflows/test:smoke.yml b/.github/workflows/test:smoke.yml index 871f236..55d985a 100644 --- a/.github/workflows/test:smoke.yml +++ b/.github/workflows/test:smoke.yml @@ -1,18 +1,40 @@ name: Smoke test + on: pull_request: branches: - main + jobs: smoke-test: runs-on: ubuntu-latest-16-core steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 + - name: Checkout + uses: actions/checkout@v3 + + - name: Set up NodeJS + uses: actions/setup-node@v3 with: node-version: '18' - - run: npm ci - - run: npm run build - - run: node dist/main --help - - run: node dist/main start -v - - run: node dist/main clean -v + + - name: Install deps + run: npm ci + + - name: Build + run: npm run build + + - name: Smoke root command + run: node dist/main --help + + - name: Smoke start command + run: node dist/main start -v + + - name: Smoke clean command + run: node dist/main clean -v + + - name: Upload logs + uses: actions/upload-artifact@v3 + if: always() + with: + name: logs + path: $HOME/topos-playground/logs From 49b620713030037fea8ff7a00e1a973d88f50f62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Dan?= Date: Tue, 29 Aug 2023 11:02:19 +0200 Subject: [PATCH 31/37] ci: set git config --- .github/workflows/test:smoke.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test:smoke.yml b/.github/workflows/test:smoke.yml index 55d985a..927ce0e 100644 --- a/.github/workflows/test:smoke.yml +++ b/.github/workflows/test:smoke.yml @@ -4,7 +4,7 @@ on: pull_request: branches: - main - + jobs: smoke-test: runs-on: ubuntu-latest-16-core @@ -16,6 +16,9 @@ jobs: uses: actions/setup-node@v3 with: node-version: '18' + + - name: Set git config + run: git config --global url.https://.insteadOf git:// - name: Install deps run: npm ci From 6fdebf0fa786f3a819105889e98b6a56d04fb943 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Dan?= Date: Tue, 29 Aug 2023 11:09:02 +0200 Subject: [PATCH 32/37] fix: use right path for logs --- .github/workflows/test:smoke.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test:smoke.yml b/.github/workflows/test:smoke.yml index 927ce0e..0841952 100644 --- a/.github/workflows/test:smoke.yml +++ b/.github/workflows/test:smoke.yml @@ -40,4 +40,4 @@ jobs: if: always() with: name: logs - path: $HOME/topos-playground/logs + path: /home/runner/.local/state/topos-playground/logs From 9df5bf514f102260503170f0617ff1f471e6df5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Dan?= Date: Tue, 29 Aug 2023 11:09:12 +0200 Subject: [PATCH 33/37] chore: clean clean command --- src/commands/clean.command.ts | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/src/commands/clean.command.ts b/src/commands/clean.command.ts index ef5d633..9682791 100644 --- a/src/commands/clean.command.ts +++ b/src/commands/clean.command.ts @@ -1,17 +1,9 @@ import { stat, readdir } from 'fs' import { Command, CommandRunner } from 'nest-commander' -import { - Observable, - catchError, - concat, - concatMap, - of, - tap, - throwError, -} from 'rxjs' +import { Observable, catchError, concat, concatMap, of, tap } from 'rxjs' import { homedir } from 'os' -import { Next, ReactiveSpawn } from '../ReactiveSpawn' +import { ReactiveSpawn } from '../ReactiveSpawn' import { log, logError } from '../loggers' @Command({ @@ -28,16 +20,13 @@ export class CleanCommand extends CommandRunner { log(`Cleaning up Topos-Playground...`) log(``) - this._verifyWorkingDirectoryExistence().subscribe(() => { - this._verifyExecutionPathExistence().subscribe(() => { - // Coordinate the steps to clean up the environment - concat( - this._shutdownERC20MessagingProtocolInfra(), - this._shutdownRedis(), - this._removeWorkingDirectory() - ).subscribe() - }) - }) + concat( + this._verifyWorkingDirectoryExistence(), + this._verifyExecutionPathExistence(), + this._shutdownERC20MessagingProtocolInfra(), + this._shutdownRedis(), + this._removeWorkingDirectory() + ).subscribe() } private _verifyWorkingDirectoryExistence() { From 5f2bb8ae84dd1e73e44c44646cf048abf3a48f85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Dan?= Date: Tue, 29 Aug 2023 11:09:30 +0200 Subject: [PATCH 34/37] fix: revert fake repo endpoint --- src/commands/start.command.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/start.command.ts b/src/commands/start.command.ts index bf8d15c..32220f6 100644 --- a/src/commands/start.command.ts +++ b/src/commands/start.command.ts @@ -200,7 +200,7 @@ export class StartCommand extends CommandRunner { defer(() => of(log('Cloning repositories...'))), this._cloneGitRepository( 'topos-protocol', - 'local-erc20-messaging-infraa', + 'local-erc20-messaging-infra', INFRA_REF ), this._cloneGitRepository( From 496d19a0fa6f67ec5d47c20cf1e254261ed84bf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Dan?= Date: Tue, 29 Aug 2023 11:12:59 +0200 Subject: [PATCH 35/37] fix: clone repo with http --- .github/workflows/test:smoke.yml | 3 --- src/commands/start.command.ts | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/test:smoke.yml b/.github/workflows/test:smoke.yml index 0841952..7d1d7f9 100644 --- a/.github/workflows/test:smoke.yml +++ b/.github/workflows/test:smoke.yml @@ -16,9 +16,6 @@ jobs: uses: actions/setup-node@v3 with: node-version: '18' - - - name: Set git config - run: git config --global url.https://.insteadOf git:// - name: Install deps run: npm ci diff --git a/src/commands/start.command.ts b/src/commands/start.command.ts index 32220f6..ad35d70 100644 --- a/src/commands/start.command.ts +++ b/src/commands/start.command.ts @@ -235,7 +235,7 @@ export class StartCommand extends CommandRunner { .reactify( `git clone --depth 1 ${ branch ? `--branch ${branch}` : '' - } git@github.com:${organizationName}/${repositoryName}.git ${ + } https://github.com/${organizationName}/${repositoryName}.git ${ globalThis.workingDir }/${repositoryName}` ) From a39d39faacd9e0f4046f03884a4ebe3f38927813 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Dan?= Date: Tue, 29 Aug 2023 11:49:41 +0200 Subject: [PATCH 36/37] ci: kill process when start completes --- .github/workflows/test:smoke.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test:smoke.yml b/.github/workflows/test:smoke.yml index 7d1d7f9..c42f61c 100644 --- a/.github/workflows/test:smoke.yml +++ b/.github/workflows/test:smoke.yml @@ -27,7 +27,7 @@ jobs: run: node dist/main --help - name: Smoke start command - run: node dist/main start -v + run: node dist/main start -v | tee >(grep -q "πŸ”₯ Everything is done! πŸ”₯" && pkill -P $$) - name: Smoke clean command run: node dist/main clean -v From c7bf0211313c665718398f7351a79d2a9327afd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Dan?= Date: Tue, 29 Aug 2023 11:59:02 +0200 Subject: [PATCH 37/37] fix: return success when string is found --- .github/workflows/test:smoke.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test:smoke.yml b/.github/workflows/test:smoke.yml index c42f61c..fa3ac9b 100644 --- a/.github/workflows/test:smoke.yml +++ b/.github/workflows/test:smoke.yml @@ -27,7 +27,7 @@ jobs: run: node dist/main --help - name: Smoke start command - run: node dist/main start -v | tee >(grep -q "πŸ”₯ Everything is done! πŸ”₯" && pkill -P $$) + run: (node dist/main start -v | tee >(grep -q "πŸ”₯ Everything is done! πŸ”₯" && pkill -P $$); [ $? -eq 143 ]) - name: Smoke clean command run: node dist/main clean -v