UNPKG

@socketsecurity/lib

Version:

Core utilities and infrastructure for Socket.dev security tools

465 lines (464 loc) 18.3 kB
/** * @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;