UNPKG

dna-template-cli

Version:

DNA Template CLI v0.3.4 - Enhanced Commands Added (enhanced-create, enhanced-list, enhanced-validate)

533 lines 22.9 kB
"use strict"; /** * @fileoverview Validation framework with schema validation for template.json files and runtime validation * Provides comprehensive validation utilities and health checks */ Object.defineProperty(exports, "__esModule", { value: true }); exports.ValidationFramework = exports.projectHealthSchema = exports.templateConfigSchema = exports.dnaModuleSchema = void 0; const tslib_1 = require("tslib"); const fs_extra_1 = tslib_1.__importDefault(require("fs-extra")); const path_1 = tslib_1.__importDefault(require("path")); const zod_1 = require("zod"); const semver_1 = tslib_1.__importDefault(require("semver")); const validation_engine_1 = require("./validation-engine"); // DNA Module Schema exports.dnaModuleSchema = zod_1.z.object({ id: zod_1.z.string().min(1), name: zod_1.z.string().min(1), description: zod_1.z.string(), version: zod_1.z.string().regex(/^\d+\.\d+\.\d+$/), author: zod_1.z.string(), license: zod_1.z.string().optional(), category: zod_1.z.enum(['auth', 'payment', 'database', 'ui', 'analytics', 'deployment', 'testing', 'monitoring']), compatibleFrameworks: zod_1.z.array(zod_1.z.enum(['nextjs', 'react-native', 'flutter', 'tauri', 'sveltekit', 'express', 'fastapi'])), dependencies: zod_1.z.record(zod_1.z.string()).optional(), devDependencies: zod_1.z.record(zod_1.z.string()).optional(), peerDependencies: zod_1.z.record(zod_1.z.string()).optional(), configuration: zod_1.z.object({ required: zod_1.z.array(zod_1.z.string()).optional(), optional: zod_1.z.array(zod_1.z.string()).optional(), environment: zod_1.z.record(zod_1.z.string()).optional(), }).optional(), conflicts: zod_1.z.array(zod_1.z.string()).optional(), requires: zod_1.z.array(zod_1.z.string()).optional(), files: zod_1.z.array(zod_1.z.object({ source: zod_1.z.string(), target: zod_1.z.string(), type: zod_1.z.enum(['template', 'copy', 'merge']).optional(), })).optional(), scripts: zod_1.z.record(zod_1.z.string()).optional(), metadata: zod_1.z.record(zod_1.z.unknown()).optional(), }); // Template Configuration Schema exports.templateConfigSchema = zod_1.z.object({ templateVersion: zod_1.z.string().regex(/^\d+\.\d+\.\d+$/), metadata: zod_1.z.object({ name: zod_1.z.string().min(1), description: zod_1.z.string(), author: zod_1.z.string(), license: zod_1.z.string().optional(), repository: zod_1.z.string().url().optional(), homepage: zod_1.z.string().url().optional(), keywords: zod_1.z.array(zod_1.z.string()).optional(), }), structure: zod_1.z.object({ baseDirectory: zod_1.z.string().optional(), files: zod_1.z.array(zod_1.z.object({ source: zod_1.z.string(), target: zod_1.z.string(), type: zod_1.z.enum(['template', 'copy', 'merge']).default('copy'), condition: zod_1.z.string().optional(), })), directories: zod_1.z.array(zod_1.z.string()).optional(), }), variables: zod_1.z.array(zod_1.z.object({ name: zod_1.z.string().min(1), description: zod_1.z.string(), type: zod_1.z.enum(['string', 'number', 'boolean', 'select', 'multiselect']), required: zod_1.z.boolean().default(false), default: zod_1.z.union([zod_1.z.string(), zod_1.z.number(), zod_1.z.boolean(), zod_1.z.array(zod_1.z.string())]).optional(), options: zod_1.z.array(zod_1.z.string()).optional(), validation: zod_1.z.object({ pattern: zod_1.z.string().optional(), min: zod_1.z.number().optional(), max: zod_1.z.number().optional(), minLength: zod_1.z.number().optional(), maxLength: zod_1.z.number().optional(), }).optional(), condition: zod_1.z.string().optional(), })).optional(), scripts: zod_1.z.object({ preGenerate: zod_1.z.array(zod_1.z.string()).optional(), postGenerate: zod_1.z.array(zod_1.z.string()).optional(), preInstall: zod_1.z.array(zod_1.z.string()).optional(), postInstall: zod_1.z.array(zod_1.z.string()).optional(), }).optional(), requirements: zod_1.z.object({ node: zod_1.z.string().optional(), npm: zod_1.z.string().optional(), yarn: zod_1.z.string().optional(), pnpm: zod_1.z.string().optional(), bun: zod_1.z.string().optional(), tools: zod_1.z.array(zod_1.z.object({ name: zod_1.z.string(), version: zod_1.z.string().optional(), optional: zod_1.z.boolean().default(false), installInstructions: zod_1.z.string().optional(), })).optional(), platforms: zod_1.z.array(zod_1.z.enum(['darwin', 'linux', 'win32'])).optional(), }).optional(), dnaModules: zod_1.z.array(zod_1.z.string()).optional(), extends: zod_1.z.string().optional(), }); // Project Health Schema exports.projectHealthSchema = zod_1.z.object({ name: zod_1.z.string(), version: zod_1.z.string(), description: zod_1.z.string().optional(), main: zod_1.z.string().optional(), scripts: zod_1.z.object({ dev: zod_1.z.string().optional(), build: zod_1.z.string().optional(), start: zod_1.z.string().optional(), test: zod_1.z.string().optional(), lint: zod_1.z.string().optional(), }).optional(), dependencies: zod_1.z.record(zod_1.z.string()).optional(), devDependencies: zod_1.z.record(zod_1.z.string()).optional(), engines: zod_1.z.object({ node: zod_1.z.string().optional(), npm: zod_1.z.string().optional(), }).optional(), }); class ValidationFramework { constructor() { this.schemaCache = new Map(); this.validationEngine = validation_engine_1.ValidationEngine.getInstance(); this.initializeSchemas(); } static getInstance() { if (!ValidationFramework.instance) { ValidationFramework.instance = new ValidationFramework(); } return ValidationFramework.instance; } /** * Validate a template.json file */ async validateTemplateConfig(templatePath) { const result = { valid: true, errors: [], warnings: [], suggestions: [], }; try { const configPath = path_1.default.join(templatePath, 'template.json'); if (!(await fs_extra_1.default.pathExists(configPath))) { result.errors.push('template.json file not found'); result.suggestions.push('Create a template.json file with proper template configuration'); result.valid = false; return result; } const configContent = await fs_extra_1.default.readJSON(configPath); // Validate against schema const validationResult = exports.templateConfigSchema.safeParse(configContent); if (!validationResult.success) { for (const error of validationResult.error.errors) { result.errors.push(`Schema validation error: ${error.path.join('.')} - ${error.message}`); } result.valid = false; return result; } const config = validationResult.data; // Validate template structure await this.validateTemplateStructure(templatePath, config, result); // Validate DNA modules await this.validateTemplateDnaModules(config, result); // Validate requirements await this.validateTemplateRequirements(config, result); // Validate variables this.validateTemplateVariables(config, result); result.valid = result.errors.length === 0; } catch (error) { result.errors.push(`Failed to validate template config: ${error instanceof Error ? error.message : 'Unknown error'}`); result.valid = false; } return result; } /** * Validate DNA module configuration */ async validateDnaModule(modulePath) { const result = { valid: true, errors: [], warnings: [], suggestions: [], }; try { const configPath = path_1.default.join(modulePath, 'dna-module.json'); if (!(await fs_extra_1.default.pathExists(configPath))) { result.errors.push('dna-module.json file not found'); result.suggestions.push('Create a dna-module.json file with proper module configuration'); result.valid = false; return result; } const configContent = await fs_extra_1.default.readJSON(configPath); // Validate against schema const validationResult = exports.dnaModuleSchema.safeParse(configContent); if (!validationResult.success) { for (const error of validationResult.error.errors) { result.errors.push(`Schema validation error: ${error.path.join('.')} - ${error.message}`); } result.valid = false; return result; } const config = validationResult.data; // Validate module files await this.validateModuleFiles(modulePath, config, result); // Validate dependencies this.validateModuleDependencies(config, result); result.valid = result.errors.length === 0; } catch (error) { result.errors.push(`Failed to validate DNA module: ${error instanceof Error ? error.message : 'Unknown error'}`); result.valid = false; } return result; } /** * Validate generated project health */ async validateProjectHealth(projectPath) { const result = { valid: true, errors: [], warnings: [], suggestions: [], }; try { // Check package.json const packageJsonPath = path_1.default.join(projectPath, 'package.json'); if (await fs_extra_1.default.pathExists(packageJsonPath)) { const packageJson = await fs_extra_1.default.readJSON(packageJsonPath); const validationResult = exports.projectHealthSchema.safeParse(packageJson); if (!validationResult.success) { for (const error of validationResult.error.errors) { result.warnings.push(`package.json issue: ${error.path.join('.')} - ${error.message}`); } } // Check for security vulnerabilities in dependencies await this.checkDependencySecurityIssues(packageJson, result); } else { result.errors.push('package.json not found'); } // Check essential project files await this.validateEssentialFiles(projectPath, result); // Check DNA configuration await this.validateDnaConfiguration(projectPath, result); // Check git repository await this.validateGitRepository(projectPath, result); // Check file permissions await this.validateFilePermissions(projectPath, result); result.valid = result.errors.length === 0; } catch (error) { result.errors.push(`Failed to validate project health: ${error instanceof Error ? error.message : 'Unknown error'}`); result.valid = false; } return result; } /** * Runtime validation for user inputs */ validateUserInput(input, schema, fieldName) { const result = { valid: true, errors: [], warnings: [], suggestions: [], }; try { const validationResult = schema.safeParse(input); if (!validationResult.success) { for (const error of validationResult.error.errors) { result.errors.push(`${fieldName} validation error: ${error.path.join('.')} - ${error.message}`); } result.valid = false; } } catch (error) { result.errors.push(`Runtime validation failed for ${fieldName}: ${error instanceof Error ? error.message : 'Unknown error'}`); result.valid = false; } return result; } /** * Comprehensive pre-generation validation */ async runComprehensiveValidation(config, templateMetadata) { const result = { valid: true, errors: [], warnings: [], suggestions: [], }; try { // Pre-generation validation const preGenResult = await this.validationEngine.validatePreGeneration(config); this.mergeValidationResults(result, preGenResult); // Template validation const templateResult = await this.validationEngine.validateTemplate(templateMetadata); this.mergeValidationResults(result, templateResult); // Environment validation const envResult = await this.validationEngine.validateEnvironment(); this.mergeValidationResults(result, envResult); // Dependency validation const depResult = await this.validationEngine.validateDependencies(config, templateMetadata); this.mergeValidationResults(result, depResult); result.valid = result.errors.length === 0; } catch (error) { result.errors.push(`Comprehensive validation failed: ${error instanceof Error ? error.message : 'Unknown error'}`); result.valid = false; } return result; } /** * Create custom validation schema */ createCustomSchema(schemaDefinition) { return schemaDefinition; } /** * Register custom schema */ registerSchema(name, schema) { this.schemaCache.set(name, schema); } /** * Get registered schema */ getSchema(name) { return this.schemaCache.get(name); } // Private helper methods initializeSchemas() { this.schemaCache.set('template', exports.templateConfigSchema); this.schemaCache.set('dnaModule', exports.dnaModuleSchema); this.schemaCache.set('projectHealth', exports.projectHealthSchema); } async validateTemplateStructure(templatePath, config, result) { if (config.structure?.files) { for (const file of config.structure.files) { const sourcePath = path_1.default.join(templatePath, file.source); if (!(await fs_extra_1.default.pathExists(sourcePath))) { result.errors.push(`Template file not found: ${file.source}`); } } } if (config.structure?.directories) { for (const dir of config.structure.directories) { const dirPath = path_1.default.join(templatePath, dir); if (!(await fs_extra_1.default.pathExists(dirPath))) { result.warnings.push(`Template directory not found: ${dir}`); } } } } async validateTemplateDnaModules(config, result) { if (config.dnaModules) { for (const moduleId of config.dnaModules) { // In a real implementation, this would check against the DNA module registry if (!this.isDnaModuleValid(moduleId)) { result.warnings.push(`DNA module may not be available: ${moduleId}`); } } } } async validateTemplateRequirements(config, result) { if (config.requirements) { const req = config.requirements; // Validate version requirements if (req.node && !semver_1.default.validRange(req.node)) { result.errors.push(`Invalid Node.js version requirement: ${req.node}`); } if (req.npm && !semver_1.default.validRange(req.npm)) { result.errors.push(`Invalid npm version requirement: ${req.npm}`); } // Validate platform requirements if (req.platforms) { const validPlatforms = ['darwin', 'linux', 'win32']; for (const platform of req.platforms) { if (!validPlatforms.includes(platform)) { result.errors.push(`Invalid platform requirement: ${platform}`); } } } } } validateTemplateVariables(config, result) { if (config.variables) { for (const variable of config.variables) { // Validate select/multiselect options if ((variable.type === 'select' || variable.type === 'multiselect') && !variable.options) { result.errors.push(`Variable '${variable.name}' of type '${variable.type}' must have options`); } // Validate default values if (variable.default !== undefined) { if (variable.type === 'number' && typeof variable.default !== 'number') { result.errors.push(`Variable '${variable.name}' default value must be a number`); } if (variable.type === 'boolean' && typeof variable.default !== 'boolean') { result.errors.push(`Variable '${variable.name}' default value must be a boolean`); } } // Validate pattern if provided if (variable.validation?.pattern) { try { new RegExp(variable.validation.pattern); } catch { result.errors.push(`Variable '${variable.name}' has invalid validation pattern`); } } } } } async validateModuleFiles(modulePath, config, result) { if (config.files) { for (const file of config.files) { const sourcePath = path_1.default.join(modulePath, file.source); if (!(await fs_extra_1.default.pathExists(sourcePath))) { result.errors.push(`Module file not found: ${file.source}`); } } } } validateModuleDependencies(config, result) { // Check for dependency conflicts if (config.conflicts) { for (const conflict of config.conflicts) { if (config.requires?.includes(conflict)) { result.errors.push(`Module cannot both require and conflict with: ${conflict}`); } } } // Validate version ranges const depTypes = ['dependencies', 'devDependencies', 'peerDependencies']; for (const depType of depTypes) { if (config[depType]) { for (const [pkg, version] of Object.entries(config[depType])) { if (typeof version === 'string' && !semver_1.default.validRange(version)) { result.warnings.push(`Invalid version range for ${pkg}: ${version}`); } } } } } async validateEssentialFiles(projectPath, result) { const essentialFiles = [ 'README.md', '.gitignore', 'package.json', ]; for (const file of essentialFiles) { const filePath = path_1.default.join(projectPath, file); if (!(await fs_extra_1.default.pathExists(filePath))) { result.warnings.push(`Essential file missing: ${file}`); } } } async validateDnaConfiguration(projectPath, result) { const dnaConfigPath = path_1.default.join(projectPath, 'dna.config.json'); if (await fs_extra_1.default.pathExists(dnaConfigPath)) { try { const dnaConfig = await fs_extra_1.default.readJSON(dnaConfigPath); // Basic validation if (!dnaConfig.template) { result.warnings.push('DNA config missing template information'); } if (!dnaConfig.generated) { result.warnings.push('DNA config missing generation timestamp'); } } catch (error) { result.errors.push('Invalid DNA configuration file'); } } else { result.warnings.push('DNA configuration file not found'); } } async validateGitRepository(projectPath, result) { const gitPath = path_1.default.join(projectPath, '.git'); if (!(await fs_extra_1.default.pathExists(gitPath))) { result.suggestions.push('Initialize git repository for version control'); } } async validateFilePermissions(projectPath, result) { try { await fs_extra_1.default.access(projectPath, fs_extra_1.default.constants.R_OK | fs_extra_1.default.constants.W_OK); } catch (error) { result.warnings.push('Project directory may have permission issues'); } } async checkDependencySecurityIssues(packageJson, result) { // In a real implementation, this would check against security databases // For now, just check for known problematic packages const problematicPackages = ['node-sass', 'bower', 'gulp']; const allDeps = { ...packageJson.dependencies, ...packageJson.devDependencies, }; for (const pkg of problematicPackages) { if (allDeps[pkg]) { result.warnings.push(`Consider replacing deprecated package: ${pkg}`); } } } mergeValidationResults(target, source) { target.errors.push(...source.errors); target.warnings.push(...source.warnings); target.suggestions.push(...source.suggestions); target.valid = target.valid && source.valid; } isDnaModuleValid(moduleId) { // In a real implementation, this would check against the DNA module registry const commonModules = [ 'auth-firebase', 'auth-supabase', 'auth-cognito', 'payment-stripe', 'payment-paypal', 'database-postgres', 'database-mongodb', 'database-mysql', 'ui-tailwind', 'ui-material', 'ui-chakra', 'mobile-navigation', 'web-analytics', ]; return commonModules.includes(moduleId); } } exports.ValidationFramework = ValidationFramework; //# sourceMappingURL=validation-framework.js.map