UNPKG

appwrite-utils-cli

Version:

Appwrite Utility Functions to help with database management, data conversion, data import, migrations, and much more. Meant to be used as a CLI tool, I do not recommend installing this in frontend environments.

216 lines (215 loc) 7.42 kB
import cliProgress from "cli-progress"; import chalk from "chalk"; import { MessageFormatter } from "./messageFormatter.js"; export class ProgressManager { static instances = new Map(); bar; startTime; totalItems; completed = 0; id; title; constructor(id, total, options = {}) { this.id = id; this.totalItems = total; this.startTime = Date.now(); this.title = options.title || "Processing"; const format = options.format || `${chalk.cyan(this.title)} ${chalk.yellow("[{bar}]")} {percentage}% | {value}/{total} | ETA: {eta}s | Speed: {speed}/s`; this.bar = new cliProgress.SingleBar({ format, barCompleteChar: '█', barIncompleteChar: '░', hideCursor: true, clearOnComplete: false, stopOnComplete: true, ...options, }, cliProgress.Presets.shades_classic); this.bar.start(total, 0); } static create(id, total, options = {}) { if (ProgressManager.instances.has(id)) { const existing = ProgressManager.instances.get(id); existing.stop(); } const instance = new ProgressManager(id, total, options); ProgressManager.instances.set(id, instance); return instance; } static get(id) { return ProgressManager.instances.get(id); } update(current, detail) { this.completed = current; // Calculate speed const elapsed = (Date.now() - this.startTime) / 1000; const speed = elapsed > 0 ? Math.round(current / elapsed) : 0; this.bar.update(current, { speed, detail: detail || '', }); if (detail) { // Update the payload for custom formatting this.bar.update(current, { detail }); } } increment(amount = 1, detail) { this.update(this.completed + amount, detail); } setTotal(total) { this.totalItems = total; this.bar.setTotal(total); } stop(showSummary = true) { this.bar.stop(); if (showSummary) { const duration = Date.now() - this.startTime; const rate = this.completed / (duration / 1000); MessageFormatter.success(`${this.title} completed`, { prefix: `${this.completed}/${this.totalItems} items in ${MessageFormatter.formatDuration(duration)} (${rate.toFixed(1)}/s)` }); } ProgressManager.instances.delete(this.id); } fail(error) { this.bar.stop(); MessageFormatter.error(`${this.title} failed: ${error}`); ProgressManager.instances.delete(this.id); } getStats() { const duration = Date.now() - this.startTime; const rate = this.completed / (duration / 1000); return { completed: this.completed, total: this.totalItems, percentage: (this.completed / this.totalItems) * 100, duration, rate, remaining: this.totalItems - this.completed, eta: this.completed > 0 ? ((this.totalItems - this.completed) / rate) * 1000 : 0, }; } static stopAll() { for (const [id, instance] of ProgressManager.instances) { instance.stop(false); } ProgressManager.instances.clear(); } } export class MultiProgressManager { multiBar; bars = new Map(); startTime; constructor(options = {}) { this.startTime = Date.now(); this.multiBar = new cliProgress.MultiBar({ clearOnComplete: false, hideCursor: true, format: options.format || `${chalk.cyan("{title}")} ${chalk.yellow("[{bar}]")} {percentage}% | {value}/{total} | {detail}`, barCompleteChar: '█', barIncompleteChar: '░', }, cliProgress.Presets.shades_classic); } addTask(id, total, title) { const bar = this.multiBar.create(total, 0, { title: title.padEnd(20), detail: '', }); this.bars.set(id, bar); } updateTask(id, current, detail) { const bar = this.bars.get(id); if (bar) { bar.update(current, { detail: detail || '', }); } } incrementTask(id, amount = 1, detail) { const bar = this.bars.get(id); if (bar) { const currentValue = bar.getProgress() * bar.getTotal(); this.updateTask(id, currentValue + amount, detail); } } completeTask(id) { const bar = this.bars.get(id); if (bar) { bar.update(bar.getTotal()); } } failTask(id, error) { const bar = this.bars.get(id); if (bar) { bar.update(bar.getProgress() * bar.getTotal(), { detail: chalk.red(`Failed: ${error}`), }); } } stop(showSummary = true) { this.multiBar.stop(); if (showSummary) { const duration = Date.now() - this.startTime; MessageFormatter.success(`All tasks completed in ${MessageFormatter.formatDuration(duration)}`); } } getTaskStats(id) { const bar = this.bars.get(id); if (!bar) return null; const progress = bar.getProgress(); const total = bar.getTotal(); const completed = Math.floor(progress * total); return { completed, total, percentage: progress * 100, remaining: total - completed, }; } getAllStats() { const stats = new Map(); for (const [id, bar] of this.bars) { stats.set(id, this.getTaskStats(id)); } return stats; } } // Utility functions for common progress scenarios export const ProgressUtils = { async withProgress(id, total, title, operation) { const progress = ProgressManager.create(id, total, { title }); try { const result = await operation(progress); progress.stop(); return result; } catch (error) { progress.fail(error instanceof Error ? error.message : String(error)); throw error; } }, async processArrayWithProgress(items, processor, options = {}) { const { title = "Processing items", batchSize = 1, showDetail = true } = options; const progress = ProgressManager.create(`process-${Date.now()}`, items.length, { title }); const results = []; try { for (let i = 0; i < items.length; i += batchSize) { const batch = items.slice(i, i + batchSize); const batchResults = await Promise.all(batch.map(async (item, batchIndex) => { const result = await processor(item, i + batchIndex); const detail = showDetail ? `Item ${i + batchIndex + 1}: ${String(item).slice(0, 30)}...` : undefined; progress.update(i + batchIndex + 1, detail); return result; })); results.push(...batchResults); } progress.stop(); return results; } catch (error) { progress.fail(error instanceof Error ? error.message : String(error)); throw error; } }, };