UNPKG

frontend-standards-checker

Version:

A comprehensive frontend standards validation tool with TypeScript support

249 lines (248 loc) 11.7 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __exportStar = (this && this.__exportStar) || function(m, exports) { for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.FrontendStandardsChecker = void 0; const index_js_1 = require("./helpers/index.js"); const logger_js_1 = require("./utils/logger.js"); const config_loader_js_1 = require("./core/config-loader.js"); const file_scanner_js_1 = require("./utils/file-scanner.js"); const project_analyzer_js_1 = require("./core/project-analyzer.js"); const rule_engine_js_1 = require("./core/rule-engine.js"); const reporter_js_1 = require("./core/reporter.js"); /** * Main Frontend Standards Checker class * Orchestrates the validation process and coordinates all modules */ class FrontendStandardsChecker { 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_js_1.Logger(this.options.verbose || this.options.debug); this.configLoader = new config_loader_js_1.ConfigLoader(this.options.rootDir, this.logger); this.projectAnalyzer = new project_analyzer_js_1.ProjectAnalyzer(this.options.rootDir, this.logger); this.fileScanner = new file_scanner_js_1.FileScanner(this.options.rootDir, this.logger); this.ruleEngine = new rule_engine_js_1.RuleEngine(this.logger); this.reporter = new reporter_js_1.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 Promise.resolve().then(() => __importStar(require('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 (0, index_js_1.loadAndLogConfig)(this.configLoader, this.options, this.logger); const projectInfo = await (0, index_js_1.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 (0, index_js_1.getChangedFiles)(this.fileScanner, this.logger, effectiveConfig); if (changedFiles.length === 0) { this.logger.info('No files staged for commit found. Nothing to check.'); return (0, index_js_1.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 (0, index_js_1.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 = (0, index_js_1.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 (0, index_js_1.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); (0, index_js_1.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 } /** * Validate content directly without writing to file * Used by Language Server for real-time validation * @param content - The file content to validate * @param filePath - The file path for context * @returns Promise<IValidationError[]> - Array of validation errors */ async validateContent(content, filePath) { try { // Load configuration if not already loaded if (!this.ruleEngine.config) { const config = await this.configLoader.load(this.options.config); this.ruleEngine.initialize(config); } // Use the RuleEngine's public validateContent method const validationResult = await this.ruleEngine.validateContent(content, filePath); return validationResult; } catch (error) { this.logger.error(`Error validating content for ${filePath}: ${error}`); return []; } } } exports.FrontendStandardsChecker = FrontendStandardsChecker; // Export default class and types exports.default = FrontendStandardsChecker; __exportStar(require("./types/standardConfiguration.type.js"), exports);