UNPKG

frontend-standards-checker

Version:

A comprehensive frontend standards validation tool with TypeScript support

193 lines 8.96 kB
import { loadAndLogConfig, analyzeProject, getChangedFiles, returnEarly, createSummary, generateReport, logSummary, processZone, } from './helpers/index.js'; import { Logger } from './utils/logger.js'; import { ConfigLoader } from './core/config-loader.js'; import { FileScanner } from './utils/file-scanner.js'; import { ProjectAnalyzer } from './core/project-analyzer.js'; import { RuleEngine } from './core/rule-engine.js'; import { Reporter } from './core/reporter.js'; /** * Main Frontend Standards Checker class * Orchestrates the validation process and coordinates all modules */ export class FrontendStandardsChecker { options; logger; configLoader; projectAnalyzer; fileScanner; ruleEngine; reporter; constructor(options = {}) { this.options = { zones: [], config: null, output: null, verbose: false, debug: false, skipStructure: false, skipNaming: false, skipContent: false, version: false, help: false, rootDir: process.cwd(), pipelineMode: false, ...options, }; this.logger = new Logger(this.options.verbose || this.options.debug); this.configLoader = new ConfigLoader(this.options.rootDir, this.logger); this.projectAnalyzer = new ProjectAnalyzer(this.options.rootDir, this.logger); this.fileScanner = new FileScanner(this.options.rootDir, this.logger); this.ruleEngine = new RuleEngine(this.logger); this.reporter = new Reporter(this.options.rootDir, this.options.output ?? null, this.logger); } /** * Main execution method * @returns Promise<ValidationResult> - Complete validation results */ async run() { try { const startTime = Date.now(); this.logger.info('🔍 Starting Frontend Standards validation...'); // interactive Prompt to include collaborators in the log let includeCollaborators = true; try { // Only show the prompt if in interactive mode and not in test environment if (process.stdout.isTTY && process.env.NODE_ENV !== 'test' && !process.env.JEST_WORKER_ID) { const inquirer = await import('inquirer'); // Create a timeout promise that resolves after 5 seconds with default value (false) const timeoutPromise = new Promise((resolve) => { setTimeout(() => { console.log('\nTimeout reached. Defaulting to N (no collaborators).'); resolve({ includeCollaborators: false }); }, 5000); }); // Create the inquirer prompt const promptPromise = inquirer.default.prompt([ { type: 'confirm', name: 'includeCollaborators', message: '¿Do you want to include the latest collaborators in the log? (This may take longer) (y/N) [5s timeout]', default: false, }, ]); // Race between the prompt and timeout const answer = await Promise.race([promptPromise, timeoutPromise]); includeCollaborators = answer.includeCollaborators; } else { // In test environment or non-interactive mode, default to false for faster execution includeCollaborators = false; } } catch (e) { this.logger.warn('Could not prompt for collaborators, defaulting to includeCollaborators=true', e); includeCollaborators = true; } const config = await loadAndLogConfig(this.configLoader, this.options, this.logger); const projectInfo = await analyzeProject(this.projectAnalyzer, config, this.logger, this.options); const zonesToValidate = this.determineZones(projectInfo, config); this.logger.info(`🎯 Zones to validate: ${zonesToValidate.join(', ')}`); this.ruleEngine.initialize(config, { skipStructure: this.options.skipStructure ?? false, skipNaming: this.options.skipNaming ?? false, skipContent: this.options.skipContent ?? false, }); let totalFiles = 0; let totalErrors = 0; let totalWarnings = 0; const zoneResults = []; let changedFiles = []; const hasOnlyZone = config.zones?.onlyZone !== undefined; if (this.options.onlyChangedFiles !== false && (this.options.onlyChangedFiles || config.onlyChangedFiles) && !hasOnlyZone) { // Merge both config and options pipelineMode const effectiveConfig = { ...config, pipelineMode: this.options.pipelineMode || config.pipelineMode, }; changedFiles = await getChangedFiles(this.fileScanner, this.logger, effectiveConfig); if (changedFiles.length === 0) { this.logger.info('No files staged for commit found. Nothing to check.'); return returnEarly(startTime); } } else if (hasOnlyZone) { this.logger.info(`🎯 Only checking zone: ${config.zones?.onlyZone} (onlyChangedFiles disabled)`); } else { this.logger.info('🔍 Checking all files in the project'); } for (const zone of zonesToValidate) { const zoneResult = await processZone({ zone, config, changedFiles, hasOnlyZone, options: this.options, rootDir: this.options.rootDir, logger: this.logger, fileScanner: this.fileScanner, ruleEngine: this.ruleEngine, projectInfo, }); zoneResults.push(zoneResult); totalFiles += zoneResult.filesProcessed; totalErrors += zoneResult.errorsCount; totalWarnings += zoneResult.warningsCount; } const result = createSummary(zoneResults, totalFiles, totalErrors, totalWarnings, startTime); // Pass the option to the reporter this.reporter.includeCollaborators = includeCollaborators; // Generate the report and get processed error/warning counts const reportResult = await generateReport(this.reporter, this.logger, zoneResults, projectInfo, config); // Use processed counts from Reporter for console summary // Get zone data from Reporter for summary const processed = this.reporter.processErrors(reportResult.zoneErrors); logSummary(this.logger, result.summary, totalFiles, reportResult.totalErrors, reportResult.totalWarnings, { errorsByZone: processed.errorsByZone, warningsByZone: processed.warningsByZone, infosByZone: processed.infosByZone, }); // Return result, but override totalErrors/totalWarnings to match processed counts return { ...result, totalErrors: reportResult.totalErrors, totalWarnings: reportResult.totalWarnings, }; } catch (error) { this.logger.error('💥 Validation failed:', error); throw error; } } /** * Determine which zones to validate based on project structure and options */ determineZones(projectInfo, config) { if (this.options.zones && this.options.zones.length > 0) { return this.options.zones; } // Handle both formats of zones let zones = []; if (Array.isArray(projectInfo.zones)) { if (typeof projectInfo.zones[0] === 'string') { zones = [...projectInfo.zones]; } else { zones = projectInfo.zones.map((zone) => zone.name); } } // Filter out packages unless explicitly included if (!config.zones?.includePackages) { zones = zones.filter((zone) => !zone.startsWith('packages/')); } return zones.length > 0 ? zones : ['.']; // Default to current directory } } // Export default class and types export default FrontendStandardsChecker; export * from './types/standardConfiguration.type.js'; //# sourceMappingURL=index.js.map