@simonecoelhosfo/optimizely-mcp-server
Version:
Optimizely MCP Server for AI assistants with integrated CLI tools
189 lines ⢠7.68 kB
JavaScript
/**
* Simple Progress Reporter - Clean, straightforward progress display
* Shows one overall progress bar and simple console logs
*/
import * as cliProgress from 'cli-progress';
import chalk from 'chalk';
export class SimpleProgressReporter {
multiBar;
mainBar = null;
totalSteps = 0;
currentStep = 0;
currentProject = '';
projectNames = new Map();
constructor() {
// Use MultiBar to enable proper logging with multibar.log()
this.multiBar = new cliProgress.MultiBar({
clearOnComplete: false,
hideCursor: true,
forceRedraw: false
}, cliProgress.Presets.shades_classic);
}
/**
* Start sync operation
*/
startSync(operation, options) {
this.multiBar.log(chalk.bold.blue(`š Optimizely Cache Sync\n`));
this.multiBar.log(chalk.gray(`${operation} ⢠${new Date().toLocaleTimeString()}\n`));
}
/**
* Set total steps when we know project count and entity count
*/
setTotalSteps(projectCount, entitiesPerProject, totalSteps) {
this.totalSteps = totalSteps || (projectCount * entitiesPerProject);
// Create the main progress bar
this.mainBar = this.multiBar.create(this.totalSteps, 0, {
label: chalk.cyan('Overall Progress')
});
if (totalSteps) {
this.multiBar.log(chalk.gray(`Total steps: ${this.totalSteps} steps calculated from project platforms and entity configurations\n`));
}
else {
this.multiBar.log(chalk.gray(`Total steps: ${projectCount} projects Ć ${entitiesPerProject} entities = ${this.totalSteps} steps\n`));
}
}
/**
* Update progress for a specific phase
*/
updateProgress(progress) {
const { phase, current, total, message, percent } = progress;
// Handle different types of progress messages
if (phase === 'projects') {
// Project-level messages - extract entity info from message
if (message.includes('Syncing project:')) {
const projectMatch = message.match(/Syncing project:\s*(.+)/);
if (projectMatch) {
const projectName = projectMatch[1].trim(); // This should fix the trailing space
if (projectName !== this.currentProject) {
this.multiBar.log(chalk.bold.cyan(`\nš¦ ${projectName}:`));
this.currentProject = projectName;
}
}
return;
}
// Check for entity starting messages
if (message.includes('Syncing') && !message.includes('Syncing project:')) {
const entityMatch = message.match(/Syncing\s+(\w+)/i);
if (entityMatch) {
const entityType = entityMatch[1].toLowerCase();
this.multiBar.log(chalk.yellow(` ā³ Starting ${entityType}...`));
}
return;
}
// Check if this is actually an entity completion message disguised as project progress
if (message.includes('synced') || message.includes('completed')) {
// Extract entity type from the message
const entityMatch = message.match(/(\w+)\s+(?:synced|completed)/i);
if (entityMatch) {
const entityType = entityMatch[1].toLowerCase();
const recordMatch = message.match(/\((\d+) records?\)/);
const recordCount = recordMatch ? ` (${recordMatch[1]} records)` : '';
this.multiBar.log(chalk.green(` ā
${entityType} completed${recordCount}`));
this.currentStep++;
if (this.mainBar) {
this.mainBar.update(this.currentStep);
}
}
return;
}
// Other project messages - just return
return;
}
// Extract project info from phase name
const parts = phase.split('_');
let projectId = '';
let entityType = phase;
let projectName = 'Unknown Project';
if (parts.length >= 3 && parts[0] === 'project') {
projectId = parts[1];
entityType = parts.slice(2).join('_');
}
// Extract project name from message
const projectMatch = message.match(/^([^:]+):\s*/);
if (projectMatch) {
projectName = projectMatch[1].trim();
this.projectNames.set(projectId, projectName);
}
else if (this.projectNames.has(projectId)) {
projectName = this.projectNames.get(projectId);
}
else if (projectId) {
projectName = `Project ${projectId}`;
}
// Show current project if it changed
if (projectName !== this.currentProject) {
this.multiBar.log(chalk.bold.cyan(`\nš¦ ${projectName}:`));
this.currentProject = projectName;
}
// Format entity type for display
const entityMap = {
'flags': 'flags',
'experiments': 'experiments',
'campaigns': 'campaigns',
'pages': 'pages',
'audiences': 'audiences',
'events': 'events',
'attributes': 'attributes',
'extensions': 'extensions',
'web_environments': 'web environments',
'environments': 'environments',
'groups': 'groups',
'webhooks': 'webhooks',
'change_history': 'change history',
'list_attributes': 'list attributes',
'features': 'features'
};
const displayEntity = entityMap[entityType] || entityType;
// Extract record count from message if available
const recordMatch = message.match(/\((\d+) records?\)/);
const recordCount = recordMatch ? ` (${recordMatch[1]} records)` : '';
// Always show progress updates - simplified
if (current === 0 && total === 1) {
// Starting entity sync
this.multiBar.log(chalk.yellow(` ā³ Starting ${displayEntity}...`));
}
else if (percent >= 100) {
// Completed entity sync
this.multiBar.log(chalk.green(` ā
${displayEntity} completed${recordCount}`));
this.currentStep++;
if (this.mainBar) {
this.mainBar.update(this.currentStep);
}
}
// Always show intermediate progress for any update
if (current > 0 && current < total) {
this.multiBar.log(chalk.blue(` š„ ${displayEntity}: ${current}/${total} (${percent.toFixed(0)}%)`));
}
}
/**
* Complete sync operation
*/
completeSync(result) {
this.multiBar.stop();
this.multiBar.log(chalk.bold.green('\n⨠Sync Completed Successfully!'));
// Show performance metrics
if (result.duration) {
this.multiBar.log(chalk.gray(`ā±ļø Completed in ${(result.duration / 1000).toFixed(1)}s`));
}
if (this.totalSteps > 0) {
this.multiBar.log(chalk.gray(`š Processed ${this.totalSteps} entity sync operations`));
}
}
/**
* Report error
*/
error(error) {
this.multiBar.stop();
this.multiBar.log(chalk.red.bold('\nā Sync Failed!'));
this.multiBar.log(chalk.red(error.message));
}
/**
* Clean up
*/
dispose() {
if (this.multiBar) {
this.multiBar.stop();
}
}
}
//# sourceMappingURL=SimpleProgressReporter.js.map