diff --git a/.gitignore b/.gitignore index fc8129e28..7e8b4d97a 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ .idea /.nyc_output /dist +.tsbuildinfo /dist/ /lib /node_modules/ diff --git a/scripts/fetch-asyncapi-example.js b/scripts/fetch-asyncapi-example.js index a354b9d6d..a0f85731c 100644 --- a/scripts/fetch-asyncapi-example.js +++ b/scripts/fetch-asyncapi-example.js @@ -119,6 +119,12 @@ const tidyUp = async () => { }; (async () => { + const exampleJsonPath = path.join(EXAMPLE_DIRECTORY, 'examples.json'); + if (fs.existsSync(exampleJsonPath) && !process.env.FORCE_FETCH_EXAMPLES) { + console.log('AsyncAPI spec examples already exists. Skipping download.'); + return; + } + await fetchAsyncAPIExamplesFromExternalURL(); await unzipAsyncAPIExamples(); await buildCLIListFromExamples(); diff --git a/src/apps/cli/commands/convert.ts b/src/apps/cli/commands/convert.ts index a89b3b7b1..6527dfdd0 100644 --- a/src/apps/cli/commands/convert.ts +++ b/src/apps/cli/commands/convert.ts @@ -8,7 +8,7 @@ import { cyan } from 'picocolors'; import { proxyFlags } from '@cli/internal/flags/proxy.flags'; import specs from '@asyncapi/specs'; import { convertFlags } from '@cli/internal/flags/convert.flags'; -import { ConversionService } from '@services/convert.service'; +import type { ConversionService } from '@services/convert.service'; import { applyProxyToPath } from '@utils/proxy'; const latestVersion = Object.keys(specs.schemas).pop() as string; @@ -17,7 +17,7 @@ const TARGET_VERSION_FLAG = 'target-version'; export default class Convert extends Command { static description = 'Convert asyncapi documents older to newer versions or OpenAPI documents to AsyncAPI'; - private conversionService = new ConversionService(); + private _conversionService?: ConversionService; static flags = { ...convertFlags(latestVersion), ...proxyFlags(), @@ -30,6 +30,15 @@ export default class Convert extends Command { }), }; + private async getConversionService(): Promise { + if (!this._conversionService) { + const { ConversionService: cService } = await import('@services/convert.service'); + this._conversionService = new cService(); + } + + return this._conversionService; + } + async run() { const { args, flags } = await this.parse(Convert); const filePath = applyProxyToPath( @@ -50,7 +59,8 @@ export default class Convert extends Command { perspective: flags['perspective'] as 'client' | 'server', }; - const result = await this.conversionService.convertDocument( + const cService = await this.getConversionService(); + const result = await cService.convertDocument( this.specFile, conversionOptions, ); @@ -62,11 +72,11 @@ export default class Convert extends Command { this.metricsMetadata.conversion_result = result; this.log( - this.conversionService.handleLogging(this.specFile, conversionOptions), + cService.handleLogging(this.specFile, conversionOptions), ); if (flags['output']) { - await this.conversionService.handleOutput( + await cService.handleOutput( flags['output'], result.data.convertedDocument, ); diff --git a/src/apps/cli/commands/validate.ts b/src/apps/cli/commands/validate.ts index ce761c24c..1984075d0 100644 --- a/src/apps/cli/commands/validate.ts +++ b/src/apps/cli/commands/validate.ts @@ -9,15 +9,12 @@ import { ValidationOptions, ValidationResult, } from '@/interfaces'; -import { - ValidationService, - ValidationStatus, -} from '@services/validation.service'; +import type { ValidationService, } from '@services/validation.service'; import { applyProxyToPath } from '@utils/proxy'; export default class Validate extends Command { static description = 'validate asyncapi file'; - private validationService = new ValidationService(); + private _validationService?: ValidationService; static flags = { ...validateFlags(), @@ -57,7 +54,8 @@ export default class Validate extends Command { suppressAllWarnings: flags['suppressAllWarnings'], }; - const result = await this.validationService.validateDocument( + const vService = await this.getValidationService(); + const result = await vService.validateDocument( this.specFile, validateOptions, ); @@ -76,11 +74,21 @@ export default class Validate extends Command { await this.handleDiagnostics(result, flags); } + const { ValidationStatus } = await import('@services/validation.service'); if (result.data?.status === ValidationStatus.INVALID) { process.exitCode = 1; } } + private async getValidationService(): Promise { + if (!this._validationService) { + const { ValidationService: vService } = await import('@services/validation.service'); + this._validationService = new vService(); + } + + return this._validationService; + } + private async handleDiagnostics( result: ServiceResult, flags: any, @@ -89,10 +97,12 @@ export default class Validate extends Command { const writeOutput = flags['save-output']; const hasIssues = (result.data?.diagnostics && result.data.diagnostics.length > 0) ?? false; + const { ValidationStatus } = await import('@services/validation.service'); const isFailSeverity = result.data?.status === ValidationStatus.INVALID; const sourceString = this.specFile?.toSourceString() || ''; - const governanceMessage = this.validationService.generateGovernanceMessage( + const vService = await this.getValidationService(); + const governanceMessage = vService.generateGovernanceMessage( sourceString, hasIssues, isFailSeverity, @@ -104,7 +114,7 @@ export default class Validate extends Command { this.log(governanceMessage); } - const diagnosticsOutput = this.validationService.formatDiagnosticsOutput( + const diagnosticsOutput = vService.formatDiagnosticsOutput( result.data?.diagnostics || [], diagnosticsFormat, flags['fail-severity'] ?? 'error', @@ -112,7 +122,7 @@ export default class Validate extends Command { if (writeOutput) { const { success, error } = - await this.validationService.saveDiagnosticsToFile( + await vService.saveDiagnosticsToFile( writeOutput, diagnosticsFormat, diagnosticsOutput, diff --git a/src/apps/cli/internal/base.ts b/src/apps/cli/internal/base.ts index 60465c943..4b1d824cc 100644 --- a/src/apps/cli/internal/base.ts +++ b/src/apps/cli/internal/base.ts @@ -7,7 +7,7 @@ import { Sink, StdOutSink, } from '@smoya/asyncapi-adoption-metrics'; -import { Parser } from '@asyncapi/parser'; +import type { Parser } from '@asyncapi/parser'; import { Specification } from '@models/SpecificationFile'; import { join, resolve } from 'path'; import { existsSync } from 'fs-extra'; @@ -25,10 +25,19 @@ class DiscardSink implements Sink { export default abstract class extends Command { recorder = this.recorderFromEnv('asyncapi_adoption'); - parser = new Parser(); + private _parser?: Parser; metricsMetadata: MetricMetadata = {}; specFile: Specification | undefined; + async getParser(): Promise { + if (!this._parser) { + const { Parser: AsyncAPIParser } = await import('@asyncapi/parser'); + this._parser = new AsyncAPIParser(); + } + + return this._parser; + } + async init(): Promise { await super.init(); const commandName: string = this.id || ''; @@ -57,7 +66,8 @@ export default abstract class extends Command { ) { if (rawDocument !== undefined) { try { - const { document } = await this.parser.parse(rawDocument); + const parser = await this.getParser(); + const { document } = await parser.parse(rawDocument); if (document !== undefined) { // @ts-ignore metadata = MetadataFromDocument(document, metadata); @@ -150,32 +160,32 @@ export default abstract class extends Command { process.env.CI !== 'true' ) { switch (process.env.NODE_ENV) { - case 'development': - // NODE_ENV set to `development` in bin/run - if (!process.env.TEST) { - // Do not pollute stdout when running tests - sink = new StdOutSink(); - } - break; - case 'production': - // NODE_ENV set to `production` in bin/run_bin, which is specified in 'bin' package.json section - sink = new NewRelicSink( - process.env.ASYNCAPI_METRICS_NEWRELIC_KEY || - 'eu01xx73a8521047150dd9414f6aedd2FFFFNRAL', - ); - - if (analyticsConfigFileContent.infoMessageShown === 'false') { - this.log( - '\nAsyncAPI anonymously tracks command executions to improve the specification and tools, ensuring no sensitive data reaches our servers. It aids in comprehending how AsyncAPI tools are used and adopted, facilitating ongoing improvements to our specifications and tools.\n\nTo disable tracking, please run the following command:\n asyncapi config analytics --disable\n\nOnce disabled, if you want to enable tracking back again then run:\n asyncapi config analytics --enable\n', - ); - analyticsConfigFileContent.infoMessageShown = 'true'; - await writeFile( - analyticsConfigFile, - JSON.stringify(analyticsConfigFileContent), - { encoding: 'utf8' }, + case 'development': + // NODE_ENV set to `development` in bin/run + if (!process.env.TEST) { + // Do not pollute stdout when running tests + sink = new StdOutSink(); + } + break; + case 'production': + // NODE_ENV set to `production` in bin/run_bin, which is specified in 'bin' package.json section + sink = new NewRelicSink( + process.env.ASYNCAPI_METRICS_NEWRELIC_KEY || + 'eu01xx73a8521047150dd9414f6aedd2FFFFNRAL', ); - } - break; + + if (analyticsConfigFileContent.infoMessageShown === 'false') { + this.log( + '\nAsyncAPI anonymously tracks command executions to improve the specification and tools, ensuring no sensitive data reaches our servers. It aids in comprehending how AsyncAPI tools are used and adopted, facilitating ongoing improvements to our specifications and tools.\n\nTo disable tracking, please run the following command:\n asyncapi config analytics --disable\n\nOnce disabled, if you want to enable tracking back again then run:\n asyncapi config analytics --enable\n', + ); + analyticsConfigFileContent.infoMessageShown = 'true'; + await writeFile( + analyticsConfigFile, + JSON.stringify(analyticsConfigFileContent), + { encoding: 'utf8' }, + ); + } + break; } } diff --git a/tsconfig.json b/tsconfig.json index a08e2b931..087959f4e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,8 @@ { "compilerOptions": { "outDir": "./lib", + "incremental": true, + "tsBuildInfoFile": "./.tsbuildinfo", "baseUrl": "./src", "target": "es6", "module": "commonjs", @@ -8,15 +10,33 @@ "esnext", ], "paths": { - "@/*": ["*"], - "@src/*": ["*"], - "@cli/*": ["apps/cli/*"], - "@api/*": ["apps/api/*"], - "@services/*": ["domains/services/*"], - "@models/*": ["domains/models/*"], - "@utils/*": ["utils/*"], - "@errors/*": ["errors/*"], - "@interfaces/*": ["interfaces/*"], + "@/*": [ + "*" + ], + "@src/*": [ + "*" + ], + "@cli/*": [ + "apps/cli/*" + ], + "@api/*": [ + "apps/api/*" + ], + "@services/*": [ + "domains/services/*" + ], + "@models/*": [ + "domains/models/*" + ], + "@utils/*": [ + "utils/*" + ], + "@errors/*": [ + "errors/*" + ], + "@interfaces/*": [ + "interfaces/*" + ], }, "declaration": true, "importHelpers": true, @@ -34,4 +54,4 @@ "include": [ "src", ], -} +} \ No newline at end of file