UNPKG

structure-validation

Version:

A Node.js CLI tool for validating codebase folder and file structure using a clean declarative configuration. Part of the guardz ecosystem for comprehensive TypeScript development.

210 lines 9.31 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ConfigService = void 0; const ValidationRule_1 = require("../../domain/entities/ValidationRule"); const ConfigValidator_1 = require("./ConfigValidator"); const CacheService_1 = require("../../infrastructure/cache/CacheService"); const path_1 = __importDefault(require("path")); const fs_1 = __importDefault(require("fs")); /** * Application service for loading and parsing configuration */ class ConfigService { constructor(configPath = 'structure-validation.config.json') { this.configPath = configPath; this.configValidator = new ConfigValidator_1.ConfigValidator(); this.cacheService = new CacheService_1.CacheService(); } /** * Find configuration file in project root (where package.json is located) */ findConfigInProjectRoot() { let currentDir = process.cwd(); const maxDepth = 10; // Prevent infinite loops for (let depth = 0; depth < maxDepth; depth++) { const packageJsonPath = path_1.default.join(currentDir, 'package.json'); const configPath = path_1.default.join(currentDir, this.configPath); // Check if this directory has a package.json (indicating it's a project root) if (fs_1.default.existsSync(packageJsonPath)) { // Check if config file exists in this directory if (fs_1.default.existsSync(configPath)) { return configPath; } } // Move up one directory const parentDir = path_1.default.dirname(currentDir); if (parentDir === currentDir) { // We've reached the filesystem root break; } currentDir = parentDir; } return null; } /** * Load configuration from file */ async loadConfig() { // First try to find the config file in the project root (where package.json is) let configPath = this.findConfigInProjectRoot(); // If not found in project root, fall back to current directory if (!configPath) { configPath = path_1.default.resolve(this.configPath); } if (!fs_1.default.existsSync(configPath)) { throw new Error(`Configuration file not found: ${this.configPath}\n\n💡 Create a configuration file in your project root:\n\n1. Create structure-validation.config.json\n2. Define your folder structure rules\n3. Run validation again\n\nExample configuration:\n\n{\n "root": "tests/fixtures",\n "structure": {\n "components": ["*.tsx"],\n "services": ["*.service.ts"],\n "utils": ["*.util.ts"],\n "**": ["*.test.ts", "*.md"]\n }\n}`); } // Check if config file has been modified if (this.cacheService.isFileModified(configPath)) { // Invalidate cache when config file changes this.cacheService.invalidate(this.cacheService.getConfigKey(configPath)); } // Try to get from cache first const cacheKey = this.cacheService.getConfigKey(configPath); const cachedConfig = this.cacheService.get(cacheKey); if (cachedConfig) { // Reconstruct ValidationRule instances from cached data const rules = cachedConfig.rules.map(rule => new ValidationRule_1.ValidationRule(rule.folder, rule.patterns)); return cachedConfig.root ? { rules, root: cachedConfig.root } : { rules }; } try { // Read and parse JSON config file const configContent = fs_1.default.readFileSync(configPath, 'utf8'); const config = JSON.parse(configContent); // Validate configuration const validation = this.configValidator.validateConfig(config); if (!validation.isValid) { const errorMessage = `Configuration validation failed:\n\n${validation.errors.map(error => `❌ ${error}`).join('\n')}`; if (validation.warnings.length > 0) { const warningMessage = `\n\n⚠️ Warnings:\n${validation.warnings.map(warning => `• ${warning}`).join('\n')}`; throw new Error(errorMessage + warningMessage); } throw new Error(errorMessage); } // Show warnings if any if (validation.warnings.length > 0) { console.warn('⚠️ Configuration warnings:'); validation.warnings.forEach(warning => console.warn(` • ${warning}`)); console.warn(''); } const parsedConfig = this.parseConfig(config); // Cache the parsed configuration data (without ValidationRule instances) const cacheData = { rules: parsedConfig.rules.map(rule => ({ folder: rule.folder, patterns: rule.patterns })), root: parsedConfig.root }; this.cacheService.set(cacheKey, cacheData, 10 * 60 * 1000); return parsedConfig; } catch (error) { if (error instanceof Error && error.message.includes('Configuration validation failed')) { throw error; } throw new Error(`Failed to load configuration: ${error instanceof Error ? error.message : String(error)}`); } } /** * Parse configuration object into ValidationConfig */ parseConfig(config) { let structure; let root; // Handle new structure format if (config.structure) { structure = config.structure; root = config.root; } else { // Handle legacy format (direct structure) structure = config; root = undefined; } // If root is not defined, try to detect it based on project type if (!root) { root = this.detectProjectRoot(); } const rules = []; for (const [folder, patterns] of Object.entries(structure)) { if (!Array.isArray(patterns)) { throw new Error(`Invalid patterns for folder '${folder}': must be an array`); } if (patterns.length === 0) { throw new Error(`No patterns specified for folder '${folder}'`); } rules.push(new ValidationRule_1.ValidationRule(folder, patterns)); } if (rules.length === 0) { throw new Error('No validation rules found in configuration'); } return { rules, root }; } /** * Detect project root based on project type */ detectProjectRoot() { const currentDir = process.cwd(); // Check for TypeScript project if (fs_1.default.existsSync(path_1.default.join(currentDir, 'tsconfig.json'))) { try { const tsconfig = JSON.parse(fs_1.default.readFileSync(path_1.default.join(currentDir, 'tsconfig.json'), 'utf8')); if (tsconfig.compilerOptions?.rootDir) { return tsconfig.compilerOptions.rootDir; } if (tsconfig.include && tsconfig.include.length > 0) { // Use the first include pattern as root const firstInclude = tsconfig.include[0]; if (firstInclude.endsWith('/**/*')) { return firstInclude.replace('/**/*', ''); } if (firstInclude.endsWith('/*')) { return firstInclude.replace('/*', ''); } return firstInclude; } } catch (error) { console.warn('⚠️ Failed to parse tsconfig.json, using default root'); } } // Check for JavaScript project if (fs_1.default.existsSync(path_1.default.join(currentDir, 'package.json'))) { try { const packageJson = JSON.parse(fs_1.default.readFileSync(path_1.default.join(currentDir, 'package.json'), 'utf8')); if (packageJson.main) { const mainDir = path_1.default.dirname(packageJson.main); if (mainDir !== '.') { return mainDir; } } } catch (error) { console.warn('⚠️ Failed to parse package.json, using default root'); } } // Default to 'src' if it exists, otherwise use current directory if (fs_1.default.existsSync(path_1.default.join(currentDir, 'src'))) { return 'src'; } return '.'; } /** * Get configuration schema for documentation */ getSchema() { return this.configValidator.getSchema(); } /** * Clear configuration cache */ clearCache() { this.cacheService.invalidate(this.cacheService.getConfigKey(this.configPath)); } } exports.ConfigService = ConfigService; //# sourceMappingURL=ConfigService.js.map