# 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/total` count 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 output - `complete()` forces 100% and appends a newline; idempotent (safe to call multiple times) - `AbortSignal` support: abort event listener marks the instance done; pre-fired signal respected via guard check in `update()` ## Usage: Basic Example ```ts 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 ```ts 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.