UNPKG

@casoon/auditmysite

Version:

Professional website analysis suite with robust accessibility testing, Core Web Vitals performance monitoring, SEO analysis, and content optimization insights. Features isolated browser contexts, retry mechanisms, and comprehensive API endpoints for profe

497 lines • 23.8 kB
"use strict"; /** * 🔒 STRICT AUDIT VALIDATORS - MANDATORY DATA VALIDATION * * Diese Validatoren und Factory-Functions erzwingen vollständige Datenstrukturen. * Sie werfen explizite Fehler wenn kritische Daten fehlen, anstatt mit * Default-Werten oder undefined-Werten weiterzumachen. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.createStrictAccessibility = createStrictAccessibility; exports.createStrictPerformance = createStrictPerformance; exports.createStrictSEO = createStrictSEO; exports.createStrictContentWeight = createStrictContentWeight; exports.createStrictMobileFriendliness = createStrictMobileFriendliness; exports.createStrictAuditPage = createStrictAuditPage; exports.createStrictAuditData = createStrictAuditData; exports.validateStrictAuditData = validateStrictAuditData; exports.validateAllPagesComplete = validateAllPagesComplete; const strict_audit_types_1 = require("../types/strict-audit-types"); // ============================================================================ // FACTORY FUNCTIONS - STRICT CREATION WITH VALIDATION // ============================================================================ /** * Factory: Erstellt strikte Accessibility-Ergebnisse mit vollständiger Validierung */ function createStrictAccessibility(data, pageUrl) { // Validate required fields if (typeof data?.score !== 'number') { throw new strict_audit_types_1.MissingAnalysisError('accessibility', pageUrl, 'score is not a number'); } if (data.score < 0 || data.score > 100) { throw new strict_audit_types_1.MissingAnalysisError('accessibility', pageUrl, `score ${data.score} is out of range 0-100`); } if (!Array.isArray(data?.errors)) { throw new strict_audit_types_1.MissingAnalysisError('accessibility', pageUrl, 'errors is not an array'); } if (!Array.isArray(data?.warnings)) { throw new strict_audit_types_1.MissingAnalysisError('accessibility', pageUrl, 'warnings is not an array'); } if (!Array.isArray(data?.notices)) { throw new strict_audit_types_1.MissingAnalysisError('accessibility', pageUrl, 'notices is not an array'); } // Validate and convert issues const errors = data.errors.map((issue, index) => createStrictAccessibilityIssue(issue, `error[${index}]`, pageUrl)); const warnings = data.warnings.map((issue, index) => createStrictAccessibilityIssue(issue, `warning[${index}]`, pageUrl)); const notices = data.notices.map((issue, index) => createStrictAccessibilityIssue(issue, `notice[${index}]`, pageUrl)); // Determine WCAG level based on score let wcagLevel; if (data.score >= 95) wcagLevel = 'AAA'; else if (data.score >= 80) wcagLevel = 'AA'; else if (data.score >= 60) wcagLevel = 'A'; else wcagLevel = 'none'; return { score: data.score, errors, warnings, notices, totalIssues: errors.length + warnings.length + notices.length, wcagLevel }; } /** * Factory: Erstellt strikte Accessibility-Issue mit vollständiger Validierung */ function createStrictAccessibilityIssue(data, context, pageUrl) { // Handle string-based issues (legacy format) if (typeof data === 'string') { return { code: 'legacy-string-issue', message: data, type: context.includes('error') ? 'error' : context.includes('warning') ? 'warning' : 'notice', selector: null, context: null, impact: null, help: null, helpUrl: null }; } if (!data?.message || typeof data.message !== 'string') { throw new strict_audit_types_1.MissingAnalysisError('accessibility', pageUrl, `${context}: message is required and must be string`); } if (!data?.type || !['error', 'warning', 'notice'].includes(data.type)) { throw new strict_audit_types_1.MissingAnalysisError('accessibility', pageUrl, `${context}: type must be error, warning, or notice`); } return { code: typeof data.code === 'string' ? data.code : 'unknown-rule', message: data.message, type: data.type, selector: typeof data.selector === 'string' ? data.selector : null, context: typeof data.context === 'string' ? data.context : null, impact: data.impact && ['minor', 'moderate', 'serious', 'critical'].includes(data.impact) ? data.impact : null, help: typeof data.help === 'string' ? data.help : null, helpUrl: typeof data.helpUrl === 'string' ? data.helpUrl : null }; } /** * Factory: Erstellt strikte Performance-Ergebnisse mit vollständiger Validierung */ function createStrictPerformance(data, pageUrl) { if (typeof data?.score !== 'number') { throw new strict_audit_types_1.MissingAnalysisError('performance', pageUrl, 'score is not a number'); } if (data.score < 0 || data.score > 100) { throw new strict_audit_types_1.MissingAnalysisError('performance', pageUrl, `score ${data.score} is out of range 0-100`); } if (!data?.grade || !['A', 'B', 'C', 'D', 'F'].includes(data.grade)) { throw new strict_audit_types_1.MissingAnalysisError('performance', pageUrl, 'grade must be A, B, C, D, or F'); } // Validate Core Web Vitals const coreWebVitals = createStrictPerformanceMetrics(data.coreWebVitals, pageUrl); // Validate issues array if (!Array.isArray(data?.issues)) { throw new strict_audit_types_1.MissingAnalysisError('performance', pageUrl, 'issues must be an array'); } const issues = data.issues.filter((issue) => typeof issue === 'string'); // Count budget violations (issues that mention "budget" or "exceeds") const budgetViolations = issues.filter((issue) => issue.toLowerCase().includes('budget') || issue.toLowerCase().includes('exceeds')).length; return { score: data.score, grade: data.grade, coreWebVitals, issues, budgetViolations }; } /** * Factory: Erstellt strikte Performance-Metriken mit vollständiger Validierung */ function createStrictPerformanceMetrics(data, pageUrl) { const requiredMetrics = [ 'largestContentfulPaint', 'firstContentfulPaint', 'cumulativeLayoutShift', 'timeToFirstByte', 'domContentLoaded', 'loadComplete', 'firstPaint' ]; const missingMetrics = []; for (const metric of requiredMetrics) { if (typeof data?.[metric] !== 'number') { missingMetrics.push(metric); } } if (missingMetrics.length > 0) { throw new strict_audit_types_1.MissingAnalysisError('performance', pageUrl, `Missing required metrics: ${missingMetrics.join(', ')}`); } return { largestContentfulPaint: data.largestContentfulPaint, firstContentfulPaint: data.firstContentfulPaint, cumulativeLayoutShift: data.cumulativeLayoutShift, timeToFirstByte: data.timeToFirstByte, domContentLoaded: data.domContentLoaded, loadComplete: data.loadComplete, firstPaint: data.firstPaint }; } /** * Factory: Erstellt strikte SEO-Ergebnisse mit vollständiger Validierung */ function createStrictSEO(data, pageUrl) { if (typeof data?.score !== 'number') { throw new strict_audit_types_1.MissingAnalysisError('seo', pageUrl, 'score is not a number'); } if (data.score < 0 || data.score > 100) { throw new strict_audit_types_1.MissingAnalysisError('seo', pageUrl, `score ${data.score} is out of range 0-100`); } if (!data?.grade || !['A', 'B', 'C', 'D', 'F'].includes(data.grade)) { throw new strict_audit_types_1.MissingAnalysisError('seo', pageUrl, 'grade must be A, B, C, D, or F'); } // Create strict meta tags data const metaTags = createStrictSEOData(data.metaTags, pageUrl); // Validate arrays if (!Array.isArray(data?.issues)) { throw new strict_audit_types_1.MissingAnalysisError('seo', pageUrl, 'issues must be an array'); } const issues = data.issues.filter((issue) => typeof issue === 'string'); const recommendations = Array.isArray(data.recommendations) ? data.recommendations.filter((rec) => typeof rec === 'string') : []; return { score: data.score, grade: data.grade, metaTags, issues, recommendations }; } /** * Factory: Erstellt strikte SEO-Metadaten mit vollständiger Validierung */ function createStrictSEOData(data, pageUrl) { return { title: typeof data?.title === 'string' ? data.title : '', titleLength: typeof data?.titleLength === 'number' ? data.titleLength : (typeof data?.title === 'string' ? data.title.length : 0), description: typeof data?.description === 'string' ? data.description : '', descriptionLength: typeof data?.descriptionLength === 'number' ? data.descriptionLength : (typeof data?.description === 'string' ? data.description.length : 0), keywords: typeof data?.keywords === 'string' ? data.keywords : '', h1Count: typeof data?.h1 === 'number' ? data.h1 : 0, h2Count: typeof data?.h2 === 'number' ? data.h2 : 0, h3Count: typeof data?.h3 === 'number' ? data.h3 : 0, totalImages: typeof data?.total === 'number' ? data.total : (typeof data?.images?.total === 'number' ? data.images.total : 0), imagesWithoutAlt: typeof data?.withoutAlt === 'number' ? data.withoutAlt : (typeof data?.images?.withoutAlt === 'number' ? data.images.withoutAlt : 0), imagesWithEmptyAlt: typeof data?.withEmptyAlt === 'number' ? data.withEmptyAlt : (typeof data?.images?.withEmptyAlt === 'number' ? data.images.withEmptyAlt : 0) }; } /** * Factory: Erstellt strikte Content-Weight-Ergebnisse mit vollständiger Validierung */ function createStrictContentWeight(data, pageUrl) { if (typeof data?.score !== 'number') { throw new strict_audit_types_1.MissingAnalysisError('contentWeight', pageUrl, 'score is not a number'); } if (data.score < 0 || data.score > 100) { throw new strict_audit_types_1.MissingAnalysisError('contentWeight', pageUrl, `score ${data.score} is out of range 0-100`); } if (!data?.grade || !['A', 'B', 'C', 'D', 'F'].includes(data.grade)) { throw new strict_audit_types_1.MissingAnalysisError('contentWeight', pageUrl, 'grade must be A, B, C, D, or F'); } const resources = createStrictContentWeightData(data.resources || data, pageUrl); const optimizations = Array.isArray(data.optimizations) ? data.optimizations.filter((opt) => typeof opt === 'string') : []; // Calculate compression ratio (compressed size / original size) const totalOriginalSize = resources.totalSize; const estimatedCompressedSize = Math.round(totalOriginalSize * 0.7); // Estimate 30% compression const compressionRatio = totalOriginalSize > 0 ? estimatedCompressedSize / totalOriginalSize : 1.0; return { score: data.score, grade: data.grade, resources, optimizations, compressionRatio }; } /** * Factory: Erstellt strikte Content-Weight-Daten mit vollständiger Validierung */ function createStrictContentWeightData(data, pageUrl) { const totalSize = typeof data?.totalSize === 'number' ? data.totalSize : (typeof data?.total === 'number' ? data.total : 0); return { totalSize, html: { size: typeof data?.html?.size === 'number' ? data.html.size : 0, files: typeof data?.html?.files === 'number' ? data.html.files : 1 }, css: { size: typeof data?.css?.size === 'number' ? data.css.size : 0, files: typeof data?.css?.files === 'number' ? data.css.files : 0 }, javascript: { size: typeof data?.javascript?.size === 'number' ? data.javascript.size : 0, files: typeof data?.javascript?.files === 'number' ? data.javascript.files : 0 }, images: { size: typeof data?.images?.size === 'number' ? data.images.size : 0, files: typeof data?.images?.files === 'number' ? data.images.files : 0 }, other: { size: typeof data?.other?.size === 'number' ? data.other.size : 0, files: typeof data?.other?.files === 'number' ? data.other.files : 0 } }; } /** * Factory: Erstellt strikte Mobile-Friendliness-Ergebnisse mit vollständiger Validierung */ function createStrictMobileFriendliness(data, pageUrl) { if (typeof data?.overallScore !== 'number') { throw new strict_audit_types_1.MissingAnalysisError('mobileFriendliness', pageUrl, 'overallScore is not a number'); } if (data.overallScore < 0 || data.overallScore > 100) { throw new strict_audit_types_1.MissingAnalysisError('mobileFriendliness', pageUrl, `overallScore ${data.overallScore} is out of range 0-100`); } if (!data?.grade || !['A', 'B', 'C', 'D', 'F'].includes(data.grade)) { throw new strict_audit_types_1.MissingAnalysisError('mobileFriendliness', pageUrl, 'grade must be A, B, C, D, or F'); } if (!Array.isArray(data?.recommendations)) { throw new strict_audit_types_1.MissingAnalysisError('mobileFriendliness', pageUrl, 'recommendations must be an array'); } const recommendations = data.recommendations.map((rec, index) => createStrictMobileRecommendation(rec, `recommendation[${index}]`, pageUrl)); // Count specific issue types from recommendations const touchTargetIssues = recommendations.filter((r) => r.category === 'Touch Targets').length; const responsiveIssues = recommendations.filter((r) => r.category === 'Media').length; return { overallScore: data.overallScore, grade: data.grade, recommendations, touchTargetIssues, responsiveIssues }; } /** * Factory: Erstellt strikte Mobile-Recommendation mit vollständiger Validierung */ function createStrictMobileRecommendation(data, context, pageUrl) { const validCategories = ['Touch Targets', 'Performance', 'Media', 'Forms', 'Navigation', 'Content']; const validPriorities = ['low', 'medium', 'high', 'critical']; if (!data?.category || !validCategories.includes(data.category)) { throw new strict_audit_types_1.MissingAnalysisError('mobileFriendliness', pageUrl, `${context}: category must be one of: ${validCategories.join(', ')}`); } if (!data?.priority || !validPriorities.includes(data.priority)) { throw new strict_audit_types_1.MissingAnalysisError('mobileFriendliness', pageUrl, `${context}: priority must be one of: ${validPriorities.join(', ')}`); } if (!data?.issue || typeof data.issue !== 'string') { throw new strict_audit_types_1.MissingAnalysisError('mobileFriendliness', pageUrl, `${context}: issue is required and must be string`); } if (!data?.recommendation || typeof data.recommendation !== 'string') { throw new strict_audit_types_1.MissingAnalysisError('mobileFriendliness', pageUrl, `${context}: recommendation is required and must be string`); } if (!data?.impact || typeof data.impact !== 'string') { throw new strict_audit_types_1.MissingAnalysisError('mobileFriendliness', pageUrl, `${context}: impact is required and must be string`); } return { category: data.category, priority: data.priority, issue: data.issue, recommendation: data.recommendation, impact: data.impact }; } // ============================================================================ // MAIN VALIDATION AND FACTORY FUNCTIONS // ============================================================================ /** * Factory: Erstellt strikte Audit-Seite mit vollständiger Validierung aller Analyse-Typen */ function createStrictAuditPage(data) { // Validate basic page data if (!data?.url || typeof data.url !== 'string') { throw new strict_audit_types_1.IncompleteAuditDataError('Page URL is required', ['url']); } if (!data?.title || typeof data.title !== 'string') { throw new strict_audit_types_1.IncompleteAuditDataError('Page title is required', ['title'], data.url); } if (!data?.status || !['passed', 'failed', 'crashed'].includes(data.status)) { throw new strict_audit_types_1.IncompleteAuditDataError('Page status must be passed, failed, or crashed', ['status'], data.url); } if (typeof data?.duration !== 'number') { throw new strict_audit_types_1.IncompleteAuditDataError('Page duration must be a number', ['duration'], data.url); } // CRITICAL: ALL analysis types must be present and valid const missingAnalyses = []; if (!data.accessibility) missingAnalyses.push('accessibility'); if (!data.performance) missingAnalyses.push('performance'); if (!data.seo) missingAnalyses.push('seo'); if (!data.contentWeight) missingAnalyses.push('contentWeight'); if (!data.mobileFriendliness) missingAnalyses.push('mobileFriendliness'); if (missingAnalyses.length > 0) { throw new strict_audit_types_1.IncompleteAuditDataError(`Missing required analysis types for page: ${data.url}`, missingAnalyses, data.url); } // Create strict analysis objects with full validation const accessibility = createStrictAccessibility(data.accessibility, data.url); const performance = createStrictPerformance(data.performance, data.url); const seo = createStrictSEO(data.seo, data.url); const contentWeight = createStrictContentWeight(data.contentWeight, data.url); const mobileFriendliness = createStrictMobileFriendliness(data.mobileFriendliness, data.url); return { url: data.url, title: data.title, status: data.status, duration: data.duration, testedAt: typeof data.testedAt === 'string' ? data.testedAt : new Date().toISOString(), accessibility, performance, seo, contentWeight, mobileFriendliness }; } /** * Main Factory: Erstellt vollständige strikte Audit-Daten mit umfassender Validierung */ function createStrictAuditData(data) { // Validate metadata if (!data?.metadata) { throw new strict_audit_types_1.IncompleteAuditDataError('Metadata is required', ['metadata']); } // Validate summary if (!data?.summary) { throw new strict_audit_types_1.IncompleteAuditDataError('Summary is required', ['summary']); } // Validate pages array if (!Array.isArray(data?.pages)) { throw new strict_audit_types_1.IncompleteAuditDataError('Pages must be an array', ['pages']); } if (data.pages.length === 0) { throw new strict_audit_types_1.IncompleteAuditDataError('Pages array cannot be empty', ['pages']); } // Create strict pages with full validation const strictPages = data.pages.map((pageData) => createStrictAuditPage(pageData)); // Validate that all pages passed the strict validation for (const page of strictPages) { if (!(0, strict_audit_types_1.hasCompleteAnalysis)(page)) { throw new strict_audit_types_1.IncompleteAuditDataError(`Page ${page.url} does not have complete analysis data`, ['complete_analysis'], page.url); } } // Calculate derived summary data const totalErrors = strictPages.reduce((sum, page) => sum + page.accessibility.errors.length, 0); const totalWarnings = strictPages.reduce((sum, page) => sum + page.accessibility.warnings.length, 0); const averageScore = strictPages.reduce((sum, page) => sum + page.accessibility.score, 0) / strictPages.length; let overallGrade; if (averageScore >= 90) overallGrade = 'A'; else if (averageScore >= 75) overallGrade = 'B'; else if (averageScore >= 60) overallGrade = 'C'; else if (averageScore >= 50) overallGrade = 'D'; else overallGrade = 'F'; return { metadata: { version: data.metadata.version || '1.0.0', timestamp: data.metadata.timestamp || new Date().toISOString(), sitemapUrl: data.metadata.sitemapUrl || '', toolVersion: data.metadata.toolVersion || '2.0.0-alpha.2', duration: typeof data.metadata.duration === 'number' ? data.metadata.duration : 0, configuration: { maxPages: typeof data.metadata.maxPages === 'number' ? data.metadata.maxPages : strictPages.length, timeout: typeof data.metadata.timeout === 'number' ? data.metadata.timeout : 30000, standard: typeof data.metadata.standard === 'string' ? data.metadata.standard : 'WCAG2AA', features: Array.isArray(data.metadata.features) ? data.metadata.features : ['accessibility', 'performance', 'seo', 'contentWeight', 'mobileFriendliness'] } }, summary: { totalPages: typeof data.summary.totalPages === 'number' ? data.summary.totalPages : strictPages.length, testedPages: strictPages.length, passedPages: strictPages.filter(p => p.status === 'passed').length, failedPages: strictPages.filter(p => p.status === 'failed').length, crashedPages: strictPages.filter(p => p.status === 'crashed').length, redirectPages: typeof data.summary.redirectPages === 'number' ? data.summary.redirectPages : 0, totalErrors, totalWarnings, averageScore: Math.round(averageScore), overallGrade }, pages: strictPages, systemPerformance: { testCompletionTimeSeconds: Math.round((data.metadata?.duration || 0) / 1000), averageTimePerPageMs: Math.round((data.metadata?.duration || 0) / strictPages.length), throughputPagesPerMinute: Math.round(strictPages.length / ((data.metadata?.duration || 1) / 1000 / 60)), memoryUsageMB: typeof data.systemPerformance?.memoryUsageMB === 'number' ? data.systemPerformance.memoryUsageMB : 0, efficiency: strictPages.length > 0 ? 100.0 : 0.0 } }; } // ============================================================================ // RUNTIME VALIDATION FUNCTIONS // ============================================================================ /** * Runtime-Validator: Prüft ob Audit-Daten vollständig und gültig sind */ function validateStrictAuditData(data) { try { const strictData = createStrictAuditData(data); // If we get here without throwing, data is valid console.log('✅ Strict audit data validation passed'); } catch (error) { if (error instanceof strict_audit_types_1.IncompleteAuditDataError || error instanceof strict_audit_types_1.MissingAnalysisError) { throw error; } throw new strict_audit_types_1.IncompleteAuditDataError(`Audit data validation failed: ${error instanceof Error ? error.message : 'Unknown error'}`, ['validation_error']); } } /** * Runtime-Validator: Prüft ob alle Seiten vollständige Analysen haben */ function validateAllPagesComplete(pages) { const incompletePages = []; pages.forEach((page, index) => { if (!(0, strict_audit_types_1.hasCompleteAnalysis)(page)) { incompletePages.push(page?.url || `page[${index}]`); } }); if (incompletePages.length > 0) { throw new strict_audit_types_1.IncompleteAuditDataError('Some pages do not have complete analysis data', ['complete_analysis'], incompletePages.join(', ')); } } //# sourceMappingURL=strict-audit-validators.js.map