flagstaff 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/ora.d.ts ADDED
@@ -0,0 +1,82 @@
1
+ export interface SpinnerDefinition {
2
+ interval?: number;
3
+ frames: string[];
4
+ }
5
+ /** Every spinner cli-spinners ships, unedited — ora re-exports it and so do we. */
6
+ export declare const spinners: Record<string, SpinnerDefinition>;
7
+ export type Color = 'black' | 'red' | 'green' | 'yellow' | 'blue' | 'magenta' | 'cyan' | 'white' | 'gray';
8
+ /** What ora writes to: `process.stderr` by default, anything stream-shaped in a test. */
9
+ export interface OraStream {
10
+ write(chunk: string, encoding?: unknown, callback?: unknown): boolean;
11
+ isTTY?: boolean;
12
+ columns?: number;
13
+ rows?: number;
14
+ cursorTo?(x: number): unknown;
15
+ moveCursor?(dx: number, dy: number): unknown;
16
+ clearLine?(direction: number): unknown;
17
+ once?(event: string, listener: () => void): unknown;
18
+ removeListener?(event: string, listener: () => void): unknown;
19
+ }
20
+ /** A prefix or suffix, resolved on every frame when it is a function. */
21
+ export type Affix = string | (() => string);
22
+ export interface Options {
23
+ text?: string;
24
+ prefixText?: Affix;
25
+ suffixText?: Affix;
26
+ spinner?: SpinnerDefinition | string;
27
+ color?: Color | false;
28
+ hideCursor?: boolean;
29
+ indent?: number;
30
+ interval?: number | undefined;
31
+ stream?: OraStream;
32
+ isEnabled?: boolean;
33
+ isSilent?: boolean;
34
+ discardStdin?: boolean;
35
+ }
36
+ export interface PersistOptions {
37
+ symbol?: string;
38
+ text?: string;
39
+ prefixText?: Affix;
40
+ suffixText?: Affix;
41
+ }
42
+ export interface PromiseOptions<T> extends Options {
43
+ successText?: string | ((result: T) => string);
44
+ failText?: string | ((error: unknown) => string);
45
+ successSymbol?: string;
46
+ failSymbol?: string;
47
+ }
48
+ export declare class Ora {
49
+ #private;
50
+ constructor(options?: Options | string);
51
+ get indent(): number;
52
+ set indent(indent: number | undefined);
53
+ get interval(): number;
54
+ get spinner(): SpinnerDefinition;
55
+ set spinner(spinner: SpinnerDefinition | string | undefined);
56
+ get text(): string;
57
+ set text(value: string | undefined);
58
+ get prefixText(): Affix;
59
+ set prefixText(value: Affix | undefined);
60
+ get suffixText(): Affix;
61
+ set suffixText(value: Affix | undefined);
62
+ get isSpinning(): boolean;
63
+ get color(): Color | false | undefined;
64
+ set color(value: Color | false | undefined);
65
+ get isEnabled(): boolean;
66
+ set isEnabled(value: boolean);
67
+ get isSilent(): boolean;
68
+ set isSilent(value: boolean);
69
+ frame(): string;
70
+ clear(): this;
71
+ render(): this;
72
+ start(text?: string): this;
73
+ stop(): this;
74
+ succeed(text?: string): this;
75
+ fail(text?: string): this;
76
+ warn(text?: string): this;
77
+ info(text?: string): this;
78
+ stopAndPersist(options?: PersistOptions): this;
79
+ }
80
+ export default function ora(options?: Options | string): Ora;
81
+ /** Run an action under a spinner, and land on the right symbol whichever way it ends. */
82
+ export declare function oraPromise<T>(action: Promise<T> | ((spinner: Ora) => Promise<T>), options?: PromiseOptions<T> | string): Promise<T>;
package/dist/ora.js ADDED
@@ -0,0 +1,515 @@
1
+ import { Buffer } from 'node:buffer';
2
+ import process from 'node:process';
3
+ import chalk from 'roundel/chalk';
4
+ import { HIDE_CURSOR, restoreCursorOnExit, SHOW_CURSOR } from './cursor.js';
5
+ import spinnerCorpus from './spinners.json' with { type: 'json' };
6
+ import { lineCount } from './width.js';
7
+ export const spinners = spinnerCorpus;
8
+ function isUnicodeSupported() {
9
+ const { env } = process;
10
+ const { TERM, TERM_PROGRAM } = env;
11
+ if (process.platform !== 'win32')
12
+ return TERM !== 'linux';
13
+ return (Boolean(env['WT_SESSION']) ||
14
+ Boolean(env['TERMINUS_SUBLIME']) ||
15
+ env['ConEmuTask'] === '{cmd::Cmder}' ||
16
+ TERM_PROGRAM === 'Terminus-Sublime' ||
17
+ TERM_PROGRAM === 'vscode' ||
18
+ TERM === 'xterm-256color' ||
19
+ TERM === 'alacritty' ||
20
+ TERM === 'rxvt-unicode' ||
21
+ TERM === 'rxvt-unicode-256color' ||
22
+ env['TERMINAL_EMULATOR'] === 'JetBrains-JediTerm');
23
+ }
24
+ function isInteractive(stream) {
25
+ return Boolean(stream?.isTTY) && process.env['TERM'] !== 'dumb' && !('CI' in process.env);
26
+ }
27
+ const UNICODE = isUnicodeSupported();
28
+ const logSymbols = {
29
+ info: chalk.blue(UNICODE ? 'ℹ' : 'i'),
30
+ success: chalk.green(UNICODE ? '✔' : '√'),
31
+ warning: chalk.yellow(UNICODE ? '⚠' : '‼'),
32
+ error: chalk.red(UNICODE ? '✖' : '×'),
33
+ };
34
+ const ASCII_ETX_CODE = 0x03;
35
+ class StdinDiscarder {
36
+ #activeCount = 0;
37
+ #stdin;
38
+ #stdinWasPaused = false;
39
+ #stdinWasRaw = false;
40
+ #handleInput = (chunk) => {
41
+ const length = chunk?.length;
42
+ if (length === undefined || length === 0)
43
+ return;
44
+ const code = typeof chunk === 'string' ? chunk.codePointAt(0) : chunk[0];
45
+ if (code === ASCII_ETX_CODE)
46
+ process.kill(process.pid, 'SIGINT');
47
+ };
48
+ start() {
49
+ this.#activeCount += 1;
50
+ if (this.#activeCount === 1)
51
+ this.#realStart();
52
+ }
53
+ stop() {
54
+ if (this.#activeCount === 0)
55
+ return;
56
+ this.#activeCount -= 1;
57
+ if (this.#activeCount === 0)
58
+ this.#realStop();
59
+ }
60
+ #realStart() {
61
+ const stdin = process.stdin;
62
+ if (process.platform === 'win32' || stdin?.isTTY !== true || typeof stdin.setRawMode !== 'function') {
63
+ this.#stdin = undefined;
64
+ return;
65
+ }
66
+ this.#stdin = stdin;
67
+ this.#stdinWasPaused = stdin.isPaused();
68
+ this.#stdinWasRaw = Boolean(stdin.isRaw);
69
+ stdin.setRawMode(true);
70
+ stdin.prependListener('data', this.#handleInput);
71
+ if (this.#stdinWasPaused)
72
+ stdin.resume();
73
+ }
74
+ #realStop() {
75
+ const stdin = this.#stdin;
76
+ if (stdin === undefined)
77
+ return;
78
+ stdin.off('data', this.#handleInput);
79
+ if (stdin.isTTY === true)
80
+ stdin.setRawMode?.(this.#stdinWasRaw);
81
+ if (this.#stdinWasPaused)
82
+ stdin.pause();
83
+ this.#stdin = undefined;
84
+ this.#stdinWasPaused = false;
85
+ this.#stdinWasRaw = false;
86
+ }
87
+ }
88
+ const stdinDiscarder = new StdinDiscarder();
89
+ const RENDER_DEFERRAL_TIMEOUT = 200;
90
+ const SYNCHRONIZED_OUTPUT_ENABLE = '\u001B[?2026h';
91
+ const SYNCHRONIZED_OUTPUT_DISABLE = '\u001B[?2026l';
92
+ const FALLBACK_INTERVAL = 100;
93
+ const DEFAULT_COLUMNS = 80;
94
+ const TRUNCATION_RESERVE = 1;
95
+ const activeHooksPerStream = new Map();
96
+ const validColors = new Set(['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white', 'gray']);
97
+ function paint(color, text) {
98
+ if (color === undefined || color === false)
99
+ return text;
100
+ return chalk[color]?.(text) ?? text;
101
+ }
102
+ export class Ora {
103
+ #linesToClear = 0;
104
+ #frameIndex = -1;
105
+ #lastFrameTime = 0;
106
+ #options;
107
+ #spinner;
108
+ #stream;
109
+ #id;
110
+ #hookedStreams = new Map();
111
+ #isInternalWrite = false;
112
+ #drainHandler;
113
+ #deferRenderTimer;
114
+ #isDiscardingStdin = false;
115
+ #color;
116
+ constructor(options) {
117
+ const given = typeof options === 'string' ? { text: options } : options;
118
+ this.#options = { color: 'cyan', stream: process.stderr, discardStdin: true, hideCursor: true, isEnabled: false, isSilent: false, indent: 0, ...given };
119
+ this.color = this.#options.color;
120
+ this.#stream = this.#options.stream;
121
+ if (typeof given?.isEnabled !== 'boolean')
122
+ this.#options.isEnabled = isInteractive(this.#stream);
123
+ if (typeof given?.isSilent !== 'boolean')
124
+ this.#options.isSilent = false;
125
+ if (this.#options.interval !== undefined && !(Number.isInteger(this.#options.interval) && this.#options.interval > 0)) {
126
+ throw new Error('The `interval` option must be a positive integer');
127
+ }
128
+ const userInterval = this.#options.interval;
129
+ this.spinner = this.#options.spinner;
130
+ this.#options.interval = userInterval;
131
+ this.text = this.#options.text;
132
+ this.prefixText = this.#options.prefixText;
133
+ this.suffixText = this.#options.suffixText;
134
+ this.indent = this.#options.indent;
135
+ if (process.env['NODE_ENV'] === 'test')
136
+ this.#exposeTestProperties();
137
+ }
138
+ #exposeTestProperties() {
139
+ const self = this;
140
+ self['_stream'] = this.#stream;
141
+ self['_isEnabled'] = this.#options.isEnabled;
142
+ Object.defineProperty(this, '_linesToClear', {
143
+ get: () => this.#linesToClear,
144
+ set: (value) => {
145
+ this.#linesToClear = value;
146
+ },
147
+ });
148
+ Object.defineProperty(this, '_frameIndex', { get: () => this.#frameIndex });
149
+ Object.defineProperty(this, '_lineCount', {
150
+ get: () => {
151
+ const columns = this.#stream.columns ?? DEFAULT_COLUMNS;
152
+ const prefixText = typeof this.#options.prefixText === 'function' ? '' : this.#options.prefixText;
153
+ const suffixText = typeof this.#options.suffixText === 'function' ? '' : this.#options.suffixText;
154
+ const fullPrefixText = typeof prefixText === 'string' && prefixText !== '' ? `${prefixText} ` : '';
155
+ const fullSuffixText = typeof suffixText === 'string' && suffixText !== '' ? ` ${suffixText}` : '';
156
+ const text = typeof this.#options.text === 'string' ? ` ${this.#options.text}` : '';
157
+ return lineCount(`${' '.repeat(this.#options.indent)}${fullPrefixText}-${text}${fullSuffixText}`, columns);
158
+ },
159
+ });
160
+ }
161
+ get indent() {
162
+ return this.#options.indent;
163
+ }
164
+ set indent(indent) {
165
+ const value = indent === undefined ? 0 : indent;
166
+ if (!(value >= 0 && Number.isInteger(value)))
167
+ throw new Error('The `indent` option must be an integer from 0 and up');
168
+ this.#options.indent = value;
169
+ }
170
+ get interval() {
171
+ return this.#options.interval ?? this.#spinner.interval ?? FALLBACK_INTERVAL;
172
+ }
173
+ get spinner() {
174
+ return this.#spinner;
175
+ }
176
+ set spinner(spinner) {
177
+ this.#frameIndex = -1;
178
+ this.#options.interval = undefined;
179
+ if (typeof spinner === 'object') {
180
+ if (!Array.isArray(spinner.frames) || spinner.frames.length === 0 || spinner.frames.some((frame) => typeof frame !== 'string')) {
181
+ throw new Error('The given spinner must have a non-empty `frames` array of strings');
182
+ }
183
+ if (spinner.interval !== undefined && !(Number.isInteger(spinner.interval) && spinner.interval > 0)) {
184
+ throw new Error('`spinner.interval` must be a positive integer if provided');
185
+ }
186
+ this.#spinner = spinner;
187
+ return;
188
+ }
189
+ const named = spinner === undefined || spinner === 'default' ? undefined : spinners[spinner];
190
+ if (!isUnicodeSupported())
191
+ this.#spinner = spinners['line'];
192
+ else if (spinner === undefined)
193
+ this.#spinner = spinners['dots'];
194
+ else if (named !== undefined)
195
+ this.#spinner = named;
196
+ else
197
+ throw new Error(`There is no built-in spinner named '${spinner}'. See https://github.com/sindresorhus/cli-spinners/blob/main/spinners.json for a full list.`);
198
+ }
199
+ get text() {
200
+ return this.#options.text;
201
+ }
202
+ set text(value) {
203
+ this.#options.text = value === undefined ? '' : value;
204
+ }
205
+ get prefixText() {
206
+ return this.#options.prefixText;
207
+ }
208
+ set prefixText(value) {
209
+ this.#options.prefixText = value === undefined ? '' : value;
210
+ }
211
+ get suffixText() {
212
+ return this.#options.suffixText;
213
+ }
214
+ set suffixText(value) {
215
+ this.#options.suffixText = value === undefined ? '' : value;
216
+ }
217
+ get isSpinning() {
218
+ return this.#id !== undefined;
219
+ }
220
+ get color() {
221
+ return this.#color;
222
+ }
223
+ set color(value) {
224
+ if (value !== undefined && value !== false && !validColors.has(value))
225
+ throw new Error('The `color` option must be a valid color or `false` to disable');
226
+ this.#color = value;
227
+ }
228
+ get isEnabled() {
229
+ return this.#options.isEnabled && !this.#options.isSilent;
230
+ }
231
+ set isEnabled(value) {
232
+ if (typeof value !== 'boolean')
233
+ throw new TypeError('The `isEnabled` option must be a boolean');
234
+ this.#options.isEnabled = value;
235
+ }
236
+ get isSilent() {
237
+ return this.#options.isSilent;
238
+ }
239
+ set isSilent(value) {
240
+ if (typeof value !== 'boolean')
241
+ throw new TypeError('The `isSilent` option must be a boolean');
242
+ this.#options.isSilent = value;
243
+ }
244
+ frame() {
245
+ const now = Date.now();
246
+ if (this.#frameIndex === -1 || now - this.#lastFrameTime >= this.interval) {
247
+ this.#frameIndex = (this.#frameIndex + 1) % this.#spinner.frames.length;
248
+ this.#lastFrameTime = now;
249
+ }
250
+ const frame = paint(this.#color, this.#spinner.frames[this.#frameIndex] ?? '');
251
+ const text = typeof this.#options.text === 'string' ? ` ${this.#options.text}` : '';
252
+ return this.#fullPrefixText() + frame + text + this.#fullSuffixText();
253
+ }
254
+ clear() {
255
+ if (!this.isEnabled || this.#stream.isTTY !== true)
256
+ return this;
257
+ this.#internalWrite(() => {
258
+ this.#stream.cursorTo?.(0);
259
+ for (let index = 0; index < this.#linesToClear; index += 1) {
260
+ if (index > 0)
261
+ this.#stream.moveCursor?.(0, -1);
262
+ this.#stream.clearLine?.(1);
263
+ }
264
+ if (this.#options.indent)
265
+ this.#stream.cursorTo?.(this.#options.indent);
266
+ return undefined;
267
+ });
268
+ this.#linesToClear = 0;
269
+ return this;
270
+ }
271
+ render() {
272
+ if (!this.isEnabled || this.#drainHandler || this.#deferRenderTimer)
273
+ return this;
274
+ const useSynchronizedOutput = this.#stream.isTTY === true;
275
+ let shouldDisableSynchronizedOutput = false;
276
+ try {
277
+ if (useSynchronizedOutput) {
278
+ this.#internalWrite(() => this.#stream.write(SYNCHRONIZED_OUTPUT_ENABLE));
279
+ shouldDisableSynchronizedOutput = true;
280
+ }
281
+ this.clear();
282
+ let frameContent = this.frame();
283
+ const columns = this.#stream.columns ?? DEFAULT_COLUMNS;
284
+ const actualLineCount = lineCount(frameContent, columns);
285
+ const consoleHeight = this.#stream.rows;
286
+ if (consoleHeight !== undefined && consoleHeight > 1 && actualLineCount > consoleHeight) {
287
+ const lines = frameContent.split('\n');
288
+ frameContent = [...lines.slice(0, consoleHeight - TRUNCATION_RESERVE), '... (content truncated to fit terminal)'].join('\n');
289
+ }
290
+ const canContinue = this.#internalWrite(() => this.#stream.write(frameContent));
291
+ if (canContinue === false && this.#stream.isTTY === true) {
292
+ this.#drainHandler = () => {
293
+ this.#drainHandler = undefined;
294
+ this.#tryRender();
295
+ };
296
+ this.#stream.once?.('drain', this.#drainHandler);
297
+ }
298
+ this.#linesToClear = lineCount(frameContent, columns);
299
+ }
300
+ finally {
301
+ if (shouldDisableSynchronizedOutput)
302
+ this.#internalWrite(() => this.#stream.write(SYNCHRONIZED_OUTPUT_DISABLE));
303
+ }
304
+ return this;
305
+ }
306
+ start(text) {
307
+ if (text !== undefined)
308
+ this.text = text;
309
+ if (this.isSilent)
310
+ return this;
311
+ if (!this.isEnabled) {
312
+ const symbol = this.text ? '-' : '';
313
+ const line = ' '.repeat(this.#options.indent) + this.#buildOutputLine(symbol, this.text, this.#options.prefixText, this.#options.suffixText);
314
+ if (line.trim() !== '')
315
+ this.#internalWrite(() => this.#stream.write(`${line}\n`));
316
+ return this;
317
+ }
318
+ if (this.isSpinning)
319
+ return this;
320
+ if (this.#options.hideCursor)
321
+ this.#hideCursor();
322
+ if (this.#options.discardStdin && process.stdin.isTTY) {
323
+ stdinDiscarder.start();
324
+ this.#isDiscardingStdin = true;
325
+ }
326
+ this.#installHook();
327
+ this.render();
328
+ this.#id = setInterval(this.render.bind(this), this.interval);
329
+ return this;
330
+ }
331
+ stop() {
332
+ clearInterval(this.#id);
333
+ this.#id = undefined;
334
+ this.#frameIndex = -1;
335
+ this.#lastFrameTime = 0;
336
+ this.#clearRenderDeferral();
337
+ this.#uninstallHook();
338
+ if (this.#drainHandler) {
339
+ this.#stream.removeListener?.('drain', this.#drainHandler);
340
+ this.#drainHandler = undefined;
341
+ }
342
+ if (this.isEnabled) {
343
+ this.clear();
344
+ if (this.#options.hideCursor)
345
+ this.#showCursor();
346
+ }
347
+ if (this.#isDiscardingStdin) {
348
+ this.#isDiscardingStdin = false;
349
+ stdinDiscarder.stop();
350
+ }
351
+ return this;
352
+ }
353
+ succeed(text) {
354
+ return this.stopAndPersist({ symbol: logSymbols.success, ...(text === undefined ? {} : { text }) });
355
+ }
356
+ fail(text) {
357
+ return this.stopAndPersist({ symbol: logSymbols.error, ...(text === undefined ? {} : { text }) });
358
+ }
359
+ warn(text) {
360
+ return this.stopAndPersist({ symbol: logSymbols.warning, ...(text === undefined ? {} : { text }) });
361
+ }
362
+ info(text) {
363
+ return this.stopAndPersist({ symbol: logSymbols.info, ...(text === undefined ? {} : { text }) });
364
+ }
365
+ stopAndPersist(options = {}) {
366
+ if (this.isSilent)
367
+ return this;
368
+ const symbol = options.symbol ?? ' ';
369
+ const text = options.text ?? this.text;
370
+ const prefixText = options.prefixText ?? this.#options.prefixText;
371
+ const suffixText = options.suffixText ?? this.#options.suffixText;
372
+ const textToWrite = `${this.#buildOutputLine(symbol, text, prefixText, suffixText)}\n`;
373
+ this.stop();
374
+ this.#internalWrite(() => this.#stream.write(textToWrite));
375
+ return this;
376
+ }
377
+ #internalWrite(fn) {
378
+ this.#isInternalWrite = true;
379
+ try {
380
+ return fn();
381
+ }
382
+ finally {
383
+ this.#isInternalWrite = false;
384
+ }
385
+ }
386
+ #tryRender() {
387
+ if (this.isSpinning)
388
+ this.render();
389
+ }
390
+ #hideCursor() {
391
+ if (this.#stream.isTTY !== true)
392
+ return;
393
+ restoreCursorOnExit();
394
+ this.#stream.write(HIDE_CURSOR);
395
+ }
396
+ #showCursor() {
397
+ if (this.#stream.isTTY !== true)
398
+ return;
399
+ this.#stream.write(SHOW_CURSOR);
400
+ }
401
+ #formatAffix(value, separator, placeBefore) {
402
+ const resolved = typeof value === 'function' ? value() : value;
403
+ if (typeof resolved === 'string' && resolved !== '')
404
+ return placeBefore ? separator + resolved : resolved + separator;
405
+ return '';
406
+ }
407
+ #fullPrefixText(prefixText = this.#options.prefixText, postfix = ' ') {
408
+ return this.#formatAffix(prefixText, postfix, false);
409
+ }
410
+ #fullSuffixText(suffixText = this.#options.suffixText, prefix = ' ') {
411
+ return this.#formatAffix(suffixText, prefix, true);
412
+ }
413
+ #buildOutputLine(symbol, text, prefixText, suffixText) {
414
+ const separator = symbol ? ' ' : '';
415
+ const fullText = typeof text === 'string' ? separator + text : '';
416
+ return this.#fullPrefixText(prefixText, ' ') + symbol + fullText + this.#fullSuffixText(suffixText, ' ');
417
+ }
418
+ #scheduleRenderDeferral() {
419
+ if (this.#deferRenderTimer)
420
+ return;
421
+ this.#deferRenderTimer = setTimeout(() => {
422
+ this.#deferRenderTimer = undefined;
423
+ if (this.isSpinning)
424
+ this.#tryRender();
425
+ }, RENDER_DEFERRAL_TIMEOUT);
426
+ this.#deferRenderTimer.unref?.();
427
+ }
428
+ #clearRenderDeferral() {
429
+ if (this.#deferRenderTimer) {
430
+ clearTimeout(this.#deferRenderTimer);
431
+ this.#deferRenderTimer = undefined;
432
+ }
433
+ }
434
+ #installHook() {
435
+ if (!this.isEnabled || this.#hookedStreams.size > 0)
436
+ return;
437
+ for (const stream of new Set([this.#stream, process.stdout, process.stderr]))
438
+ this.#hookStream(stream);
439
+ }
440
+ #hookStream(stream) {
441
+ if (!stream || this.#hookedStreams.has(stream) || stream.isTTY !== true || typeof stream.write !== 'function')
442
+ return;
443
+ if (activeHooksPerStream.has(stream)) {
444
+ console.warn('[ora] Multiple concurrent spinners detected. This may cause visual corruption. Use one spinner at a time.');
445
+ }
446
+ const originalWrite = stream.write;
447
+ this.#hookedStreams.set(stream, originalWrite);
448
+ activeHooksPerStream.set(stream, this);
449
+ stream.write = (chunk, encoding, callback) => this.#hookedWrite(stream, originalWrite, chunk, encoding, callback);
450
+ }
451
+ #uninstallHook() {
452
+ for (const [stream, originalWrite] of this.#hookedStreams) {
453
+ stream.write = originalWrite;
454
+ if (activeHooksPerStream.get(stream) === this)
455
+ activeHooksPerStream.delete(stream);
456
+ }
457
+ this.#hookedStreams.clear();
458
+ }
459
+ #hookedWrite(stream, originalWrite, chunk, encoding, callback) {
460
+ const [enc, cb] = typeof encoding === 'function' ? [undefined, encoding] : [encoding, callback];
461
+ if (this.#isInternalWrite)
462
+ return originalWrite.call(stream, chunk, enc, cb);
463
+ this.clear();
464
+ const chunkString = stringifyChunk(chunk, enc);
465
+ const terminatesLine = chunkString.endsWith('\n') || chunkString.endsWith('\r');
466
+ const writeResult = originalWrite.call(stream, chunk, enc, cb);
467
+ if (terminatesLine)
468
+ this.#clearRenderDeferral();
469
+ else if (chunkString.length > 0)
470
+ this.#scheduleRenderDeferral();
471
+ if (this.isSpinning && !this.#deferRenderTimer)
472
+ this.render();
473
+ return writeResult;
474
+ }
475
+ }
476
+ function stringifyChunk(chunk, encoding) {
477
+ if (chunk === undefined || chunk === null)
478
+ return '';
479
+ if (typeof chunk === 'string')
480
+ return chunk;
481
+ if (Buffer.isBuffer(chunk) || ArrayBuffer.isView(chunk)) {
482
+ const normalized = typeof encoding === 'string' && encoding !== '' && encoding !== 'buffer' ? encoding : 'utf8';
483
+ return Buffer.from(chunk).toString(normalized);
484
+ }
485
+ return String(chunk);
486
+ }
487
+ export default function ora(options) {
488
+ return new Ora(options);
489
+ }
490
+ export async function oraPromise(action, options) {
491
+ const actionIsFunction = typeof action === 'function';
492
+ const actionIsPromise = typeof action?.then === 'function';
493
+ if (!actionIsFunction && !actionIsPromise)
494
+ throw new TypeError('Parameter `action` must be a Function or a Promise');
495
+ const settings = typeof options === 'object' && options !== null ? options : {};
496
+ const { successText, failText, successSymbol, failSymbol } = settings;
497
+ const spinner = ora(options).start();
498
+ try {
499
+ const result = await (actionIsFunction ? action(spinner) : action);
500
+ const text = successText === undefined ? undefined : typeof successText === 'string' ? successText : successText(result);
501
+ if (successSymbol === undefined)
502
+ spinner.succeed(text);
503
+ else
504
+ spinner.stopAndPersist({ symbol: successSymbol, ...(text === undefined ? {} : { text }) });
505
+ return result;
506
+ }
507
+ catch (error) {
508
+ const text = failText === undefined ? undefined : typeof failText === 'string' ? failText : failText(error);
509
+ if (failSymbol === undefined)
510
+ spinner.fail(text);
511
+ else
512
+ spinner.stopAndPersist({ symbol: failSymbol, ...(text === undefined ? {} : { text }) });
513
+ throw error;
514
+ }
515
+ }