From 1021b6b269050230f6b19efeeaa07b3b7c2355bf Mon Sep 17 00:00:00 2001 From: Abir Date: Mon, 21 Feb 2022 17:49:20 +0530 Subject: [PATCH] feat: add global specification watcher (#220) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Lukasz Gornicki Co-authored-by: Maciej UrbaƄczyk --- src/commands/diff.ts | 18 ++++++++++-- src/commands/validate.ts | 13 +++++++-- src/flags.ts | 6 ++++ src/globals.ts | 50 ++++++++++++++++++++++++++++++++++ test/commands/validate.test.ts | 1 - 5 files changed, 82 insertions(+), 6 deletions(-) create mode 100644 src/flags.ts create mode 100644 src/globals.ts diff --git a/src/commands/diff.ts b/src/commands/diff.ts index 129566f8012..1bc0869043e 100644 --- a/src/commands/diff.ts +++ b/src/commands/diff.ts @@ -12,6 +12,8 @@ import { DiffOverrideFileError, DiffOverrideJSONError, } from '../errors/diff-error'; +import { specWatcher, specWatcherParams } from '../globals'; +import { watchFlag } from '../flags'; const { readFile } = fs; @@ -36,6 +38,7 @@ export default class Diff extends Command { char: 'o', description: 'path to JSON file containing the override properties', }), + watch: watchFlag }; static args = [ @@ -59,11 +62,12 @@ export default class Diff extends Command { const outputFormat = flags['format']; const outputType = flags['type']; const overrideFilePath = flags['overrides']; - + const watchMode = flags['watch']; let firstDocument: Specification, secondDocument: Specification; try { firstDocument = await load(firstDocumentPath); + enableWatch(watchMode, { spec: firstDocument, handler: this, handlerName: 'diff', docVersion: 'old', label: 'DIFF_OLD' }); } catch (err) { if (err instanceof SpecificationFileNotFound) { this.error( @@ -79,6 +83,7 @@ export default class Diff extends Command { try { secondDocument = await load(secondDocumentPath); + enableWatch(watchMode, { spec: secondDocument, handler: this, handlerName: 'diff', docVersion: 'new', label: 'DIFF_NEW' }); } catch (err) { if (err instanceof SpecificationFileNotFound) { this.error( @@ -126,7 +131,6 @@ export default class Diff extends Command { }); } } - outputJson(diffOutput: AsyncAPIDiff, outputType: string) { if (outputType === 'breaking') { this.log(JSON.stringify(diffOutput.breaking(), null, 2)); @@ -161,3 +165,13 @@ async function readOverrideFile(path: string): Promise { throw new DiffOverrideJSONError(); } } +/** + * function to enable watchmode. + * The function is abstracted here, to avoid eslint cognitive complexity error. + */ +const enableWatch = (status: boolean, watcher: specWatcherParams) => { + if (status) { + specWatcher(watcher); + } +}; + diff --git a/src/commands/validate.ts b/src/commands/validate.ts index adc05d84590..988fcb5dd51 100644 --- a/src/commands/validate.ts +++ b/src/commands/validate.ts @@ -3,12 +3,15 @@ import * as parser from '@asyncapi/parser'; import Command from '../base'; import { ValidationError } from '../errors/validation-error'; import { load } from '../models/SpecificationFile'; +import { specWatcher } from '../globals'; +import { watchFlag } from '../flags'; export default class Validate extends Command { static description = 'validate asyncapi file'; static flags = { - help: flags.help({ char: 'h' }) + help: flags.help({ char: 'h' }), + watch: watchFlag } static args = [ @@ -16,11 +19,15 @@ export default class Validate extends Command { ] async run() { - const { args } = this.parse(Validate); + const { args, flags } = this.parse(Validate); // NOSONAR const filePath = args['spec-file']; - const specFile = await load(filePath); + const watchMode = flags['watch']; + const specFile = await load(filePath); + if (watchMode) { + specWatcher({spec: specFile, handler: this, handlerName: 'validate'}); + } try { if (specFile.getFilePath()) { await parser.parse(specFile.text()); diff --git a/src/flags.ts b/src/flags.ts new file mode 100644 index 00000000000..7a6c7aae35c --- /dev/null +++ b/src/flags.ts @@ -0,0 +1,6 @@ +import { flags } from '@oclif/command'; + +export const watchFlag = flags.boolean({ + char: 'w', + description: 'Enable watch mode' +}); diff --git a/src/globals.ts b/src/globals.ts new file mode 100644 index 00000000000..95338ebc3e8 --- /dev/null +++ b/src/globals.ts @@ -0,0 +1,50 @@ +import chokidar from 'chokidar'; +import chalk from 'chalk'; +import Command from './base'; +import { Specification } from './models/SpecificationFile'; + +const GreenLog = chalk.hex('#00FF00'); +const OrangeLog = chalk.hex('#FFA500'); +const CHOKIDAR_CONFIG = { + // awaitWriteFinish: true // Used for large size specification files. + +}; +const WATCH_MESSAGES = { + logOnStart: (filePath: string) => console.log(GreenLog(`Watching AsyncAPI file at ${filePath}\n`)), + logOnChange: (handlerName: string) => console.log(OrangeLog(`Change detected, running ${handlerName}\n`)), + logOnAutoDisable: (docVersion: 'old' | 'new' | '' = '') => console.log(OrangeLog(`Watch mode for ${docVersion || 'AsyncAPI'} file was not enabled.`), OrangeLog('\nINFO: Watch works only with files from local file system\n')) +}; + +const CHOKIDAR_INSTANCE_STORE = new Map(); + +export type specWatcherParams = { + spec: Specification, + handler: Command, + handlerName: string, + label?: string, + docVersion?: 'old' | 'new'; +} + +export const specWatcher = (params: specWatcherParams) => { + if (!params.spec.getFilePath()) { return WATCH_MESSAGES.logOnAutoDisable(params.docVersion); } + if (CHOKIDAR_INSTANCE_STORE.get(params.label || '_default')) { return; } + + const filePath = params.spec.getFilePath() as string; + try { + WATCH_MESSAGES.logOnStart(filePath); + chokidar + .watch(filePath, CHOKIDAR_CONFIG) + .on('change', async () => { + WATCH_MESSAGES.logOnChange(params.handlerName); + try { + await params.handler.run(); + } catch (err) { + await params.handler.catch(err); + } + }); + CHOKIDAR_INSTANCE_STORE.set(params.label || '_default', true); + } catch (error) { + console.log(error); + } +}; + diff --git a/test/commands/validate.test.ts b/test/commands/validate.test.ts index 31681d43cf2..ff53a897935 100644 --- a/test/commands/validate.test.ts +++ b/test/commands/validate.test.ts @@ -3,7 +3,6 @@ import * as path from 'path'; import { expect, test } from '@oclif/test'; import {NO_CONTEXTS_SAVED} from '../../src/errors/context-error'; - import TestHelper from '../testHelper'; const testHelper = new TestHelper();