@simonecoelhosfo/optimizely-mcp-server
Version:
Optimizely MCP Server for AI assistants with integrated CLI tools
210 lines • 7.05 kB
JavaScript
/**
* 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