cli-spinner
A lightweight, zero-dependency terminal spinner for Node.js command-line tools.
Repository Health
Technical Analysis
cli-spinner is a minimal Node.js library for adding an animated loading indicator to command-line applications. It ships with over 30 built-in spinner styles — from simple ASCII characters to Braille-based Unicode animations and emoji cycles — selectable by numeric index, name, or a fully custom character sequence.
The API is intentionally small: create a Spinner instance, optionally configure its title, character set, and tick interval, then call start() and stop(). It automatically no-ops when output isn’t a TTY, making it safe to drop into scripts that may run in both interactive terminals and piped or CI contexts.
What You Get
- A
Spinnerclass withstart()/stop()/isSpinning()lifecycle methods - Over 30 built-in spinner character sets in
spinners.json, selectable by index or string - Custom spinner strings and tick callbacks via the
onTickoption - Automatic TTY detection so spinners no-op safely in non-interactive or piped output
Common Use Cases
- Showing progress while a CLI tool waits on a network request or file operation
- Indicating an in-progress build, install, or deploy step in a terminal-based tool
- Adding lightweight visual feedback to scripts without pulling in a heavier terminal-UI framework
Under The Hood
Architecture
cli-spinner is a single-file library (index.js, ~130 lines) built around one constructor function, Spinner, exposed with prototype methods for start, stop, isSpinning, and configuration setters (setSpinnerString, setSpinnerDelay, setSpinnerTitle). There’s no internal layering: the constructor reads options directly, static methods (Spinner.setDefaultSpinnerString, Spinner.setDefaultSpinnerDelay) mutate module-level closures used as defaults for future instances, and Spinner.spinners is populated once from the bundled spinners.json data file. Terminal output flows directly through Node’s built-in readline module for clearing and repositioning the cursor, with no adapter between the spinner logic and the stream it writes to — a caller who wanted a different rendering target would need to reach into the stream and onTick options rather than a pluggable interface. Because the whole surface is one flat object, any change to the constructor’s option handling or the tick loop in start() ripples directly to every consumer.
Tech Stack
The library has zero runtime dependencies and targets Node.js >=0.10 per its package.json engines field, relying solely on the built-in readline module for cursor control. It’s distributed as plain CommonJS (require/exports) with no build step, no TypeScript, no bundler, and no transpilation — the published package is the source as-is. Spinner character sets are stored as static JSON (spinners.json) rather than generated at runtime, keeping the package fully self-contained.
Code Quality
No test files or CI configuration exist anywhere in the repository — there’s no test/ directory, no test script in package.json, and no workflow files. Error handling is minimal; notably, stop() checks if(this.isSpinning === false), which compares the method reference itself (always truthy) rather than invoking this.isSpinning(), so the intended early-return branch never actually triggers. Naming is plain, readable camelCase throughout, but there are no type annotations, no JSDoc types, and no linter or formatter configuration.
API Design
The public API is deliberately small: construct a Spinner with a string or options object, then call start()/stop(). A printf-style %s placeholder inside the text lets callers position the animated character anywhere in a custom message, and the static setDefaultSpinnerString/setDefaultSpinnerDelay methods reduce repeated configuration across instances. The bundled catalog of 30+ spinner styles, selectable by numeric index or literal string, means most consumers never need to hand-write animation frames, and the README documents every method inline. The tradeoff is a dated, callback-only API with no TypeScript definitions and no async/ETA-aware progress reporting.