UNPKG

@simonecoelhosfo/optimizely-mcp-server

Version:

Optimizely MCP Server for AI assistants with integrated CLI tools

210 lines 7.05 kB
/** * Simple Compact Progress Reporter - Terminal-compatible compact display * Avoids complex ANSI escape sequences for better compatibility */ import chalk from 'chalk'; import ProgressEventEmitter from '../events/ProgressEventEmitter.js'; export class SimpleCompactReporter { projects = new Map(); totalSteps = 0; completedSteps = 0; startTime = Date.now(); lastProgressUpdate = 0; currentProject = ''; constructor() { // Listen to progress events ProgressEventEmitter.on('progress', this.handleProgressEvent.bind(this)); } /** * Start sync operation */ startSync(operation, options) { console.log(chalk.bold.blue(`🚀 Optimizely Cache Sync`)); console.log(chalk.gray(`${operation}${new Date().toLocaleTimeString()}`)); console.log(''); } /** * Set total steps */ setTotalSteps(projectCount, entitiesPerProject, totalSteps) { this.totalSteps = totalSteps || (projectCount * entitiesPerProject); } /** * Handle progress events */ handleProgressEvent(event) { if (!event.projectName) return; if (event.type === 'start') { if (event.entity === 'project') { this.startProject(event.projectName); } else { this.trackEntity(event.projectName, event.entity); } } else if (event.type === 'progress' && event.count !== undefined) { this.updateEntity(event.projectName, event.entity, event.count, event.isOngoing); } else if (event.type === 'complete') { this.completeEntity(event.projectName, event.entity, event.count); this.completedSteps++; this.printProgress(); } } /** * Start a new project */ startProject(projectName) { this.currentProject = projectName; const isFeature = projectName.toLowerCase().includes('feature') || projectName.toLowerCase().includes('flag'); this.projects.set(projectName, { name: projectName, entities: new Map(), isFeature, printed: false }); } /** * Track an entity */ trackEntity(projectName, entityName) { const project = this.projects.get(projectName); if (!project || project.entities.has(entityName)) return; project.entities.set(entityName, { current: 0, complete: false, lastUpdate: Date.now() }); } /** * Update entity progress */ updateEntity(projectName, entityName, count, isOngoing) { const project = this.projects.get(projectName); if (!project) return; const entity = project.entities.get(entityName); if (!entity || entity.complete) return; entity.current = count; if (!isOngoing) { entity.total = count; } entity.lastUpdate = Date.now(); // Print project status if not yet printed if (!project.printed && projectName === this.currentProject) { this.printProjectStatus(projectName); project.printed = true; } // Throttle progress updates to every 500ms const now = Date.now(); if (now - this.lastProgressUpdate > 500) { this.printProgress(); this.lastProgressUpdate = now; } } /** * Complete an entity */ completeEntity(projectName, entityName, finalCount) { const project = this.projects.get(projectName); if (!project) return; const entity = project.entities.get(entityName); if (!entity) return; entity.complete = true; entity.current = finalCount || entity.current; entity.total = entity.current; // Check if all entities in project are complete const allComplete = Array.from(project.entities.values()).every(e => e.complete); if (allComplete && project.printed) { this.printProjectStatus(projectName); } } /** * Print project status */ printProjectStatus(projectName) { const project = this.projects.get(projectName); if (!project) return; // Print project header if first time if (!project.printed) { console.log(''); // Blank line const icon = project.isFeature ? '📦' : '📂'; console.log(chalk.bold.cyan(`${icon} ${projectName}`)); } // Print entity summary const entities = Array.from(project.entities.entries()); entities.forEach(([name, state], index) => { const isLast = index === entities.length - 1; const prefix = isLast ? '└─' : '│ '; const displayName = name.padEnd(16, ' '); let status = ''; if (state.complete) { const count = state.total || state.current; status = chalk.green(`${count} ✔`); } else { const total = state.total ? `/${state.total}` : ''; status = chalk.yellow(`${state.current}${total} ⟳`); } console.log(`${prefix} ${displayName} ${status}`); }); } /** * Print overall progress */ printProgress() { if (this.totalSteps === 0) return; const percent = Math.round((this.completedSteps / this.totalSteps) * 100); const elapsed = Math.round((Date.now() - this.startTime) / 1000); // Simple progress indicator const blocks = Math.floor(percent / 10); const bar = '▓'.repeat(blocks) + '░'.repeat(10 - blocks); console.log(chalk.dim(`\n[${bar}] ${percent}% • ${this.completedSteps}/${this.totalSteps}${elapsed}s\n`)); } /** * Complete sync operation */ completeSync(result) { // Print final status for all projects this.projects.forEach((project, name) => { if (!project.printed) { this.printProjectStatus(name); } }); console.log(''); console.log(chalk.bold.green('✨ Sync Completed Successfully!')); if (result.duration) { console.log(chalk.gray(`⏱️ Completed in ${(result.duration / 1000).toFixed(1)}s`)); } } /** * Report error */ error(error) { console.log(''); console.error(chalk.red.bold('❌ Sync Failed!')); console.error(chalk.red(error.message)); } /** * Clean up */ dispose() { // Remove event listeners ProgressEventEmitter.removeListener('progress', this.handleProgressEvent.bind(this)); } /** * Legacy updateProgress method (no-op) */ updateProgress(progress) { // Do nothing - we use events only } } //# sourceMappingURL=SimpleCompactReporter.js.map