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.

202 lines (201 loc) 7.96 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ConfigValidator = void 0; /** * Application service for validating configuration files */ class ConfigValidator { /** * Validate configuration object against schema */ validateConfig(config) { const errors = []; const warnings = []; // Check if config is an object if (!config || typeof config !== 'object') { errors.push('Configuration must be an object'); return { isValid: false, errors, warnings }; } // Handle new structure format let structure; let root; if (config.structure) { // New format with structure property structure = config.structure; root = config.root; // Validate root if provided if (root && typeof root !== 'string') { errors.push('Root must be a string'); } } else { // Legacy format (direct structure) structure = config; root = undefined; } // Validate each folder configuration for (const [folder, patterns] of Object.entries(structure)) { const folderValidation = this.validateFolderConfig(folder, patterns); errors.push(...folderValidation.errors); warnings.push(...folderValidation.warnings); } // Check for required global patterns if (!structure['**'] && !structure['global']) { warnings.push('Consider adding global patterns with "**" key for common files like .md, .json, etc.'); } return { isValid: errors.length === 0, errors, warnings }; } /** * Validate individual folder configuration */ validateFolderConfig(folder, patterns) { const errors = []; const warnings = []; // Validate folder name if (!folder || typeof folder !== 'string') { errors.push('Folder name must be a non-empty string'); return { errors, warnings }; } // Check for reserved folder names if (folder === 'root' || folder === 'global') { warnings.push(`"${folder}" is a reserved folder name, consider using a different name`); } // Validate patterns if (Array.isArray(patterns)) { // Simple array format if (patterns.length === 0) { errors.push(`No patterns specified for folder "${folder}"`); } for (const pattern of patterns) { if (typeof pattern !== 'string') { errors.push(`Pattern in folder "${folder}" must be a string, got ${typeof pattern}`); } else if (!pattern.trim()) { errors.push(`Empty pattern found in folder "${folder}"`); } else { const patternValidation = this.validatePattern(pattern); if (!patternValidation.isValid) { errors.push(`Invalid pattern "${pattern}" in folder "${folder}": ${patternValidation.error}`); } } } } else if (typeof patterns === 'object' && patterns !== null) { // Advanced object format const advancedPatterns = patterns; if (!Array.isArray(advancedPatterns.patterns)) { errors.push(`"patterns" must be an array in folder "${folder}"`); } else if (advancedPatterns.patterns.length === 0) { errors.push(`No patterns specified for folder "${folder}"`); } else { // Validate patterns array for (const pattern of advancedPatterns.patterns) { if (typeof pattern !== 'string') { errors.push(`Pattern in folder "${folder}" must be a string, got ${typeof pattern}`); } else { const patternValidation = this.validatePattern(pattern); if (!patternValidation.isValid) { errors.push(`Invalid pattern "${pattern}" in folder "${folder}": ${patternValidation.error}`); } } } // Validate exclude patterns if present if (advancedPatterns.exclude && Array.isArray(advancedPatterns.exclude)) { for (const excludePattern of advancedPatterns.exclude) { if (typeof excludePattern !== 'string') { errors.push(`Exclude pattern in folder "${folder}" must be a string`); } else { const patternValidation = this.validatePattern(excludePattern); if (!patternValidation.isValid) { errors.push(`Invalid exclude pattern "${excludePattern}" in folder "${folder}": ${patternValidation.error}`); } } } } // Validate maxDepth if present if (advancedPatterns.maxDepth !== undefined) { if (typeof advancedPatterns.maxDepth !== 'number' || advancedPatterns.maxDepth < 0) { errors.push(`maxDepth in folder "${folder}" must be a non-negative number`); } } // Validate validator if present if (advancedPatterns.validator !== undefined && typeof advancedPatterns.validator !== 'function') { errors.push(`validator in folder "${folder}" must be a function`); } } } else { errors.push(`Invalid configuration for folder "${folder}": must be an array or object`); } return { errors, warnings }; } /** * Validate individual pattern */ validatePattern(pattern) { if (!pattern || typeof pattern !== 'string') { return { isValid: false, error: 'Pattern must be a non-empty string' }; } // Check for basic glob syntax const validGlobChars = /^[a-zA-Z0-9.*?{}[\]()|\\\/\-_]+$/; if (!validGlobChars.test(pattern)) { return { isValid: false, error: 'Pattern contains invalid characters' }; } // Check for balanced braces in variable placeholders const braceCount = (pattern.match(/\{/g) || []).length; const closeBraceCount = (pattern.match(/\}/g) || []).length; if (braceCount !== closeBraceCount) { return { isValid: false, error: 'Unbalanced braces in variable placeholder' }; } // Check for valid variable placeholder syntax const variablePattern = /\{[^}]+\}/g; const matches = pattern.match(variablePattern); if (matches) { for (const match of matches) { if (match.length < 3) { // {a} is minimum return { isValid: false, error: 'Invalid variable placeholder syntax' }; } } } return { isValid: true }; } /** * Get configuration schema for documentation */ getSchema() { return ` Configuration Schema: { "folderName": string[] | { patterns: string[], exclude?: string[], nested?: boolean, maxDepth?: number, validator?: (file: any) => boolean } } Examples: { "components": ["*.tsx", "*.jsx"], "services": { patterns: ["*.service.ts"], exclude: ["*.test.ts"], nested: true, maxDepth: 2 }, "**": ["*.md", "*.json"] // Global patterns } `; } } exports.ConfigValidator = ConfigValidator; //# sourceMappingURL=ConfigValidator.js.map