From 6e5f5d5c9a4afcb8caceb4ceee058f1b83e8c723 Mon Sep 17 00:00:00 2001 From: Divlo Date: Sat, 10 Sep 2022 17:03:05 +0200 Subject: [PATCH] fix: setup automatic config "cleaner" for manually removed Leon instances --- src/commands/__test__/info.test.ts | 64 +++++++++++++------- src/commands/__test__/start.test.ts | 70 ++++++++++++++++++++++ src/commands/info.ts | 28 ++++++--- src/services/LeonInstance.ts | 57 ++++++++++-------- src/services/__test__/LeonInstance.test.ts | 21 ++++--- 5 files changed, 177 insertions(+), 63 deletions(-) diff --git a/src/commands/__test__/info.test.ts b/src/commands/__test__/info.test.ts index ad18b79..42f91aa 100644 --- a/src/commands/__test__/info.test.ts +++ b/src/commands/__test__/info.test.ts @@ -12,6 +12,7 @@ import { config } from '../../services/Config.js' import type { LeonInstanceOptions } from '../../services/LeonInstance.js' import { LeonInstance } from '../../services/LeonInstance.js' import { Log } from '../../services/Log.js' +import { isExistingPath } from '../../utils/isExistingPath.js' const leonInstanceOptions: LeonInstanceOptions = { name: 'random-name', @@ -57,32 +58,56 @@ await tap.test('leon info', async (t) => { const exitCode = await command.execute() t.equal(exitCode, 0) t.equal(consoleLogSpy.calledWith(chalk.cyan('\nLeon instances:\n')), true) + let infoResult = table([ + [chalk.bold('Name'), leonInstance.name], + [chalk.bold('Path'), leonInstance.path], + [chalk.bold('Mode'), leonInstance.mode], + [chalk.bold('Birth date'), birthDayString], + [chalk.bold('Version'), version] + ]) + infoResult += '\n------------------------------\n\n' + t.equal(consoleLogSpy.calledWith(infoResult), true) + } + ) + + await t.test( + 'should succeeds and advise the user to create an instance', + async (t) => { + sinon.stub(console, 'log').value(() => {}) + const consoleLogSpy = sinon.spy(console, 'log') + fsMock({ + [config.path]: JSON.stringify({ instances: [] }) + }) + const command = cli.process(['info']) + const exitCode = await command.execute() + t.equal(exitCode, 0) + t.equal( + consoleLogSpy.calledWith(chalk.bold('No Leon instances found.')), + true + ) t.equal( consoleLogSpy.calledWith( - table([ - [chalk.bold('Name'), leonInstance.name], - [chalk.bold('Path'), leonInstance.path], - [chalk.bold('Mode'), leonInstance.mode], - [chalk.bold('Birth date'), birthDayString], - [chalk.bold('Version'), version] - ]) + 'You can give birth to a Leon instance using:' ), true ) + t.equal(consoleLogSpy.calledWith(chalk.cyan('leon create birth')), true) } ) await t.test( - 'should succeeds and advise the user to create an instance', + 'should succeeds and advise the user to create an instance with instance path not found', async (t) => { sinon.stub(console, 'log').value(() => {}) const consoleLogSpy = sinon.spy(console, 'log') fsMock({ - [config.path]: JSON.stringify({ instances: [] }) + [config.path]: JSON.stringify(configData) }) const command = cli.process(['info']) const exitCode = await command.execute() t.equal(exitCode, 0) + t.equal(await isExistingPath(leonInstance.path), false) + t.strictSame(config.get('instances', []), []) t.equal( consoleLogSpy.calledWith(chalk.bold('No Leon instances found.')), true @@ -110,18 +135,15 @@ await tap.test('leon info', async (t) => { const exitCode = await command.execute() t.equal(exitCode, 0) t.equal(consoleLogSpy.calledWith(chalk.cyan('\nLeon instances:\n')), true) - t.equal( - consoleLogSpy.calledWith( - table([ - [chalk.bold('Name'), leonInstance.name], - [chalk.bold('Path'), `${leonInstance.path}`], - [chalk.bold('Mode'), leonInstance.mode], - [chalk.bold('Birth date'), birthDayString], - [chalk.bold('Version'), '0.0.0'] - ]) - ), - true - ) + let infoResult = table([ + [chalk.bold('Name'), leonInstance.name], + [chalk.bold('Path'), leonInstance.path], + [chalk.bold('Mode'), leonInstance.mode], + [chalk.bold('Birth date'), birthDayString], + [chalk.bold('Version'), '0.0.0'] + ]) + infoResult += '\n------------------------------\n\n' + t.equal(consoleLogSpy.calledWith(infoResult), true) } ) diff --git a/src/commands/__test__/start.test.ts b/src/commands/__test__/start.test.ts index 825aa06..0276ba8 100644 --- a/src/commands/__test__/start.test.ts +++ b/src/commands/__test__/start.test.ts @@ -1,11 +1,81 @@ import tap from 'tap' +import sinon from 'sinon' +import fsMock from 'mock-fs' +import chalk from 'chalk' import { StartCommand } from '../start.js' import { cli } from '../../cli.js' +import type { ConfigData } from '../../services/Config.js' +import { config } from '../../services/Config.js' +import type { LeonInstanceOptions } from '../../services/LeonInstance.js' +import { LeonInstance } from '../../services/LeonInstance.js' +import { isExistingPath } from '../../utils/isExistingPath.js' + +const leonInstanceOptions: LeonInstanceOptions = { + name: 'random-name', + birthDate: '2022-02-20T10:11:33.315Z', + mode: 'docker', + path: '/path', + startCount: 0 +} + +const leonInstance = new LeonInstance(leonInstanceOptions) +const configData: ConfigData = { + instances: [leonInstance] +} await tap.test('leon start', async (t) => { + t.afterEach(() => { + fsMock.restore() + sinon.restore() + }) + await t.test('should be instance of the command', async (t) => { const command = cli.process(['start']) t.equal(command instanceof StartCommand, true) }) + + await t.test( + 'should fails with instance not found (automatic config cleaner)', + async (t) => { + sinon.stub(console, 'error').value(() => {}) + const consoleErrorSpy = sinon.spy(console, 'error') + fsMock({ + [config.path]: JSON.stringify(configData) + }) + const command = cli.process(['start']) + const exitCode = await command.execute() + t.equal(exitCode, 1) + t.equal(await isExistingPath(leonInstance.path), false) + t.strictSame(config.get('instances', []), []) + t.equal( + consoleErrorSpy.calledWith( + `${chalk.red('Error:')} You should have at least one instance.` + ), + true + ) + } + ) + + await t.test( + 'should fails with instance not found with specified name', + async (t) => { + sinon.stub(console, 'error').value(() => {}) + const consoleErrorSpy = sinon.spy(console, 'error') + fsMock({ + [config.path]: JSON.stringify(configData) + }) + const command = cli.process(['start', '--name="random-name"']) + const exitCode = await command.execute() + t.equal(exitCode, 1) + t.equal( + consoleErrorSpy.calledWith( + `${chalk.red( + 'Error:' + )} This instance doesn't exists, please provider another name.` + ), + true + ) + } + ) }) diff --git a/src/commands/info.ts b/src/commands/info.ts index e2fb4d8..066ec72 100644 --- a/src/commands/info.ts +++ b/src/commands/info.ts @@ -16,24 +16,36 @@ export class InfoCommand extends Command { description: 'Name of the Leon instance.' }) + public static logNoInstancesFound(): void { + console.log(chalk.bold('No Leon instances found.')) + console.log('You can give birth to a Leon instance using:') + console.log(chalk.cyan('leon create birth')) + } + async execute(): Promise { try { if (this.name != null) { console.log() const leonInstance = await LeonInstance.get(this.name) - await leonInstance.logInfo() + console.log(await leonInstance.info()) } else { const instances = config.get('instances', []) if (instances.length === 0) { - console.log(chalk.bold('No Leon instances found.')) - console.log('You can give birth to a Leon instance using:') - console.log(chalk.cyan('leon create birth')) + InfoCommand.logNoInstancesFound() } else { - console.log(chalk.cyan('\nLeon instances:\n')) + let infoResult = '' for (const instance of instances) { - const leonInstance = new LeonInstance(instance) - await leonInstance.logInfo() - console.log('------------------------------\n') + const leonInstance = await LeonInstance.find(instance.name) + if (leonInstance != null) { + infoResult += (await leonInstance.info()) + '\n' + infoResult += '------------------------------\n\n' + } + } + if (infoResult.length === 0) { + InfoCommand.logNoInstancesFound() + } else { + console.log(chalk.cyan('\nLeon instances:\n')) + console.log(infoResult) } } } diff --git a/src/services/LeonInstance.ts b/src/services/LeonInstance.ts index c5c641e..096ba25 100644 --- a/src/services/LeonInstance.ts +++ b/src/services/LeonInstance.ts @@ -38,6 +38,7 @@ export interface LeonInstanceOptions extends CreateOptions { export class LeonInstance implements LeonInstanceOptions { static readonly INVALID_VERSION = '0.0.0' + static readonly DEFAULT_START_PORT = 1337 public name: string public path: string @@ -84,7 +85,8 @@ export class LeonInstance implements LeonInstanceOptions { public async start(port?: number): Promise { process.chdir(this.path) - const LEON_PORT = port?.toString() ?? '1337' + const LEON_PORT_STRING = port ?? LeonInstance.DEFAULT_START_PORT + const LEON_PORT = LEON_PORT_STRING.toString() this.incrementStartCount() if (this.mode === 'docker') { return await this.startDocker(LEON_PORT) @@ -160,24 +162,35 @@ export class LeonInstance implements LeonInstanceOptions { return instance.name === name }) if (instance != null) { - return new LeonInstance(instance) + const leonInstance = new LeonInstance(instance) + if (await isExistingPath(instance.path)) { + return leonInstance + } + await leonInstance.delete() + return null } return null } static async get(name?: string): Promise { + let instanceName = name ?? 'default' + const logErrorAtLeastOneInstance = new LogError({ + message: 'You should have at least one instance.' + }) if (name == null) { const instances = config.get('instances', []) const isEmptyInstances = instances.length === 0 if (isEmptyInstances) { - throw new LogError({ - message: 'You should have at least one instance.' - }) + throw logErrorAtLeastOneInstance } - return new LeonInstance(instances[0]) + const firstInstance = instances[0] + instanceName = firstInstance.name } - const leonInstance = await LeonInstance.find(name) + const leonInstance = await LeonInstance.find(instanceName) if (leonInstance == null) { + if (name == null) { + throw logErrorAtLeastOneInstance + } throw new LogError({ message: "This instance doesn't exists, please provider another name." }) @@ -243,7 +256,7 @@ export class LeonInstance implements LeonInstanceOptions { public async update(leon: Leon): Promise { const currentVersion = await this.getVersion() const sourceCodePath = await leon.getSourceCode() - const sourceCodeVersion = await LeonInstance.getVersion(sourceCodePath) + const sourceCodeVersion = await this.getVersion() if (currentVersion !== sourceCodeVersion || leon.useDevelopGitBranch) { await fs.promises.rm(this.path, { force: true, @@ -254,12 +267,12 @@ export class LeonInstance implements LeonInstanceOptions { } } - public static async getVersion(sourceCodePath: string): Promise { - const packageJsonPath = path.join(sourceCodePath, 'package.json') + public async getVersion(): Promise { + const packageJsonPath = path.join(this.path, 'package.json') let version = LeonInstance.INVALID_VERSION if (await isExistingPath(packageJsonPath)) { const packageJSON = await readPackage({ - cwd: sourceCodePath, + cwd: this.path, normalize: false }) version = packageJSON.version ?? LeonInstance.INVALID_VERSION @@ -267,22 +280,16 @@ export class LeonInstance implements LeonInstanceOptions { return version } - public async getVersion(): Promise { - return await LeonInstance.getVersion(this.path) - } - - public async logInfo(): Promise { + public async info(): Promise { const birthDay = new Date(this.birthDate) const birthDayString = date.format(birthDay, 'DD/MM/YYYY - HH:mm:ss') const version = await this.getVersion() - console.log( - table([ - [chalk.bold('Name'), this.name], - [chalk.bold('Path'), this.path], - [chalk.bold('Mode'), this.mode], - [chalk.bold('Birth date'), birthDayString], - [chalk.bold('Version'), version] - ]) - ) + return table([ + [chalk.bold('Name'), this.name], + [chalk.bold('Path'), this.path], + [chalk.bold('Mode'), this.mode], + [chalk.bold('Birth date'), birthDayString], + [chalk.bold('Version'), version] + ]) } } diff --git a/src/services/__test__/LeonInstance.test.ts b/src/services/__test__/LeonInstance.test.ts index 19626a2..dbbc6cb 100644 --- a/src/services/__test__/LeonInstance.test.ts +++ b/src/services/__test__/LeonInstance.test.ts @@ -21,23 +21,24 @@ const configData: ConfigData = { await tap.test('services/LeonInstance', async (t) => { await t.test('find', async (t) => { - t.beforeEach(() => { - fsMock({ - [config.path]: JSON.stringify(configData) - }) - }) - t.afterEach(() => { fsMock.restore() }) await t.test('should find the instance with its name', async (t) => { + fsMock({ + [config.path]: JSON.stringify(configData), + [leonInstance.path]: {} + }) const instance = await LeonInstance.find(leonInstance.name) - t.not(instance, undefined) t.equal(instance?.name, leonInstance.name) }) await t.test('should not find the instance with wrong name', async (t) => { + fsMock({ + [config.path]: JSON.stringify(configData), + [leonInstance.path]: {} + }) const instance = await LeonInstance.find('wrong name') t.equal(instance, null) }) @@ -61,7 +62,8 @@ await tap.test('services/LeonInstance', async (t) => { 'should return the first instance if name is undefined', async (t) => { fsMock({ - [config.path]: JSON.stringify(configData) + [config.path]: JSON.stringify(configData), + [leonInstance.path]: {} }) const instance = await LeonInstance.get() t.equal(instance.name, leonInstance.name) @@ -84,7 +86,8 @@ await tap.test('services/LeonInstance', async (t) => { 'should return the instance with the name specified', async (t) => { fsMock({ - [config.path]: JSON.stringify(configData) + [config.path]: JSON.stringify(configData), + [leonInstance.path]: {} }) const instance = await LeonInstance.get(leonInstance.name) t.equal(instance.name, leonInstance.name)