From 3ecd12658834d5ceb0a714b2f8e3702da2f5df85 Mon Sep 17 00:00:00 2001 From: Sahil Dahekar Date: Wed, 13 Nov 2024 11:21:36 +0530 Subject: [PATCH] feat: implemented logic to output diagnostics log by adding additional output flag to validate cli command. (#1563) Co-authored-by: Souvik De --- src/core/parser.ts | 100 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 84 insertions(+), 16 deletions(-) diff --git a/src/core/parser.ts b/src/core/parser.ts index 3bbb69c87c3..b692f174652 100644 --- a/src/core/parser.ts +++ b/src/core/parser.ts @@ -12,11 +12,29 @@ import { red, yellow, green, cyan } from 'chalk'; import type { Diagnostic } from '@asyncapi/parser/cjs'; import type Command from './base'; import type { Specification } from './models/SpecificationFile'; +import { promises } from 'fs'; +import path from 'path'; + +type DiagnosticsFormat = 'stylish' | 'json' | 'junit' | 'html' | 'text' | 'teamcity' | 'pretty'; export type SeverityKind = 'error' | 'warn' | 'info' | 'hint'; export { convertToOldAPI }; +const { writeFile } = promises; + +const formatExtensions: Record = { + stylish: '.txt', + json: '.json', + junit: '.xml', + html: '.html', + text: '.txt', + teamcity: '.txt', + pretty: '.txt', +}; + +const validFormats = ['stylish', 'json', 'junit', 'html', 'text', 'teamcity', 'pretty']; + const parser = new Parser({ __unstable: { resolver: { @@ -56,6 +74,10 @@ export function validationFlags({ logDiagnostics = true }: ValidationFlagsOption options: ['error', 'warn', 'info', 'hint'] as const, default: 'error', })(), + output: Flags.string({ + description: 'The output file name. Omitting this flag the result will be printed in the console.', + char: 'o' + }) }; } @@ -63,6 +85,7 @@ export interface ValidateOptions { 'log-diagnostics'?: boolean; 'diagnostics-format'?: `${OutputFormat}`; 'fail-severity'?: SeverityKind; + 'output'?: string; } export async function validate(command: Command, specFile: Specification, options: ValidateOptions = {}) { @@ -76,30 +99,56 @@ export async function parse(command: Command, specFile: Specification, options: return { document, diagnostics, status }; } -function logDiagnostics(diagnostics: Diagnostic[], command: Command, specFile: Specification, options: ValidateOptions = {}): 'valid' | 'invalid' { +function logDiagnostics( + diagnostics: Diagnostic[], + command: Command, + specFile: Specification, + options: ValidateOptions = {} +): 'valid' | 'invalid' { const logDiagnostics = options['log-diagnostics']; const failSeverity = options['fail-severity'] ?? 'error'; const diagnosticsFormat = options['diagnostics-format'] ?? 'stylish'; - const sourceString = specFile.toSourceString(); - if (diagnostics.length) { - if (hasFailSeverity(diagnostics, failSeverity)) { - if (logDiagnostics) { - command.logToStderr(`\n${sourceString} and/or referenced documents have governance issues.`); - command.logToStderr(formatOutput(diagnostics, diagnosticsFormat, failSeverity)); - } - return ValidationStatus.INVALID; - } - if (logDiagnostics) { - command.log(`\n${sourceString} is valid but has (itself and/or referenced documents) governance issues.`); - command.log(formatOutput(diagnostics, diagnosticsFormat, failSeverity)); - } - } else if (logDiagnostics) { + const hasIssues = diagnostics.length > 0; + const isFailSeverity = hasIssues && hasFailSeverity(diagnostics, failSeverity); + + if (logDiagnostics) { + logGovernanceMessage(command, sourceString, hasIssues, isFailSeverity); + outputDiagnostics(command, diagnostics, diagnosticsFormat, failSeverity, options); + } + + return isFailSeverity ? ValidationStatus.INVALID : ValidationStatus.VALID; +} + +function logGovernanceMessage( + command: Command, + sourceString: string, + hasIssues: boolean, + isFailSeverity: boolean +) { + if (!hasIssues) { command.log(`\n${sourceString} is valid! ${sourceString} and referenced documents don't have governance issues.`); + } else if (isFailSeverity) { + command.logToStderr(`\n${sourceString} and/or referenced documents have governance issues.`); + } else { + command.log(`\n${sourceString} is valid but has (itself and/or referenced documents) governance issues.`); } +} - return ValidationStatus.VALID; +function outputDiagnostics( + command: Command, + diagnostics: Diagnostic[], + diagnosticsFormat: DiagnosticsFormat, + failSeverity: SeverityKind, + options: ValidateOptions +) { + const diagnosticsOutput = formatOutput(diagnostics, diagnosticsFormat, failSeverity); + if (options.output) { + writeValidationDiagnostic(options.output, command, diagnosticsFormat, diagnosticsOutput); + } else { + command.log(diagnosticsOutput); + } } export function formatOutput(diagnostics: Diagnostic[], format: `${OutputFormat}`, failSeverity: SeverityKind) { @@ -145,3 +194,22 @@ function hasFailSeverity(diagnostics: Diagnostic[], failSeverity: SeverityKind) const diagnosticSeverity = getDiagnosticSeverity(failSeverity); return diagnostics.some(diagnostic => diagnostic.severity <= diagnosticSeverity); } + +async function writeValidationDiagnostic(outputPath: string, command: Command, format: DiagnosticsFormat, formatOutput: string) { + if (!validFormats.includes(format)) { + command.logToStderr(`Invalid diagnostics format: "${format}"`); + return; + } + + const expectedExtension = formatExtensions[format as keyof typeof formatExtensions]; + const actualExtension = path.extname(outputPath); + + // Validate file extension against diagnostics format + if (expectedExtension && (actualExtension !== expectedExtension)) { + command.logToStderr(`Invalid file extension for format "${format}". Expected extension: "${expectedExtension}"`); + } else { + await writeFile(path.resolve(process.cwd(), outputPath), formatOutput, { + encoding: 'utf-8', + }).catch(err => console.log(err)); + } +}