0
# Progress Bars
1
2
Visual command-line progress bars with automatic time calculations, customizable display, and ETA predictions for long-running operations.
3
4
## Capabilities
5
6
### ProgressBar Class
7
8
Creates and manages visual progress bars with automatic formatting and time estimation.
9
10
```typescript { .api }
11
/**
12
* Visual progress bar for command-line applications
13
* @param size - Total progress value (default: 100)
14
* @param options - Display and behavior configuration
15
*/
16
class ProgressBar {
17
constructor(
18
size?: number,
19
options?: {
20
/** Increment size for each tick() call (default: 1) */
21
tickSize?: number;
22
/** Suppress all output (default: false) */
23
silent?: boolean;
24
/** Terminal width for bar formatting (default: process.stdout.columns || 70) */
25
terminalWidth?: number;
26
}
27
);
28
29
/** Set current progress to specific value */
30
setValue(value: number): string;
31
32
/** Increment progress by tickSize amount */
33
tick(): string;
34
35
/** Register callback to execute when progress reaches 100% */
36
onFinish(callback: Function): void;
37
38
/** Get elapsed time in milliseconds since creation */
39
getEllapsed(): number;
40
41
/** Get estimated remaining time in milliseconds */
42
getRemaining(): number;
43
44
/** Render current progress bar and return display string */
45
print(): string;
46
47
/** Write text to stdout (respects silent mode) */
48
write(text: string): void;
49
}
50
```
51
52
**Usage Examples:**
53
54
```typescript
55
import { ProgressBar } from "stdio";
56
57
// Basic progress bar
58
const progress = new ProgressBar(100);
59
for (let i = 0; i <= 100; i++) {
60
progress.setValue(i);
61
await delay(50); // Simulate work
62
}
63
64
// Using tick() for incremental progress
65
const progress = new ProgressBar(1000, { tickSize: 10 });
66
while (progress.value < progress.size) {
67
await processItem();
68
progress.tick(); // Increment by 10
69
}
70
71
// Custom configuration
72
const progress = new ProgressBar(50, {
73
tickSize: 2,
74
silent: false,
75
terminalWidth: 80
76
});
77
78
// With completion callback
79
const progress = new ProgressBar(100);
80
progress.onFinish(() => {
81
console.log('\nProcessing complete!');
82
});
83
84
for (let i = 0; i <= 100; i++) {
85
progress.setValue(i);
86
await simulateWork();
87
}
88
// Callback executes automatically when setValue(100) is called
89
90
// File processing example
91
import { createReadStream } from 'fs';
92
import { stat } from 'fs/promises';
93
94
async function processLargeFile(filename: string) {
95
const stats = await stat(filename);
96
const progress = new ProgressBar(stats.size);
97
98
const stream = createReadStream(filename);
99
let bytesProcessed = 0;
100
101
stream.on('data', (chunk) => {
102
bytesProcessed += chunk.length;
103
progress.setValue(bytesProcessed);
104
});
105
106
stream.on('end', () => {
107
console.log('\nFile processing complete!');
108
});
109
}
110
```
111
112
### Progress Display Format
113
114
Progress bars automatically format with time information and ETA:
115
116
```
117
00:01:23 45% [##############################·····················] ETA 00:01:52
118
```
119
120
**Display Components:**
121
- **Elapsed Time**: `00:01:23` - Time since progress started
122
- **Percentage**: `45%` - Current completion percentage
123
- **Visual Bar**: `[###···]` - Visual representation with `#` for completed, `·` for remaining
124
- **ETA**: `00:01:52` - Estimated time remaining
125
126
### Constructor Options
127
128
Comprehensive configuration for progress bar behavior and appearance.
129
130
```typescript { .api }
131
interface ProgressBarOptions {
132
/**
133
* Increment size for tick() calls (default: 1)
134
* Determines how much progress increases with each tick()
135
*/
136
tickSize?: number;
137
138
/**
139
* Suppress all output (default: false)
140
* When true, progress bar is tracked but not displayed
141
*/
142
silent?: boolean;
143
144
/**
145
* Terminal width for formatting (default: process.stdout.columns || 70)
146
* Controls total width of progress bar display
147
*/
148
terminalWidth?: number;
149
}
150
```
151
152
### Instance Properties
153
154
Access to progress bar state and configuration:
155
156
```typescript { .api }
157
class ProgressBar {
158
/** Total progress value (set in constructor) */
159
size: number;
160
161
/** Increment size for tick() calls */
162
tickSize: number;
163
164
/** Current progress value */
165
value: number;
166
167
/** Progress start timestamp */
168
startTime: number;
169
170
/** Recent time estimates for smoothing calculations */
171
lastRemainingTimes: number[];
172
173
/** Output suppression flag */
174
silent: boolean;
175
176
/** Terminal width for formatting */
177
terminalWidth: number;
178
179
/** Completion callback function */
180
callback: Function;
181
}
182
```
183
184
## Advanced Features
185
186
### Time Calculation and ETA
187
188
Sophisticated time estimation with smoothing algorithms:
189
190
```typescript
191
// ETA calculation uses running average of recent processing times
192
const progress = new ProgressBar(1000);
193
194
for (let i = 0; i < 1000; i++) {
195
// Variable processing times are automatically handled
196
const workTime = Math.random() * 100 + 50;
197
await delay(workTime);
198
199
progress.tick();
200
201
// ETA becomes more accurate as more data points are collected
202
console.log(`ETA: ${progress.getRemaining()}ms`);
203
}
204
```
205
206
**Time Features:**
207
- **Elapsed Time**: Accurate tracking from progress start
208
- **ETA Smoothing**: Uses rolling average of last 5 time measurements
209
- **Completion Detection**: ETA shows 0 when progress reaches 100%
210
- **Time Formatting**: Automatic formatting with days, hours, minutes, seconds
211
212
### Visual Customization
213
214
Automatic visual formatting based on terminal width:
215
216
```typescript
217
// Progress bar adapts to available terminal space
218
const progress = new ProgressBar(100, { terminalWidth: 120 });
219
220
// Bar width calculation:
221
// Total width = terminalWidth
222
// Bar width = terminalWidth - elapsed_text - percentage - eta_text
223
// Example: 120 - 8 - 4 - 12 = 96 characters for the actual bar
224
225
// On narrow terminals
226
const narrowProgress = new ProgressBar(100, { terminalWidth: 40 });
227
// Automatically adjusts bar width to fit
228
```
229
230
### Silent Mode and Testing
231
232
Support for background processing and testing scenarios:
233
234
```typescript
235
// Silent mode for background processing
236
const silentProgress = new ProgressBar(100, { silent: true });
237
238
// Progress is tracked but not displayed
239
for (let i = 0; i <= 100; i++) {
240
silentProgress.setValue(i);
241
// No output to terminal
242
}
243
244
// Check progress programmatically
245
console.log(`Final progress: ${silentProgress.value}/${silentProgress.size}`);
246
247
// Testing progress bars
248
function testProgressBar() {
249
const progress = new ProgressBar(10, { silent: true });
250
251
// Verify behavior without terminal output
252
assert.equal(progress.value, 0);
253
254
progress.tick();
255
assert.equal(progress.value, 1);
256
257
progress.setValue(5);
258
assert.equal(progress.value, 5);
259
260
const displayString = progress.print();
261
assert(displayString.includes('50%'));
262
}
263
```
264
265
### Error Handling and Edge Cases
266
267
Robust handling of various scenarios:
268
269
```typescript
270
const progress = new ProgressBar(100);
271
272
// Values beyond size are clamped
273
progress.setValue(150);
274
console.log(progress.value); // 100 (clamped to size)
275
276
// Negative values are handled
277
progress.setValue(-10);
278
console.log(progress.value); // 0 (clamped to minimum)
279
280
// Division by zero protection
281
const zeroProgress = new ProgressBar(0);
282
zeroProgress.setValue(0);
283
// Safely handles edge case without crashing
284
285
// Completion callback safety
286
progress.onFinish(() => {
287
throw new Error('Callback error');
288
});
289
290
try {
291
progress.setValue(100); // Completion triggers callback
292
} catch (error) {
293
console.error('Callback failed:', error.message);
294
// Progress bar itself remains stable
295
}
296
```
297
298
### Integration Patterns
299
300
Common patterns for different use cases:
301
302
**File Upload/Download:**
303
304
```typescript
305
async function uploadFile(file: File, url: string) {
306
const progress = new ProgressBar(file.size);
307
308
const formData = new FormData();
309
formData.append('file', file);
310
311
const response = await fetch(url, {
312
method: 'POST',
313
body: formData
314
});
315
316
// Track upload progress (if supported by fetch implementation)
317
const reader = response.body.getReader();
318
let uploaded = 0;
319
320
while (true) {
321
const { done, value } = await reader.read();
322
if (done) break;
323
324
uploaded += value.length;
325
progress.setValue(uploaded);
326
}
327
328
return response;
329
}
330
```
331
332
**Batch Processing:**
333
334
```typescript
335
async function processBatch<T>(
336
items: T[],
337
processor: (item: T) => Promise<void>
338
): Promise<void> {
339
const progress = new ProgressBar(items.length);
340
341
progress.onFinish(() => {
342
console.log(`\nProcessed ${items.length} items successfully!`);
343
});
344
345
for (let i = 0; i < items.length; i++) {
346
try {
347
await processor(items[i]);
348
progress.tick();
349
} catch (error) {
350
console.error(`\nError processing item ${i}:`, error.message);
351
// Continue with remaining items
352
progress.tick();
353
}
354
}
355
}
356
```
357
358
**Nested Progress:**
359
360
```typescript
361
async function complexOperation() {
362
const mainProgress = new ProgressBar(3);
363
364
console.log('Phase 1: Data Collection');
365
await collectData();
366
mainProgress.tick();
367
368
console.log('Phase 2: Data Processing');
369
await processDataWithProgress(); // This has its own progress bar
370
mainProgress.tick();
371
372
console.log('Phase 3: Results Generation');
373
await generateResults();
374
mainProgress.tick();
375
376
console.log('Operation complete!');
377
}
378
379
async function processDataWithProgress() {
380
const subProgress = new ProgressBar(1000, {
381
terminalWidth: 60 // Smaller bar for sub-operation
382
});
383
384
for (let i = 0; i < 1000; i++) {
385
await processItem(i);
386
subProgress.tick();
387
}
388
}
389
```
390
391
## Constants
392
393
```typescript { .api }
394
/** Default terminal width when process.stdout.columns is unavailable */
395
const DEFAULT_TERMINAL_WIDTH = 70;
396
```