UNPKG

@casoon/auditmysite

Version:

A comprehensive command-line tool for automated accessibility, security, performance, and SEO testing using Playwright and pa11y, based on sitemap URLs

370 lines 14.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.EventDrivenQueue = void 0; const events_1 = require("events"); class EventDrivenQueue extends events_1.EventEmitter { constructor(options = {}) { super(); this.queue = []; this.completed = []; this.failed = []; this.activeWorkers = new Set(); this.isProcessing = false; this.startTime = null; this.lastProgressUpdate = 0; this.progressUpdateInterval = 1000; // 1 second this.statusInterval = null; this.options = { maxRetries: 3, maxConcurrent: 1, priorityPatterns: [ { pattern: '/home', priority: 10 }, { pattern: '/', priority: 9 }, { pattern: '/about', priority: 8 }, { pattern: '/contact', priority: 7 } ], retryDelay: 1000, enableEvents: true, enableShortStatus: true, // Standardmäßig aktiviert statusUpdateInterval: 2000, // 2 Sekunden ...options }; // Event-Listener für interne Queue-Events if (this.options.enableEvents) { this.setupEventListeners(); } } setupEventListeners() { // Interne Event-Handler this.on('url-added', (url, priority) => { this.emit('queue:urlAdded', { url, priority, timestamp: new Date() }); this.options.eventCallbacks?.onUrlAdded?.(url, priority); }); this.on('url-started', (url) => { this.emit('queue:urlStarted', { url, timestamp: new Date() }); this.options.eventCallbacks?.onUrlStarted?.(url); }); this.on('url-completed', (url, result, duration) => { this.emit('queue:urlCompleted', { url, result, duration, timestamp: new Date() }); this.options.eventCallbacks?.onUrlCompleted?.(url, result, duration); }); this.on('url-failed', (url, error, attempts) => { this.emit('queue:urlFailed', { url, error, attempts, timestamp: new Date() }); this.options.eventCallbacks?.onUrlFailed?.(url, error, attempts); }); this.on('url-retrying', (url, attempts) => { this.emit('queue:urlRetrying', { url, attempts, timestamp: new Date() }); this.options.eventCallbacks?.onUrlRetrying?.(url, attempts); }); this.on('queue-empty', () => { this.emit('queue:empty', { timestamp: new Date() }); this.options.eventCallbacks?.onQueueEmpty?.(); }); this.on('progress-update', (stats) => { this.emit('queue:progressUpdate', { stats, timestamp: new Date() }); this.options.eventCallbacks?.onProgressUpdate?.(stats); }); this.on('error', (error) => { this.emit('queue:error', { error, timestamp: new Date() }); this.options.eventCallbacks?.onError?.(error); }); } addUrls(urls) { const newUrls = urls.filter(url => !this.queue.some(q => q.url === url)); newUrls.forEach(url => { const priority = this.calculatePriority(url); const queuedUrl = { url, priority, status: 'pending', attempts: 0 }; this.queue.push(queuedUrl); this.emit('url-added', url, priority); }); // Sortiere nach Priorität (höchste zuerst) this.queue.sort((a, b) => b.priority - a.priority); this.updateProgress(); } getNextUrl() { const pendingUrl = this.queue.find(q => q.status === 'pending'); if (pendingUrl && this.activeWorkers.size < this.options.maxConcurrent) { pendingUrl.status = 'in-progress'; pendingUrl.startedAt = new Date(); pendingUrl.attempts++; this.activeWorkers.add(pendingUrl.url); this.emit('url-started', pendingUrl.url); this.updateProgress(); return pendingUrl; } return null; } markCompleted(url, result) { const queuedUrl = this.queue.find(q => q.url === url); if (queuedUrl) { queuedUrl.status = 'completed'; queuedUrl.result = result; queuedUrl.completedAt = new Date(); queuedUrl.duration = queuedUrl.completedAt.getTime() - queuedUrl.startedAt.getTime(); this.completed.push(queuedUrl); this.queue = this.queue.filter(q => q.url !== url); this.activeWorkers.delete(url); this.emit('url-completed', url, result, queuedUrl.duration); this.updateProgress(); this.checkQueueEmpty(); } } markFailed(url, error) { const queuedUrl = this.queue.find(q => q.url === url); if (queuedUrl) { if (queuedUrl.attempts < this.options.maxRetries) { // Retry logic queuedUrl.status = 'retrying'; this.emit('url-retrying', url, queuedUrl.attempts); // Schedule retry setTimeout(() => { queuedUrl.status = 'pending'; this.updateProgress(); }, this.options.retryDelay); } else { // Max retries reached queuedUrl.status = 'failed'; queuedUrl.error = error; queuedUrl.completedAt = new Date(); queuedUrl.duration = queuedUrl.completedAt.getTime() - queuedUrl.startedAt.getTime(); this.failed.push(queuedUrl); this.queue = this.queue.filter(q => q.url !== url); this.activeWorkers.delete(url); this.emit('url-failed', url, error, queuedUrl.attempts); this.updateProgress(); this.checkQueueEmpty(); } } } checkQueueEmpty() { if (this.queue.length === 0 && this.activeWorkers.size === 0) { this.emit('queue-empty'); } } updateProgress() { const now = Date.now(); if (now - this.lastProgressUpdate < this.progressUpdateInterval) { return; } this.lastProgressUpdate = now; const stats = this.getStats(); this.emit('progress-update', stats); } getStats() { const total = this.queue.length + this.completed.length + this.failed.length; const pending = this.queue.filter(q => q.status === 'pending').length; const inProgress = this.queue.filter(q => q.status === 'in-progress').length; const completed = this.completed.length; const failed = this.failed.length; const retrying = this.queue.filter(q => q.status === 'retrying').length; const progress = total > 0 ? ((completed + failed) / total) * 100 : 0; // Berechne durchschnittliche Dauer const completedWithDuration = this.completed.filter(q => q.duration); const averageDuration = completedWithDuration.length > 0 ? completedWithDuration.reduce((sum, q) => sum + q.duration, 0) / completedWithDuration.length : 0; // Schätze verbleibende Zeit const remainingItems = pending + inProgress + retrying; const estimatedTimeRemaining = remainingItems > 0 && averageDuration > 0 ? (remainingItems * averageDuration) / this.options.maxConcurrent : 0; // System-Metriken (vereinfacht) const memoryUsage = process.memoryUsage().heapUsed / 1024 / 1024; // MB const cpuUsage = process.cpuUsage().user / 1000000; // Sekunden return { total, pending, inProgress, completed, failed, retrying, progress: Math.round(progress * 100) / 100, averageDuration: Math.round(averageDuration), estimatedTimeRemaining: Math.round(estimatedTimeRemaining), activeWorkers: this.activeWorkers.size, memoryUsage: Math.round(memoryUsage * 100) / 100, cpuUsage: Math.round(cpuUsage * 100) / 100 }; } calculatePriority(url) { const pattern = this.options.priorityPatterns.find(p => url.includes(p.pattern)); return pattern ? pattern.priority : 1; } /** * 🚀 Integrierte Parallel-Verarbeitung der Queue * Verarbeitet alle URLs parallel mit automatischer Status-Berichterstattung */ async processUrls(urls, options) { console.log(`🚀 Starte Queue-Verarbeitung für ${urls.length} URLs mit ${this.options.maxConcurrent} Workern`); this.addUrls(urls); this.startTime = new Date(); // Starte Status-Updates if (this.options.enableShortStatus) { this.startStatusUpdates(options.onShortStatus); } const results = []; const promises = []; // Erstelle Worker-Promises for (let i = 0; i < this.options.maxConcurrent; i++) { promises.push(this.worker(i, options)); } // Warte auf alle Worker await Promise.all(promises); // Stoppe Status-Updates this.stopStatusUpdates(); // Sammle alle Ergebnisse results.push(...this.completed.map(q => q.result)); const duration = Date.now() - this.startTime.getTime(); console.log(`✅ Queue-Verarbeitung abgeschlossen: ${this.completed.length}/${urls.length} URLs in ${duration}ms`); return results; } /** * 🔧 Worker-Funktion für parallele Verarbeitung */ async worker(workerId, options) { while (this.queue.length > 0 || this.activeWorkers.size > 0) { const queuedUrl = this.getNextUrl(); if (!queuedUrl) { // Warte kurz wenn keine URLs verfügbar await new Promise(resolve => setTimeout(resolve, 100)); continue; } try { const result = await options.processor(queuedUrl.url); this.markCompleted(queuedUrl.url, result); options.onResult?.(queuedUrl.url, result); } catch (error) { this.markFailed(queuedUrl.url, String(error)); options.onError?.(queuedUrl.url, String(error)); } } } /** * 📊 Startet kurze Status-Updates */ startStatusUpdates(onShortStatus) { this.statusInterval = setInterval(() => { const stats = this.getStats(); const status = this.generateShortStatus(stats); this.emit('short-status', status); onShortStatus?.(status); this.options.eventCallbacks?.onShortStatus?.(status); }, this.options.statusUpdateInterval); } /** * ⏹️ Stoppt Status-Updates */ stopStatusUpdates() { if (this.statusInterval) { clearInterval(this.statusInterval); this.statusInterval = null; } } /** * 📝 Generiert kurze Status-Nachricht mit verbesserter ETA */ generateShortStatus(stats) { const progress = Math.round(stats.progress); const workers = `${stats.activeWorkers}/${this.options.maxConcurrent}`; const memory = Math.round(stats.memoryUsage); // Verbesserte ETA-Berechnung let eta = '?'; if (stats.estimatedTimeRemaining > 0) { const seconds = Math.round(stats.estimatedTimeRemaining / 1000); if (seconds < 60) { eta = `${seconds}s`; } else if (seconds < 3600) { eta = `${Math.round(seconds / 60)}m`; } else { eta = `${Math.round(seconds / 3600)}h`; } } // Speed-Indicator (URLs pro Minute) const speed = stats.completed > 0 && this.startTime ? Math.round((stats.completed / ((Date.now() - this.startTime.getTime()) / 60000)) * 10) / 10 : 0; // Memory-Warning const memoryWarning = stats.memoryUsage > 500 ? ' ⚠️' : ''; return `📊 ${progress}% | ${stats.completed}/${stats.total} | 🔧 ${workers} | 💾 ${memory}MB${memoryWarning} | ⏱️ ${eta} | 🚀 ${speed}/min`; } // Public API für Event-Listener onUrlAdded(callback) { this.on('queue:urlAdded', callback); return this; } onUrlStarted(callback) { this.on('queue:urlStarted', callback); return this; } onUrlCompleted(callback) { this.on('queue:urlCompleted', callback); return this; } onUrlFailed(callback) { this.on('queue:urlFailed', callback); return this; } onUrlRetrying(callback) { this.on('queue:urlRetrying', callback); return this; } onQueueEmpty(callback) { this.on('queue:empty', callback); return this; } onProgressUpdate(callback) { this.on('queue:progressUpdate', callback); return this; } onError(callback) { this.on('queue:error', callback); return this; } // Utility-Methoden getCompletedResults() { return this.completed.map(q => q.result); } getFailedResults() { return this.failed.map(q => ({ url: q.url, error: q.error, attempts: q.attempts })); } clear() { this.queue = []; this.completed = []; this.failed = []; this.activeWorkers.clear(); this.isProcessing = false; this.startTime = null; } pause() { this.isProcessing = false; } resume() { this.isProcessing = true; } isPaused() { return !this.isProcessing; } getQueueSize() { return this.queue.length; } getActiveWorkers() { return this.activeWorkers.size; } getMaxConcurrent() { return this.options.maxConcurrent; } setMaxConcurrent(max) { this.options.maxConcurrent = max; } } exports.EventDrivenQueue = EventDrivenQueue; //# sourceMappingURL=event-driven-queue.js.map