frontend-standards-checker
Version:
A comprehensive frontend standards validation tool with TypeScript support
249 lines (248 loc) • 11.7 kB
JavaScript
"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);