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
JavaScript
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;
}
},
};