frontend-standards-checker
Version:
A comprehensive frontend standards validation tool with TypeScript support
193 lines • 8.96 kB
JavaScript
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