UNPKG

qraft

Version:

A powerful CLI tool to qraft structured project setups from GitHub template repositories

297 lines 11.5 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ProgressIndicator = void 0; const chalk_1 = __importDefault(require("chalk")); class ProgressIndicator { constructor(options = {}) { this.steps = []; this.currentStepIndex = -1; this.startTime = 0; this.spinnerIndex = 0; this.options = { showPercentage: true, showElapsed: true, showETA: true, showStepDetails: true, clearOnComplete: false, spinnerChars: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'], updateInterval: 100, ...options }; } addStep(step) { this.steps.push({ ...step, status: 'pending', weight: step.weight || 1 }); } addSteps(steps) { steps.forEach(step => this.addStep(step)); } start() { this.startTime = Date.now(); this.currentStepIndex = -1; this.render(); this.startSpinner(); } nextStep() { // Complete current step if running if (this.currentStepIndex >= 0 && this.currentStepIndex < this.steps.length) { const currentStep = this.steps[this.currentStepIndex]; if (currentStep.status === 'running') { currentStep.status = 'completed'; currentStep.endTime = Date.now(); } } // Move to next step this.currentStepIndex++; if (this.currentStepIndex < this.steps.length) { const nextStep = this.steps[this.currentStepIndex]; nextStep.status = 'running'; nextStep.startTime = Date.now(); } this.render(); } completeStep(stepId, success = true) { let step; if (stepId) { step = this.steps.find(s => s.id === stepId); } else if (this.currentStepIndex >= 0 && this.currentStepIndex < this.steps.length) { step = this.steps[this.currentStepIndex]; } if (step) { step.status = success ? 'completed' : 'failed'; step.endTime = Date.now(); this.render(); } } failStep(stepId, error) { let step; if (stepId) { step = this.steps.find(s => s.id === stepId); } else if (this.currentStepIndex >= 0 && this.currentStepIndex < this.steps.length) { step = this.steps[this.currentStepIndex]; } if (step) { step.status = 'failed'; step.endTime = Date.now(); step.error = error; this.render(); } } skipStep(stepId, reason) { let step; if (stepId) { step = this.steps.find(s => s.id === stepId); } else if (this.currentStepIndex >= 0 && this.currentStepIndex < this.steps.length) { step = this.steps[this.currentStepIndex]; } if (step) { step.status = 'skipped'; step.endTime = Date.now(); step.error = reason; this.render(); } } complete() { // Complete any remaining running step if (this.currentStepIndex >= 0 && this.currentStepIndex < this.steps.length) { const currentStep = this.steps[this.currentStepIndex]; if (currentStep.status === 'running') { currentStep.status = 'completed'; currentStep.endTime = Date.now(); } } this.stopSpinner(); this.render(); if (this.options.clearOnComplete) { this.clear(); } } render() { if (!process.stdout.isTTY) return; this.clear(); const progress = this.calculateProgress(); const elapsed = Date.now() - this.startTime; const eta = this.calculateETA(progress, elapsed); // Progress bar const barWidth = 30; const filledWidth = Math.round(barWidth * progress); const emptyWidth = barWidth - filledWidth; const progressBar = '█'.repeat(filledWidth) + '░'.repeat(emptyWidth); let output = ''; // Main progress line output += chalk_1.default.cyan('Progress: '); output += chalk_1.default.green(`[${progressBar}]`); if (this.options.showPercentage) { output += chalk_1.default.yellow(` ${(progress * 100).toFixed(1)}%`); } if (this.options.showElapsed) { output += chalk_1.default.gray(` | Elapsed: ${this.formatTime(elapsed)}`); } if (this.options.showETA && eta > 0) { output += chalk_1.default.gray(` | ETA: ${this.formatTime(eta)}`); } output += '\n'; // Current step with spinner if (this.currentStepIndex >= 0 && this.currentStepIndex < this.steps.length) { const currentStep = this.steps[this.currentStepIndex]; const spinner = currentStep.status === 'running' ? (this.options.spinnerChars?.[this.spinnerIndex] || '') : ''; output += chalk_1.default.blue(`${spinner} ${currentStep.name}`); if (currentStep.description) { output += chalk_1.default.gray(` - ${currentStep.description}`); } output += '\n'; } // Step details if (this.options.showStepDetails) { this.steps.forEach((step, index) => { const icon = this.getStepIcon(step.status); const color = this.getStepColor(step.status); const prefix = index === this.currentStepIndex ? '→ ' : ' '; output += `${prefix}${color(icon)} ${color(step.name)}`; if (step.status === 'failed' && step.error) { output += chalk_1.default.red(` (${step.error})`); } else if (step.status === 'skipped' && step.error) { output += chalk_1.default.yellow(` (${step.error})`); } output += '\n'; }); } process.stdout.write(output); } calculateProgress() { const totalWeight = this.steps.reduce((sum, step) => sum + (step.weight || 1), 0); const completedWeight = this.steps .filter(step => step.status === 'completed') .reduce((sum, step) => sum + (step.weight || 1), 0); // Add partial progress for current running step let runningWeight = 0; if (this.currentStepIndex >= 0 && this.currentStepIndex < this.steps.length) { const currentStep = this.steps[this.currentStepIndex]; if (currentStep.status === 'running' && currentStep.startTime) { // Assume 50% progress for running step (could be more sophisticated) runningWeight = (currentStep.weight || 1) * 0.5; } } return totalWeight > 0 ? (completedWeight + runningWeight) / totalWeight : 0; } calculateETA(progress, elapsed) { if (progress <= 0) return 0; const totalEstimated = elapsed / progress; return Math.max(0, totalEstimated - elapsed); } formatTime(ms) { const seconds = Math.floor(ms / 1000); const minutes = Math.floor(seconds / 60); const hours = Math.floor(minutes / 60); if (hours > 0) { return `${hours}h ${minutes % 60}m ${seconds % 60}s`; } else if (minutes > 0) { return `${minutes}m ${seconds % 60}s`; } else { return `${seconds}s`; } } getStepIcon(status) { switch (status) { case 'pending': return '○'; case 'running': return '●'; case 'completed': return '✓'; case 'failed': return '✗'; case 'skipped': return '⊘'; default: return '○'; } } getStepColor(status) { switch (status) { case 'pending': return chalk_1.default.gray; case 'running': return chalk_1.default.blue; case 'completed': return chalk_1.default.green; case 'failed': return chalk_1.default.red; case 'skipped': return chalk_1.default.yellow; default: return chalk_1.default.gray; } } startSpinner() { this.intervalId = setInterval(() => { this.spinnerIndex = (this.spinnerIndex + 1) % (this.options.spinnerChars?.length || 1); this.render(); }, this.options.updateInterval); } stopSpinner() { if (this.intervalId) { clearInterval(this.intervalId); this.intervalId = undefined; } } clear() { if (!process.stdout.isTTY) return; // Clear previous output const linesToClear = this.options.showStepDetails ? this.steps.length + 3 : 3; for (let i = 0; i < linesToClear; i++) { process.stdout.write('\x1b[1A\x1b[2K'); // Move up and clear line } process.stdout.write('\x1b[1G'); // Move to beginning of line } // Get summary of completed operations getSummary() { const total = this.steps.length; const completed = this.steps.filter(s => s.status === 'completed').length; const failed = this.steps.filter(s => s.status === 'failed').length; const skipped = this.steps.filter(s => s.status === 'skipped').length; const duration = Date.now() - this.startTime; const success = failed === 0 && completed + skipped === total; return { total, completed, failed, skipped, duration, success }; } // Display final summary displaySummary() { const summary = this.getSummary(); console.log(chalk_1.default.cyan('\n📊 Operation Summary:')); console.log(chalk_1.default.gray('─'.repeat(40))); console.log(`${chalk_1.default.yellow('Total Steps:')} ${summary.total}`); console.log(`${chalk_1.default.green('Completed:')} ${summary.completed}`); if (summary.failed > 0) { console.log(`${chalk_1.default.red('Failed:')} ${summary.failed}`); } if (summary.skipped > 0) { console.log(`${chalk_1.default.yellow('Skipped:')} ${summary.skipped}`); } console.log(`${chalk_1.default.blue('Duration:')} ${this.formatTime(summary.duration)}`); console.log(`${chalk_1.default.cyan('Status:')} ${summary.success ? chalk_1.default.green('Success') : chalk_1.default.red('Failed')}`); console.log(chalk_1.default.gray('─'.repeat(40))); } // Test helper methods getSteps() { return [...this.steps]; } getCurrentStepIndex() { return this.currentStepIndex; } getStartTime() { return this.startTime; } // Simulate progress for testing simulateProgress(stepId, _progressPercent) { const step = this.steps.find(s => s.id === stepId); if (step && step.status === 'running') { // This is a simplified simulation - in real use, progress would be tracked differently this.render(); } } } exports.ProgressIndicator = ProgressIndicator; //# sourceMappingURL=progressIndicator.js.map