UNPKG

@controlplane/cli

Version:

Control Plane Corporation CLI

125 lines 3.77 kB
"use strict"; // ANCHOR - Constants Object.defineProperty(exports, "__esModule", { value: true }); exports.TerminalSpinner = void 0; const FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']; const FRAME_INTERVAL_MS = 80; // Erase the current line and return to its start, so the next write redraws in place. const CLEAR_LINE = '\x1b[2K\r'; const HIDE_CURSOR = '\x1b[?25l'; const SHOW_CURSOR = '\x1b[?25h'; // ANCHOR - TerminalSpinner /** * An in-place terminal spinner: one animated line showing that work is happening while * nothing streams. It animates on a scheduler, persists other lines above itself without * disturbing the animation, and clears its line and restores the cursor when stopped. On * a non-terminal stream it degrades to printing each label once, with no animation. */ class TerminalSpinner { constructor(stream, opts = {}) { var _a, _b; this.running = false; this.frame = 0; this.label = ''; this.timer = null; this.stream = stream; this.scheduler = (_a = opts.scheduler) !== null && _a !== void 0 ? _a : systemScheduler(); this.paint = (_b = opts.paint) !== null && _b !== void 0 ? _b : plainText; } // Public Methods // /** @inheritdoc */ start(label) { this.label = label; // Without a terminal there is no in-place line to animate; announce it once. if (!this.stream.isTTY) { this.stream.write(`${label}\n`); return; } if (this.running) { this.scheduler.clear(this.timer); } this.frame = 0; this.running = true; this.stream.write(HIDE_CURSOR); this.render(); this.timer = this.scheduler.set(() => this.tick(), FRAME_INTERVAL_MS); } /** @inheritdoc */ setLabel(label) { this.label = label; if (this.running) { this.render(); } } /** @inheritdoc */ persist(line) { if (!this.running) { this.stream.write(`${line}\n`); return; } // Erase the spinner, drop the line above it, then redraw the spinner beneath. this.stream.write(`${CLEAR_LINE}${line}\n`); this.render(); } /** @inheritdoc */ stop() { if (!this.running) { return; } this.running = false; this.scheduler.clear(this.timer); this.timer = null; this.stream.write(`${CLEAR_LINE}${SHOW_CURSOR}`); } /** @inheritdoc */ get active() { return this.running; } // Private Methods // /** * Advances to the next animation frame and redraws. * * @returns {void} */ tick() { this.frame = (this.frame + 1) % FRAMES.length; this.render(); } /** * Redraws the current frame and label in place. * * @returns {void} */ render() { this.stream.write(`${CLEAR_LINE}${this.paint(FRAMES[this.frame])} ${this.label}`); } } exports.TerminalSpinner = TerminalSpinner; // SECTION - Functions /** * The production scheduler, backed by unref'd timers so a running spinner never keeps * the process alive on its own. * * @returns {SpinnerScheduler} The scheduler. */ function systemScheduler() { return { set: (callback, millis) => { const handle = setInterval(callback, millis); handle.unref(); return handle; }, clear: (handle) => clearInterval(handle), }; } /** * The default frame styling: none. * * @param {string} text - The frame to style. * @returns {string} The text unchanged. */ function plainText(text) { return text; } // !SECTION //# sourceMappingURL=spinner.js.map