@socketsecurity/lib
Version:
Core utilities and infrastructure for Socket.dev security tools
465 lines (464 loc) • 18.3 kB
TypeScript
/**
* @fileoverview CLI spinner utilities for long-running operations.
* Provides animated progress indicators with CI environment detection.
*/
import type { Writable } from 'stream';
import type { ShimmerColorGradient, ShimmerConfig, ShimmerDirection, ShimmerState } from './effects/text-shimmer';
/**
* Named color values supported by the spinner.
* Maps to standard terminal colors with bright variants.
*/
export type ColorName = 'black' | 'blue' | 'blueBright' | 'cyan' | 'cyanBright' | 'gray' | 'green' | 'greenBright' | 'magenta' | 'magentaBright' | 'red' | 'redBright' | 'white' | 'whiteBright' | 'yellow' | 'yellowBright';
/**
* Special 'inherit' color value that uses the spinner's current color.
* Used with shimmer effects to dynamically inherit the spinner color.
*/
export type ColorInherit = 'inherit';
/**
* RGB color tuple with values 0-255 for red, green, and blue channels.
* @example [140, 82, 255] // Socket purple
* @example [255, 0, 0] // Red
*/
export type ColorRgb = readonly [number, number, number];
/**
* Union of all supported color types: named colors or RGB tuples.
*/
export type ColorValue = ColorName | ColorRgb;
/**
* Symbol types for status messages.
* Maps to log symbols: success (✓), fail (✗), info (ℹ), warn (⚠).
*/
export type SymbolType = 'fail' | 'info' | 'success' | 'warn';
/**
* Convert a color value to RGB tuple format.
* Named colors are looked up in the `colorToRgb` map, RGB tuples are returned as-is.
* @param color - Color name or RGB tuple
* @returns RGB tuple with values 0-255
*/
export declare function toRgb(color: ColorValue): ColorRgb;
/**
* Progress tracking information for display in spinner.
* Used by `progress()` and `progressStep()` methods to show animated progress bars.
*/
export type ProgressInfo = {
/** Current progress value */
current: number;
/** Total/maximum progress value */
total: number;
/** Optional unit label displayed after the progress count (e.g., 'files', 'items') */
unit?: string | undefined;
};
/**
* Internal shimmer state with color configuration.
* Extends `ShimmerState` with additional color property that can be inherited from spinner.
*/
export type ShimmerInfo = ShimmerState & {
/** Color for shimmer effect - can inherit from spinner, use explicit color, or gradient */
color: ColorInherit | ColorValue | ShimmerColorGradient;
};
/**
* Spinner instance for displaying animated loading indicators.
* Provides methods for status updates, progress tracking, and text shimmer effects.
*
* KEY BEHAVIORS:
* - Methods WITHOUT "AndStop" keep the spinner running (e.g., `success()`, `fail()`)
* - Methods WITH "AndStop" auto-clear the spinner line (e.g., `successAndStop()`, `failAndStop()`)
* - Status messages (done, success, fail, info, warn, step, substep) go to stderr
* - Data messages (`log()`) go to stdout
*
* @example
* ```ts
* import { Spinner } from '@socketsecurity/lib/spinner'
*
* const spinner = Spinner({ text: 'Loading…' })
* spinner.start()
*
* // Show success while continuing to spin
* spinner.success('Step 1 complete')
*
* // Stop the spinner with success message
* spinner.successAndStop('All done!')
* ```
*/
export type Spinner = {
/** Current spinner color as RGB tuple */
color: ColorRgb;
/** Current spinner animation style */
spinner: SpinnerStyle;
/** Whether spinner is currently animating */
get isSpinning(): boolean;
/** Get current shimmer state (enabled/disabled and configuration) */
get shimmerState(): ShimmerInfo | undefined;
/** Clear the current line without stopping the spinner */
clear(): Spinner;
/** Show debug message without stopping (only if debug mode enabled) */
debug(text?: string | undefined, ...extras: unknown[]): Spinner;
/** Show debug message and stop the spinner (only if debug mode enabled) */
debugAndStop(text?: string | undefined, ...extras: unknown[]): Spinner;
/** Alias for `fail()` - show error without stopping */
error(text?: string | undefined, ...extras: unknown[]): Spinner;
/** Alias for `failAndStop()` - show error and stop */
errorAndStop(text?: string | undefined, ...extras: unknown[]): Spinner;
/** Show failure (✗) without stopping the spinner */
fail(text?: string | undefined, ...extras: unknown[]): Spinner;
/** Show failure (✗) and stop the spinner, auto-clearing the line */
failAndStop(text?: string | undefined, ...extras: unknown[]): Spinner;
/** Get current spinner text (getter) or set new text (setter) */
text(value: string): Spinner;
text(): string;
/** Increase indentation by specified spaces (default: 2) */
indent(spaces?: number | undefined): Spinner;
/** Decrease indentation by specified spaces (default: 2) */
dedent(spaces?: number | undefined): Spinner;
/** Show info (ℹ) message without stopping the spinner */
info(text?: string | undefined, ...extras: unknown[]): Spinner;
/** Show info (ℹ) message and stop the spinner, auto-clearing the line */
infoAndStop(text?: string | undefined, ...extras: unknown[]): Spinner;
/** Log to stdout without stopping the spinner */
log(text?: string | undefined, ...extras: unknown[]): Spinner;
/** Log and stop the spinner, auto-clearing the line */
logAndStop(text?: string | undefined, ...extras: unknown[]): Spinner;
/** Start spinning with optional text */
start(text?: string | undefined): Spinner;
/** Stop spinning and clear internal state, auto-clearing the line */
stop(text?: string | undefined): Spinner;
/** Stop and show final text without clearing the line */
stopAndPersist(text?: string | undefined): Spinner;
/** Show main step message to stderr without stopping */
step(text?: string | undefined, ...extras: unknown[]): Spinner;
/** Show indented substep message to stderr without stopping */
substep(text?: string | undefined, ...extras: unknown[]): Spinner;
/** Show success (✓) without stopping the spinner */
success(text?: string | undefined, ...extras: unknown[]): Spinner;
/** Show success (✓) and stop the spinner, auto-clearing the line */
successAndStop(text?: string | undefined, ...extras: unknown[]): Spinner;
/** Alias for `success()` - show success without stopping */
done(text?: string | undefined, ...extras: unknown[]): Spinner;
/** Alias for `successAndStop()` - show success and stop */
doneAndStop(text?: string | undefined, ...extras: unknown[]): Spinner;
/** Update progress bar with current/total values and optional unit */
progress(current: number, total: number, unit?: string | undefined): Spinner;
/** Increment progress by specified amount (default: 1) */
progressStep(amount?: number): Spinner;
/** Enable shimmer effect (restores saved config or uses defaults) */
enableShimmer(): Spinner;
/** Disable shimmer effect (preserves config for later re-enable) */
disableShimmer(): Spinner;
/** Set complete shimmer configuration */
setShimmer(config: ShimmerConfig): Spinner;
/** Update partial shimmer configuration */
updateShimmer(config: Partial<ShimmerConfig>): Spinner;
/** Show warning (⚠) without stopping the spinner */
warn(text?: string | undefined, ...extras: unknown[]): Spinner;
/** Show warning (⚠) and stop the spinner, auto-clearing the line */
warnAndStop(text?: string | undefined, ...extras: unknown[]): Spinner;
};
/**
* Configuration options for creating a spinner instance.
*/
export type SpinnerOptions = {
/**
* Spinner color as RGB tuple or color name.
* @default [140, 82, 255] Socket purple
*/
readonly color?: ColorValue | undefined;
/**
* Shimmer effect configuration or direction string.
* When enabled, text will have an animated shimmer effect.
* @default undefined No shimmer effect
*/
readonly shimmer?: ShimmerConfig | ShimmerDirection | undefined;
/**
* Animation style with frames and timing.
* @default 'socket' Custom Socket animation in CLI, minimal in CI
*/
readonly spinner?: SpinnerStyle | undefined;
/**
* Abort signal for cancelling the spinner.
* @default getAbortSignal() from process constants
*/
readonly signal?: AbortSignal | undefined;
/**
* Output stream for spinner rendering.
* @default process.stderr
*/
readonly stream?: Writable | undefined;
/**
* Initial text to display with the spinner.
* @default undefined No initial text
*/
readonly text?: string | undefined;
/**
* Theme to use for spinner colors.
* Accepts theme name ('socket', 'sunset', etc.) or Theme object.
* @default Current theme from getTheme()
*/
readonly theme?: import('./themes/types').Theme | import('./themes/themes').ThemeName | undefined;
};
/**
* Animation style definition for spinner frames.
* Defines the visual appearance and timing of the spinner animation.
*/
export type SpinnerStyle = {
/** Array of animation frames (strings to display sequentially) */
readonly frames: string[];
/**
* Milliseconds between frame changes.
* @default 80 Standard frame rate
*/
readonly interval?: number | undefined;
};
/**
* Minimal spinner style for CI environments.
* Uses empty frame and max interval to effectively disable animation in CI.
*/
export declare const ciSpinner: SpinnerStyle;
/**
* Get available CLI spinner styles or a specific style by name.
* Extends the standard cli-spinners collection with Socket custom spinners.
*
* Custom spinners:
* - `socket` (default): Socket pulse animation with sparkles and lightning
*
* @param styleName - Optional name of specific spinner style to retrieve
* @returns Specific spinner style if name provided, all styles if omitted, `undefined` if style not found
* @see https://github.com/sindresorhus/cli-spinners/blob/main/spinners.json
*
* @example
* ```ts
* // Get all available spinner styles
* const allSpinners = getCliSpinners()
*
* // Get specific style
* const socketStyle = getCliSpinners('socket')
* const dotsStyle = getCliSpinners('dots')
* ```
*/
/*@__NO_SIDE_EFFECTS__*/
export declare function getCliSpinners(styleName?: string | undefined): SpinnerStyle | Record<string, SpinnerStyle> | undefined;
/**
* Create a spinner instance for displaying loading indicators.
* Provides an animated CLI spinner with status messages, progress tracking, and shimmer effects.
*
* AUTO-CLEAR BEHAVIOR:
* - All *AndStop() methods AUTO-CLEAR the spinner line via yocto-spinner.stop()
* Examples: `doneAndStop()`, `successAndStop()`, `failAndStop()`, etc.
*
* - Methods WITHOUT "AndStop" do NOT clear (spinner keeps spinning)
* Examples: `done()`, `success()`, `fail()`, etc.
*
* STREAM USAGE:
* - Spinner animation: stderr (yocto-spinner default)
* - Status methods (done, success, fail, info, warn, step, substep): stderr
* - Data methods (`log()`): stdout
*
* COMPARISON WITH LOGGER:
* - `logger.done()` does NOT auto-clear (requires manual `logger.clearLine()`)
* - `spinner.doneAndStop()` DOES auto-clear (built into yocto-spinner.stop())
* - Pattern: `logger.clearLine().done()` vs `spinner.doneAndStop()`
*
* @param options - Configuration options for the spinner
* @returns New spinner instance
*
* @example
* ```ts
* import { Spinner } from '@socketsecurity/lib/spinner'
*
* // Basic usage
* const spinner = Spinner({ text: 'Loading data…' })
* spinner.start()
* await fetchData()
* spinner.successAndStop('Data loaded!')
*
* // With custom color
* const spinner = Spinner({
* text: 'Processing…',
* color: [255, 0, 0] // Red
* })
*
* // With shimmer effect
* const spinner = Spinner({
* text: 'Building…',
* shimmer: { dir: 'ltr', speed: 0.5 }
* })
*
* // Show progress
* spinner.progress(5, 10, 'files')
* spinner.progressStep() // Increment by 1
* ```
*/
/*@__NO_SIDE_EFFECTS__*/
export declare function Spinner(options?: SpinnerOptions | undefined): Spinner;
/**
* Get the default spinner instance.
* Lazily creates the spinner to avoid circular dependencies during module initialization.
* Reuses the same instance across calls.
*
* @returns Shared default spinner instance
*
* @example
* ```ts
* import { getDefaultSpinner } from '@socketsecurity/lib/spinner'
*
* const spinner = getDefaultSpinner()
* spinner.start('Loading…')
* ```
*/
export declare function getDefaultSpinner(): ReturnType<typeof Spinner>;
// REMOVED: Deprecated `spinner` export
// Migration: Use getDefaultSpinner() instead
// See: getDefaultSpinner() function above
/**
* Configuration options for `withSpinner()` helper.
* @template T - Return type of the async operation
*/
export type WithSpinnerOptions<T> = {
/** Message to display while the spinner is running */
message: string;
/** Async function to execute while spinner is active */
operation: () => Promise<T>;
/**
* Optional spinner instance to use.
* If not provided, operation runs without spinner.
*/
spinner?: Spinner | undefined;
/**
* Optional spinner options to apply during the operation.
* These options will be pushed when the operation starts and popped when it completes.
* Supports color and shimmer configuration.
*/
withOptions?: Partial<Pick<SpinnerOptions, 'color' | 'shimmer'>> | undefined;
};
/**
* Execute an async operation with spinner lifecycle management.
* Ensures `spinner.stop()` is always called via try/finally, even if the operation throws.
* Provides safe cleanup and consistent spinner behavior.
*
* @template T - Return type of the operation
* @param options - Configuration object
* @param options.message - Message to display while spinner is running
* @param options.operation - Async function to execute
* @param options.spinner - Optional spinner instance (if not provided, no spinner is used)
* @returns Result of the operation
* @throws Re-throws any error from operation after stopping spinner
*
* @example
* ```ts
* import { Spinner, withSpinner } from '@socketsecurity/lib/spinner'
*
* const spinner = Spinner()
*
* // With spinner instance
* const result = await withSpinner({
* message: 'Processing…',
* operation: async () => {
* return await processData()
* },
* spinner
* })
*
* // Without spinner instance (no-op, just runs operation)
* const result = await withSpinner({
* message: 'Processing…',
* operation: async () => {
* return await processData()
* }
* })
* ```
*/
export declare function withSpinner<T>(options: WithSpinnerOptions<T>): Promise<T>;
/**
* Configuration options for `withSpinnerRestore()` helper.
* @template T - Return type of the async operation
*/
export type WithSpinnerRestoreOptions<T> = {
/** Async function to execute while spinner is stopped */
operation: () => Promise<T>;
/** Optional spinner instance to restore after operation */
spinner?: Spinner | undefined;
/** Whether spinner was spinning before the operation (used to conditionally restart) */
wasSpinning: boolean;
};
/**
* Execute an async operation with conditional spinner restart.
* Useful when you need to temporarily stop a spinner for an operation,
* then restore it to its previous state (if it was spinning).
*
* @template T - Return type of the operation
* @param options - Configuration object
* @param options.operation - Async function to execute
* @param options.spinner - Optional spinner instance to manage
* @param options.wasSpinning - Whether spinner was spinning before the operation
* @returns Result of the operation
* @throws Re-throws any error from operation after restoring spinner state
*
* @example
* ```ts
* import { getDefaultSpinner, withSpinnerRestore } from '@socketsecurity/lib/spinner'
*
* const spinner = getDefaultSpinner()
* const wasSpinning = spinner.isSpinning
* spinner.stop()
*
* const result = await withSpinnerRestore({
* operation: async () => {
* // Do work without spinner
* return await someOperation()
* },
* spinner,
* wasSpinning
* })
* // Spinner is automatically restarted if wasSpinning was true
* ```
*/
export declare function withSpinnerRestore<T>(options: WithSpinnerRestoreOptions<T>): Promise<T>;
/**
* Configuration options for `withSpinnerSync()` helper.
* @template T - Return type of the sync operation
*/
export type WithSpinnerSyncOptions<T> = {
/** Message to display while the spinner is running */
message: string;
/** Synchronous function to execute while spinner is active */
operation: () => T;
/**
* Optional spinner instance to use.
* If not provided, operation runs without spinner.
*/
spinner?: Spinner | undefined;
/**
* Optional spinner options to apply during the operation.
* These options will be pushed when the operation starts and popped when it completes.
* Supports color and shimmer configuration.
*/
withOptions?: Partial<Pick<SpinnerOptions, 'color' | 'shimmer'>> | undefined;
};
/**
* Execute a synchronous operation with spinner lifecycle management.
* Ensures `spinner.stop()` is always called via try/finally, even if the operation throws.
* Provides safe cleanup and consistent spinner behavior for sync operations.
*
* @template T - Return type of the operation
* @param options - Configuration object
* @param options.message - Message to display while spinner is running
* @param options.operation - Synchronous function to execute
* @param options.spinner - Optional spinner instance (if not provided, no spinner is used)
* @returns Result of the operation
* @throws Re-throws any error from operation after stopping spinner
*
* @example
* ```ts
* import { Spinner, withSpinnerSync } from '@socketsecurity/lib/spinner'
*
* const spinner = Spinner()
*
* const result = withSpinnerSync({
* message: 'Processing…',
* operation: () => {
* return processDataSync()
* },
* spinner
* })
* ```
*/
export declare function withSpinnerSync<T>(options: WithSpinnerSyncOptions<T>): T;