3.9 KiB
progress
(no submodules)
Purpose
createProgress renders a fixed-width ASCII progress bar for tracking long-running operations with a known total. It writes bar updates to any WritableStreamLike output — TTY or non-TTY — using in-place line clearing on TTY terminals and newline-separated lines on non-TTY streams (CI, pipes). The bar updates are driven by explicit update(current) calls from the caller; no internal timer is used.
The instance is AbortSignal-aware: aborting the signal silently makes all subsequent update() calls no-ops, allowing calling code to cancel a long operation without crashing.
Features
- Fixed-width block bar with configurable
width(default 40 characters) using█fill and░empty characters - Percentage and
current/totalcount displayed after the bar:[████████████░░░░░░░░] 60% (60/100) - TTY mode: clears the current line and re-renders in-place via
clearLine/cursorTo - Non-TTY mode: writes each update on its own line for readable CI output
render()returns the bar string without writing to the stream — useful for embedding in larger outputcomplete()forces 100% and appends a newline; idempotent (safe to call multiple times)AbortSignalsupport: abort event listener marks the instance done; pre-fired signal respected via guard check inupdate()
Usage: Basic Example
import { createProgress } from "@suaveplan/cli/progress/progress";
const p = createProgress({ total: 100 });
for (let i = 0; i <= 100; i++) {
p.update(i);
await sleep(20);
}
p.complete();
Usage: Advanced Example
import { createProgress } from "@suaveplan/cli/progress/progress";
import { Writable } from "node:stream";
const controller = new AbortController();
const p = createProgress({
total: files.length,
width: 60,
signal: controller.signal,
});
let processed = 0;
for (const file of files) {
if (controller.signal.aborted) break;
await processFile(file);
p.update(++processed);
}
p.complete();
API Reference
createProgress(opts: ProgressOptions): ProgressInstance
Creates a progress bar instance. No output is written until update() or complete() is called.
opts.total: number — the maximum value (100%). Must be positive.
opts.width?: number — width of the bar in characters (default 40).
opts.stream?: WritableStreamLike — output target (default process.stdout). TTY in-place rendering requires clearLine and cursorTo methods plus isTTY === true.
opts.signal?: AbortSignal — when aborted, future update() calls become no-ops and the bar stops advancing.
ProgressInstance
update(current: number): void — updates the displayed progress to current. Values are clamped to [0, total]. No-op if complete() was already called or the signal was aborted.
complete(): void — writes a 100% bar with a trailing newline and marks the instance done. Idempotent.
render(): string — returns the current bar string without writing to the stream.
ProgressOptionsSchema
Zod schema for ProgressOptions — validates total (positive number) and width (optional positive number). Excludes stream and signal.
WritableStreamLike
Re-exported from ../spinner/spinner.types.js. Any object with write(text: string): void satisfies this interface; clearLine, cursorTo, and isTTY are optional and enable TTY mode when present.
Implementation Notes
Progress percentage is computed as Math.min(1, current / total) to handle over-total updates gracefully. The filled block count is Math.round(pct * width) — rounding avoids a full-bar display at 99% on small widths. The complete() guard (done flag) prevents double-writes when called multiple times. The AbortSignal listener uses { once: true } so it self-removes after the first event.