Standard input/output manager for Node.js with command-line parsing, async file reading, interactive terminal, and progress bars
—
Visual command-line progress bars with automatic time calculations, customizable display, and ETA predictions for long-running operations.
Creates and manages visual progress bars with automatic formatting and time estimation.
/**
* Visual progress bar for command-line applications
* @param size - Total progress value (default: 100)
* @param options - Display and behavior configuration
*/
class ProgressBar {
constructor(
size?: number,
options?: {
/** Increment size for each tick() call (default: 1) */
tickSize?: number;
/** Suppress all output (default: false) */
silent?: boolean;
/** Terminal width for bar formatting (default: process.stdout.columns || 70) */
terminalWidth?: number;
}
);
/** Set current progress to specific value */
setValue(value: number): string;
/** Increment progress by tickSize amount */
tick(): string;
/** Register callback to execute when progress reaches 100% */
onFinish(callback: Function): void;
/** Get elapsed time in milliseconds since creation */
getEllapsed(): number;
/** Get estimated remaining time in milliseconds */
getRemaining(): number;
/** Render current progress bar and return display string */
print(): string;
/** Write text to stdout (respects silent mode) */
write(text: string): void;
}Usage Examples:
import { ProgressBar } from "stdio";
// Basic progress bar
const progress = new ProgressBar(100);
for (let i = 0; i <= 100; i++) {
progress.setValue(i);
await delay(50); // Simulate work
}
// Using tick() for incremental progress
const progress = new ProgressBar(1000, { tickSize: 10 });
while (progress.value < progress.size) {
await processItem();
progress.tick(); // Increment by 10
}
// Custom configuration
const progress = new ProgressBar(50, {
tickSize: 2,
silent: false,
terminalWidth: 80
});
// With completion callback
const progress = new ProgressBar(100);
progress.onFinish(() => {
console.log('\nProcessing complete!');
});
for (let i = 0; i <= 100; i++) {
progress.setValue(i);
await simulateWork();
}
// Callback executes automatically when setValue(100) is called
// File processing example
import { createReadStream } from 'fs';
import { stat } from 'fs/promises';
async function processLargeFile(filename: string) {
const stats = await stat(filename);
const progress = new ProgressBar(stats.size);
const stream = createReadStream(filename);
let bytesProcessed = 0;
stream.on('data', (chunk) => {
bytesProcessed += chunk.length;
progress.setValue(bytesProcessed);
});
stream.on('end', () => {
console.log('\nFile processing complete!');
});
}Progress bars automatically format with time information and ETA:
00:01:23 45% [##############################·····················] ETA 00:01:52Display Components:
00:01:23 - Time since progress started45% - Current completion percentage[###···] - Visual representation with # for completed, · for remaining00:01:52 - Estimated time remainingComprehensive configuration for progress bar behavior and appearance.
interface ProgressBarOptions {
/**
* Increment size for tick() calls (default: 1)
* Determines how much progress increases with each tick()
*/
tickSize?: number;
/**
* Suppress all output (default: false)
* When true, progress bar is tracked but not displayed
*/
silent?: boolean;
/**
* Terminal width for formatting (default: process.stdout.columns || 70)
* Controls total width of progress bar display
*/
terminalWidth?: number;
}Access to progress bar state and configuration:
class ProgressBar {
/** Total progress value (set in constructor) */
size: number;
/** Increment size for tick() calls */
tickSize: number;
/** Current progress value */
value: number;
/** Progress start timestamp */
startTime: number;
/** Recent time estimates for smoothing calculations */
lastRemainingTimes: number[];
/** Output suppression flag */
silent: boolean;
/** Terminal width for formatting */
terminalWidth: number;
/** Completion callback function */
callback: Function;
}Sophisticated time estimation with smoothing algorithms:
// ETA calculation uses running average of recent processing times
const progress = new ProgressBar(1000);
for (let i = 0; i < 1000; i++) {
// Variable processing times are automatically handled
const workTime = Math.random() * 100 + 50;
await delay(workTime);
progress.tick();
// ETA becomes more accurate as more data points are collected
console.log(`ETA: ${progress.getRemaining()}ms`);
}Time Features:
Automatic visual formatting based on terminal width:
// Progress bar adapts to available terminal space
const progress = new ProgressBar(100, { terminalWidth: 120 });
// Bar width calculation:
// Total width = terminalWidth
// Bar width = terminalWidth - elapsed_text - percentage - eta_text
// Example: 120 - 8 - 4 - 12 = 96 characters for the actual bar
// On narrow terminals
const narrowProgress = new ProgressBar(100, { terminalWidth: 40 });
// Automatically adjusts bar width to fitSupport for background processing and testing scenarios:
// Silent mode for background processing
const silentProgress = new ProgressBar(100, { silent: true });
// Progress is tracked but not displayed
for (let i = 0; i <= 100; i++) {
silentProgress.setValue(i);
// No output to terminal
}
// Check progress programmatically
console.log(`Final progress: ${silentProgress.value}/${silentProgress.size}`);
// Testing progress bars
function testProgressBar() {
const progress = new ProgressBar(10, { silent: true });
// Verify behavior without terminal output
assert.equal(progress.value, 0);
progress.tick();
assert.equal(progress.value, 1);
progress.setValue(5);
assert.equal(progress.value, 5);
const displayString = progress.print();
assert(displayString.includes('50%'));
}Robust handling of various scenarios:
const progress = new ProgressBar(100);
// Values beyond size are clamped
progress.setValue(150);
console.log(progress.value); // 100 (clamped to size)
// Negative values are handled
progress.setValue(-10);
console.log(progress.value); // 0 (clamped to minimum)
// Division by zero protection
const zeroProgress = new ProgressBar(0);
zeroProgress.setValue(0);
// Safely handles edge case without crashing
// Completion callback safety
progress.onFinish(() => {
throw new Error('Callback error');
});
try {
progress.setValue(100); // Completion triggers callback
} catch (error) {
console.error('Callback failed:', error.message);
// Progress bar itself remains stable
}Common patterns for different use cases:
File Upload/Download:
async function uploadFile(file: File, url: string) {
const progress = new ProgressBar(file.size);
const formData = new FormData();
formData.append('file', file);
const response = await fetch(url, {
method: 'POST',
body: formData
});
// Track upload progress (if supported by fetch implementation)
const reader = response.body.getReader();
let uploaded = 0;
while (true) {
const { done, value } = await reader.read();
if (done) break;
uploaded += value.length;
progress.setValue(uploaded);
}
return response;
}Batch Processing:
async function processBatch<T>(
items: T[],
processor: (item: T) => Promise<void>
): Promise<void> {
const progress = new ProgressBar(items.length);
progress.onFinish(() => {
console.log(`\nProcessed ${items.length} items successfully!`);
});
for (let i = 0; i < items.length; i++) {
try {
await processor(items[i]);
progress.tick();
} catch (error) {
console.error(`\nError processing item ${i}:`, error.message);
// Continue with remaining items
progress.tick();
}
}
}Nested Progress:
async function complexOperation() {
const mainProgress = new ProgressBar(3);
console.log('Phase 1: Data Collection');
await collectData();
mainProgress.tick();
console.log('Phase 2: Data Processing');
await processDataWithProgress(); // This has its own progress bar
mainProgress.tick();
console.log('Phase 3: Results Generation');
await generateResults();
mainProgress.tick();
console.log('Operation complete!');
}
async function processDataWithProgress() {
const subProgress = new ProgressBar(1000, {
terminalWidth: 60 // Smaller bar for sub-operation
});
for (let i = 0; i < 1000; i++) {
await processItem(i);
subProgress.tick();
}
}/** Default terminal width when process.stdout.columns is unavailable */
const DEFAULT_TERMINAL_WIDTH = 70;Install with Tessl CLI
npx tessl i tessl/npm-stdio