UNPKG

design-agent

Version:

Universal AI Design Review Agent - CLI tool for scanning code for design drift

281 lines (230 loc) 7.53 kB
/** * Validation utilities for design agent * Validates configuration, inputs, and outputs */ export function validateConfig(config) { const errors = []; const warnings = []; // Required fields if (!config.project) { errors.push('Project name is required'); } if (!config.code || !config.code.include) { errors.push('Code include paths are required'); } if (!config.designSystem || !config.designSystem.sources) { errors.push('Design system sources are required'); } // Validate design system sources if (config.designSystem?.sources) { for (const source of config.designSystem.sources) { if (!source.kind) { errors.push('Design system source missing kind'); } if (!source.path) { errors.push('Design system source missing path'); } } } // Validate checks configuration if (config.checks) { const validChecks = [ 'tokenDrift', 'utilities', 'inlineStyles', 'typography', 'contrastStatic', 'storybookProps', 'accessibility', 'security' ]; for (const check of Object.keys(config.checks)) { if (!validChecks.includes(check)) { warnings.push(`Unknown check type: ${check}`); } } } // Validate integrations if (config.integrations) { if (config.integrations.slackWebhook && !isValidUrl(config.integrations.slackWebhook)) { errors.push('Invalid Slack webhook URL'); } } return { errors, warnings }; } export function validateFindings(findings) { const errors = []; for (const finding of findings) { // Required fields if (!finding.file) { errors.push('Finding missing file path'); } if (!finding.line) { errors.push('Finding missing line number'); } if (!finding.kind) { errors.push('Finding missing kind'); } if (!finding.msg) { errors.push('Finding missing message'); } // Validate severity const validSeverities = ['critical', 'major', 'minor']; if (finding.severity && !validSeverities.includes(finding.severity)) { errors.push(`Invalid severity: ${finding.severity}`); } // Validate autofix if present if (finding.autofix) { if (!finding.autofix.before || !finding.autofix.after) { errors.push('Autofix missing before/after values'); } if (finding.autofix.confidence && (finding.autofix.confidence < 0 || finding.autofix.confidence > 100)) { errors.push('Autofix confidence must be between 0 and 100'); } } } return errors; } export function validateEnvironmentVariables() { const errors = []; const warnings = []; // Check for required environment variables const required = ['NODE_ENV']; for (const env of required) { if (!process.env[env]) { errors.push(`Required environment variable missing: ${env}`); } } // Check for optional but recommended environment variables const recommended = ['ANTHROPIC_API_KEY', 'SLACK_WEBHOOK_URL']; for (const env of recommended) { if (!process.env[env]) { warnings.push(`Recommended environment variable not set: ${env}`); } } // Validate API keys if present if (process.env.ANTHROPIC_API_KEY && !process.env.ANTHROPIC_API_KEY.startsWith('sk-ant-')) { errors.push('Invalid Anthropic API key format'); } if (process.env.OPENAI_API_KEY && !process.env.OPENAI_API_KEY.startsWith('sk-')) { errors.push('Invalid OpenAI API key format'); } return { errors, warnings }; } export function validateFilePaths(paths) { const errors = []; const warnings = []; for (const path of paths) { // Check if path exists try { const fs = require('fs'); if (!fs.existsSync(path)) { warnings.push(`Path does not exist: ${path}`); } } catch (error) { // Skip if fs is not available } // Check for dangerous paths if (path.includes('..') || path.includes('~')) { errors.push(`Potentially dangerous path: ${path}`); } // Check for system directories const systemDirs = ['/etc', '/usr', '/bin', '/sbin', '/var']; for (const dir of systemDirs) { if (path.startsWith(dir)) { errors.push(`Accessing system directory: ${path}`); } } } return { errors, warnings }; } export function validateOutputFormat(format) { const validFormats = ['markdown', 'json', 'sarif', 'html']; if (!validFormats.includes(format)) { return `Invalid output format: ${format}. Valid formats: ${validFormats.join(', ')}`; } return null; } export function validateSeverity(severity) { const validSeverities = ['critical', 'major', 'minor']; if (!validSeverities.includes(severity)) { return `Invalid severity: ${severity}. Valid severities: ${validSeverities.join(', ')}`; } return null; } export function validateAdapter(adapter) { const validAdapters = [ 'tailwind', 'tokens', 'storybook', 'vercel', 'openai', 'anthropic', 'langchain', 'llamaindex', 'aws', 'docker', 'vite', 'nodejs' ]; if (!validAdapters.includes(adapter)) { return `Invalid adapter: ${adapter}. Valid adapters: ${validAdapters.join(', ')}`; } return null; } export function generateValidationReport(validationResults) { let report = '## Validation Report\n\n'; if (validationResults.errors.length === 0 && validationResults.warnings.length === 0) { report += '✅ All validations passed!\n'; return report; } if (validationResults.errors.length > 0) { report += '### Errors (Must Fix)\n\n'; validationResults.errors.forEach(error => { report += `- ❌ ${error}\n`; }); report += '\n'; } if (validationResults.warnings.length > 0) { report += '### Warnings (Consider Fixing)\n\n'; validationResults.warnings.forEach(warning => { report += `- ⚠️ ${warning}\n`; }); } return report; } function isValidUrl(url) { try { new URL(url); return true; } catch { return false; } } export function sanitizeInput(input) { if (typeof input !== 'string') { return input; } // Remove potentially dangerous characters return input .replace(/[<>]/g, '') // Remove angle brackets .replace(/javascript:/gi, '') // Remove javascript: protocol .replace(/on\w+=/gi, '') // Remove event handlers .trim(); } export function validateJsonSchema(data, schema) { const errors = []; // Basic schema validation if (schema.required) { for (const field of schema.required) { if (!(field in data)) { errors.push(`Required field missing: ${field}`); } } } if (schema.properties) { for (const [field, rules] of Object.entries(schema.properties)) { if (field in data) { const value = data[field]; if (rules.type && typeof value !== rules.type) { errors.push(`Field ${field} must be of type ${rules.type}`); } if (rules.enum && !rules.enum.includes(value)) { errors.push(`Field ${field} must be one of: ${rules.enum.join(', ')}`); } if (rules.minLength && value.length < rules.minLength) { errors.push(`Field ${field} must be at least ${rules.minLength} characters`); } if (rules.maxLength && value.length > rules.maxLength) { errors.push(`Field ${field} must be at most ${rules.maxLength} characters`); } } } } return errors; }