@reliverse/rempts 1.7.57 → 1.7.59

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.
@@ -1,255 +1,451 @@
1
1
  import { re } from "@reliverse/relico";
2
- import { block } from "../cancel/cancel.js";
3
- const unicode = process.platform !== "win32" || process.env["TERM_PROGRAM"] === "vscode";
4
- const defaultFrames = unicode ? ["\u25D2", "\u25D0", "\u25D3", "\u25D1"] : ["\u2022", "o", "O", "0"];
5
- const defaultDelay = unicode ? 80 : 120;
6
- function isInteractive() {
7
- return process.stdout.isTTY && !process.env["CI"] && !process.env["GITHUB_ACTIONS"] && !process.env["GITLAB_CI"] && !process.env["BUILDKITE"] && process.env["TERM"] !== "dumb";
8
- }
9
- function formatProgress(options) {
10
- const { current, total, format = "both" } = options;
11
- const percentage = Math.round(current / total * 100);
12
- switch (format) {
13
- case "percentage":
14
- return `${percentage}%`;
15
- case "count":
16
- return `${current}/${total}`;
17
- case "both":
18
- return `${current}/${total} (${percentage}%)`;
19
- default:
20
- return `${current}/${total}`;
2
+ import cliSpinners, { randomSpinner } from "cli-spinners";
3
+ import prettyBytes from "pretty-bytes";
4
+ import prettyMilliseconds from "pretty-ms";
5
+ import ora, {
6
+ oraPromise
7
+ } from "./spinner-impl.js";
8
+ function isColorsEnabled(isSpinnerEnabledFlag) {
9
+ if (process.env["CLI_NO_COLOR"] === "1") return false;
10
+ return isSpinnerEnabledFlag;
11
+ }
12
+ function toStyler(input, fallback) {
13
+ if (!input) return fallback;
14
+ if (typeof input === "function") return input;
15
+ const fn = re[input];
16
+ if (typeof fn === "function") return fn;
17
+ return fallback;
18
+ }
19
+ const identity = (s) => s;
20
+ function isCIEnvironment() {
21
+ const { CI, GITHUB_ACTIONS, BUILD_NUMBER, RUN_ID } = process.env;
22
+ return CI === "true" || GITHUB_ACTIONS === "true" || typeof BUILD_NUMBER !== "undefined" || typeof RUN_ID !== "undefined";
23
+ }
24
+ const defaultStderr = process.stderr;
25
+ function isInteractive(stream = defaultStderr) {
26
+ return Boolean(stream && stream.isTTY);
27
+ }
28
+ function getDefaultEnabled(stream = defaultStderr) {
29
+ const disabledByEnv = process.env["CLI_NO_SPINNER"] === "1" || process.env["CLI_NO_COLOR"] === "1";
30
+ if (disabledByEnv) return false;
31
+ if (isCIEnvironment()) return false;
32
+ return isInteractive(stream);
33
+ }
34
+ export const defaultSpinnerOptions = {
35
+ color: "cyan",
36
+ spinner: "dots",
37
+ hideCursor: true,
38
+ indent: 0,
39
+ discardStdin: true,
40
+ respectEnv: true,
41
+ showTiming: false
42
+ };
43
+ export function isSpinnerEnabled(options) {
44
+ const stream = options?.stream ?? defaultStderr;
45
+ const respectEnv = options?.respectEnv !== false;
46
+ if (typeof options?.isEnabled === "boolean") return options.isEnabled;
47
+ return respectEnv ? getDefaultEnabled(stream) : true;
48
+ }
49
+ export function createSpinner(input) {
50
+ const base = typeof input === "string" ? { text: input } : { ...input ?? {} };
51
+ const stream = base.stream ?? defaultStderr;
52
+ const respectEnv = base.respectEnv !== false;
53
+ const isEnabled = base.isEnabled ?? (respectEnv ? getDefaultEnabled(stream) : true);
54
+ const isSilent = base.isSilent ?? false;
55
+ const resolvedColor = base.color ?? defaultSpinnerOptions.color ?? "cyan";
56
+ const resolvedSpinner = base.spinner ?? defaultSpinnerOptions.spinner ?? "dots";
57
+ const resolvedHideCursor = base.hideCursor ?? defaultSpinnerOptions.hideCursor ?? true;
58
+ const resolvedIndent = base.indent ?? defaultSpinnerOptions.indent ?? 0;
59
+ const resolvedDiscardStdin = base.discardStdin ?? defaultSpinnerOptions.discardStdin ?? true;
60
+ const options = {
61
+ // Defaults chosen to be broadly useful; callers can override
62
+ color: resolvedColor,
63
+ hideCursor: resolvedHideCursor,
64
+ indent: resolvedIndent,
65
+ stream,
66
+ isEnabled,
67
+ isSilent,
68
+ discardStdin: resolvedDiscardStdin,
69
+ ...resolvedSpinner !== void 0 ? { spinner: resolvedSpinner } : {},
70
+ ...base.interval !== void 0 ? { interval: base.interval } : {},
71
+ ...base.prefixText !== void 0 ? { prefixText: base.prefixText } : {},
72
+ ...base.suffixText !== void 0 ? { suffixText: base.suffixText } : {},
73
+ ...base.text !== void 0 ? { text: base.text } : {}
74
+ };
75
+ const spinner = ora(options);
76
+ const colorsEnabled = isColorsEnabled(isEnabled);
77
+ const themeObj = base.theme ?? {};
78
+ const dim = colorsEnabled ? themeObj.dim ?? re.dim ?? identity : identity;
79
+ const info = colorsEnabled ? themeObj.info ?? re.cyan ?? identity : identity;
80
+ const success = colorsEnabled ? themeObj.success ?? re.green ?? identity : identity;
81
+ const error = colorsEnabled ? themeObj.error ?? re.red ?? identity : identity;
82
+ const progress = colorsEnabled ? themeObj.progress ?? re.cyan ?? identity : identity;
83
+ const rate = colorsEnabled ? themeObj.rate ?? re.cyan ?? identity : identity;
84
+ const bytesColor = colorsEnabled ? themeObj.bytes ?? re.cyan ?? identity : identity;
85
+ const percent = colorsEnabled ? themeObj.percentage ?? re.yellow ?? identity : identity;
86
+ spinner.__reTheme = { dim, info, success, error, progress, rate, bytesColor, percent };
87
+ spinner.__textStyler = toStyler(base.textColor, info);
88
+ spinner.__prefixStyler = toStyler(base.prefixColor, identity);
89
+ spinner.__suffixStyler = toStyler(base.suffixColor, identity);
90
+ spinner.__successStyler = toStyler(base.successColor, success);
91
+ spinner.__failStyler = toStyler(base.failColor, error);
92
+ if (options.text) {
93
+ const style = spinner.__textStyler;
94
+ spinner.text = style(options.text);
21
95
  }
96
+ return spinner;
97
+ }
98
+ export async function withSpinnerPromise(action, options) {
99
+ return oraPromise(action, options);
22
100
  }
23
- function formatTimer(origin) {
24
- const duration = (performance.now() - origin) / 1e3;
25
- const min = Math.floor(duration / 60);
26
- const secs = Math.floor(duration % 60);
27
- return min > 0 ? `[${min}m ${secs}s]` : `[${secs}s]`;
28
- }
29
- function removeTrailingDots(msg) {
30
- return msg.replace(/\.+$/, "");
31
- }
32
- export function useSpinner(options) {
33
- let loop;
34
- const interactive = isInteractive();
35
- let unblock = null;
36
- const state = {
37
- isActive: false,
38
- isPaused: false,
39
- startTime: null,
40
- pausedTime: 0,
41
- text: options.text,
42
- isCancelled: false,
43
- origin: performance.now(),
44
- indicatorTimer: 0
101
+ export async function withSpinner(textOrOptions, action, onSuccessText, onFailText) {
102
+ const startTime = Date.now();
103
+ const options = typeof textOrOptions === "string" ? { text: textOrOptions } : textOrOptions;
104
+ const spinner = createSpinner(textOrOptions).start();
105
+ try {
106
+ const result = await action(spinner);
107
+ const successMsg = getSuccessMessage(result, onSuccessText, options, startTime);
108
+ const style = spinner.__successStyler ?? identity;
109
+ spinner.succeed(successMsg ? style(successMsg) : void 0);
110
+ return result;
111
+ } catch (error) {
112
+ const err = error;
113
+ const failMsg = getFailMessage(err, onFailText, options);
114
+ const style = spinner.__failStyler ?? identity;
115
+ spinner.fail(failMsg ? style(failMsg) : void 0);
116
+ throw err;
117
+ }
118
+ }
119
+ function getSuccessMessage(result, onSuccessText, options, startTime) {
120
+ let message;
121
+ if (typeof onSuccessText === "function") {
122
+ message = onSuccessText(result);
123
+ } else if (typeof onSuccessText === "string") {
124
+ message = onSuccessText;
125
+ } else if (options.defaultSuccess) {
126
+ message = options.defaultSuccess;
127
+ }
128
+ if (options.showTiming && message) {
129
+ const elapsed = Date.now() - startTime;
130
+ const timing = prettyMilliseconds(elapsed, { compact: true });
131
+ message = `${message} (${timing})`;
132
+ }
133
+ return message;
134
+ }
135
+ function getFailMessage(error, onFailText, options) {
136
+ if (typeof onFailText === "function") {
137
+ return onFailText(error);
138
+ } else if (typeof onFailText === "string") {
139
+ return onFailText;
140
+ } else if (options.defaultFail) {
141
+ return options.defaultFail;
142
+ }
143
+ }
144
+ export function updateSpinnerText(spinner, text, options) {
145
+ const { prefix, suffix } = options ?? {};
146
+ const styleText = spinner.__textStyler ?? identity;
147
+ const stylePrefix = spinner.__prefixStyler ?? identity;
148
+ const styleSuffix = spinner.__suffixStyler ?? identity;
149
+ const prefixStyled = prefix ? stylePrefix(prefix) : "";
150
+ const suffixStyled = suffix ? styleSuffix(suffix) : "";
151
+ const fullText = `${prefixStyled}${styleText(text)}${suffixStyled}`;
152
+ spinner.text = fullText;
153
+ }
154
+ export function stopAndPersist(spinner, options) {
155
+ spinner.stopAndPersist({
156
+ symbol: options.symbol ?? " ",
157
+ text: options.text ?? spinner.text,
158
+ prefixText: options.prefixText ?? spinner.prefixText,
159
+ suffixText: options.suffixText ?? spinner.suffixText
160
+ });
161
+ }
162
+ export function createTimedSpinner(input) {
163
+ const startTime = Date.now();
164
+ const options = typeof input === "string" ? { text: input } : { ...input ?? {} };
165
+ const spinner = createSpinner({ ...options, showTiming: true });
166
+ return {
167
+ spinner,
168
+ getElapsed: () => Date.now() - startTime,
169
+ succeedWithTiming: (text) => {
170
+ const elapsed = Date.now() - startTime;
171
+ const timing = prettyMilliseconds(elapsed, { compact: true });
172
+ const dim = spinner.__reTheme?.dim ?? identity;
173
+ const successStyle = spinner.__successStyler ?? identity;
174
+ const message = text ? `${successStyle(text)} ${dim(`(${timing})`)}` : void 0;
175
+ spinner.succeed(message);
176
+ },
177
+ failWithTiming: (text) => {
178
+ const elapsed = Date.now() - startTime;
179
+ const timing = prettyMilliseconds(elapsed, { compact: true });
180
+ const dim = spinner.__reTheme?.dim ?? identity;
181
+ const failStyle = spinner.__failStyler ?? identity;
182
+ const message = text ? `${failStyle(text)} ${dim(`(failed after ${timing})`)}` : void 0;
183
+ spinner.fail(message);
184
+ }
45
185
  };
46
- const handleExit = (code) => {
47
- const msg = code > 1 ? options.errorMessage ?? "Operation failed" : options.cancelMessage ?? "Operation cancelled";
48
- state.isCancelled = code === 1;
49
- if (state.isActive) {
50
- controls.stop(msg, code);
51
- if (state.isCancelled && typeof options.onCancel === "function") {
52
- options.onCancel();
186
+ }
187
+ export function createSpinnerGroup(options) {
188
+ const { items, concurrent = false, ...baseOptions } = options;
189
+ const spinners2 = items.map((item, index) => {
190
+ const spinnerOptions = {
191
+ ...baseOptions,
192
+ text: item,
193
+ indent: (baseOptions.indent ?? 0) + (concurrent ? 0 : index * 2)
194
+ };
195
+ return createSpinner(spinnerOptions);
196
+ });
197
+ return {
198
+ spinners: spinners2,
199
+ updateAll: (text) => {
200
+ for (const spinner of spinners2) {
201
+ updateSpinnerText(spinner, text);
202
+ }
203
+ },
204
+ succeedAll: (text) => {
205
+ for (const spinner of spinners2) {
206
+ const style = spinner.__successStyler ?? identity;
207
+ spinner.succeed(text ? style(text) : void 0);
208
+ }
209
+ },
210
+ failAll: (text) => {
211
+ for (const spinner of spinners2) {
212
+ const style = spinner.__failStyler ?? identity;
213
+ spinner.fail(text ? style(text) : void 0);
214
+ }
215
+ },
216
+ stopAll: () => {
217
+ for (const spinner of spinners2) {
218
+ spinner.stop();
53
219
  }
54
220
  }
55
221
  };
56
- const errorEventHandler = () => handleExit(2);
57
- const signalEventHandler = () => handleExit(1);
58
- const registerHooks = () => {
59
- process.on("uncaughtExceptionMonitor", errorEventHandler);
60
- process.on("unhandledRejection", errorEventHandler);
61
- process.on("SIGINT", signalEventHandler);
62
- process.on("SIGTERM", signalEventHandler);
63
- process.on("exit", handleExit);
64
- if (options.signal) {
65
- options.signal.addEventListener("abort", signalEventHandler);
222
+ }
223
+ export async function withEnhancedSpinner(textOrOptions, action) {
224
+ const startTime = Date.now();
225
+ const options = typeof textOrOptions === "string" ? { text: textOrOptions, showTiming: false } : { showTiming: false, ...textOrOptions };
226
+ const baseSpinner = createSpinner(options).start();
227
+ const enhancedSpinner = Object.assign(baseSpinner, {
228
+ updateText: (text, updateOptions) => {
229
+ updateSpinnerText(baseSpinner, text, updateOptions);
230
+ },
231
+ setProgress: (current, total, text) => {
232
+ const progressText = text ? `${text} (${current}/${total})` : `${current}/${total}`;
233
+ baseSpinner.text = progressText;
66
234
  }
67
- };
68
- const clearHooks = () => {
69
- process.removeListener("uncaughtExceptionMonitor", errorEventHandler);
70
- process.removeListener("unhandledRejection", errorEventHandler);
71
- process.removeListener("SIGINT", signalEventHandler);
72
- process.removeListener("SIGTERM", signalEventHandler);
73
- process.removeListener("exit", handleExit);
74
- if (options.signal) {
75
- options.signal.removeEventListener("abort", signalEventHandler);
235
+ });
236
+ try {
237
+ const result = await action(enhancedSpinner);
238
+ let successText = options.successText;
239
+ if (options.showTiming && successText) {
240
+ const elapsed = Date.now() - startTime;
241
+ const timing = prettyMilliseconds(elapsed, { compact: true });
242
+ successText = `${successText} (${timing})`;
243
+ }
244
+ baseSpinner.succeed(successText);
245
+ return result;
246
+ } catch (error) {
247
+ const err = error;
248
+ let failText = options.failText;
249
+ if (options.showTiming && failText) {
250
+ const elapsed = Date.now() - startTime;
251
+ const timing = prettyMilliseconds(elapsed, { compact: true });
252
+ failText = `${failText} (failed after ${timing})`;
253
+ }
254
+ baseSpinner.fail(failText);
255
+ throw err;
256
+ }
257
+ }
258
+ export function isSpinnerRunning(spinner) {
259
+ return spinner.isSpinning;
260
+ }
261
+ export function safeStopSpinner(spinner) {
262
+ if (spinner?.isSpinning) {
263
+ spinner.stop();
264
+ }
265
+ }
266
+ export function createBuildSpinner(operation, options) {
267
+ const spinner = createSpinner({ text: operation, ...options }).start();
268
+ return {
269
+ spinner,
270
+ complete: (message) => {
271
+ const successStyle = spinner.__successStyler ?? identity;
272
+ spinner.succeed(successStyle(message ?? `${operation} completed successfully!`));
273
+ },
274
+ error: (error) => {
275
+ const errorMessage = typeof error === "string" ? error : error.message;
276
+ const failStyle = spinner.__failStyler ?? identity;
277
+ spinner.fail(failStyle(`${operation} failed: ${errorMessage}`));
278
+ },
279
+ updateProgress: (step) => {
280
+ const info = spinner.__reTheme?.info ?? identity;
281
+ updateSpinnerText(spinner, `${operation} - ${info(step)}`);
76
282
  }
77
283
  };
78
- const clearPrevMessage = () => {
79
- if (state.prevMessage === void 0) return;
80
- if (process.env["CI"]) process.stdout.write("\n");
81
- const prevLines = state.prevMessage.split("\n");
82
- process.stdout.write(`\x1B[${prevLines.length}A`);
83
- process.stdout.write("\x1B[0J");
84
- };
85
- const controls = {
86
- start: (text) => {
87
- if (text) {
88
- options.text = text;
89
- state.text = text;
90
- }
91
- if (options.silent || !interactive) {
92
- console.log(options.prefixText ? `${options.prefixText} ${options.text}` : options.text);
93
- state.isActive = true;
94
- state.startTime = Date.now();
95
- return controls;
284
+ }
285
+ export function createFileProgressSpinner(operation, options) {
286
+ const { totalBytes, showBytes = true, showRate = false, ...spinnerOptions } = options ?? {};
287
+ const startTime = Date.now();
288
+ const spinner = createSpinner({ text: operation, ...spinnerOptions }).start();
289
+ return {
290
+ spinner,
291
+ updateProgress: (bytesProcessed, fileName) => {
292
+ let progressText = operation;
293
+ if (fileName) {
294
+ const info = spinner.__reTheme?.info ?? identity;
295
+ progressText += ` - ${info(fileName)}`;
96
296
  }
97
- state.isActive = true;
98
- state.origin = performance.now();
99
- unblock = block({ output: process.stdout });
100
- process.stdout.write(`${re.dim("\u2502")}
101
- `);
102
- let frameIndex = 0;
103
- registerHooks();
104
- loop = setInterval(() => {
105
- if (process.env["CI"] && state.text === state.prevMessage) {
106
- return;
107
- }
108
- clearPrevMessage();
109
- state.prevMessage = state.text;
110
- const frame = re.magenta((options.frames ?? defaultFrames)[frameIndex] ?? "");
111
- if (process.env["CI"]) {
112
- process.stdout.write(`${frame} ${state.text}...`);
113
- } else if (options.indicator === "timer") {
114
- process.stdout.write(`${frame} ${state.text} ${formatTimer(state.origin)}`);
297
+ if (showBytes) {
298
+ if (totalBytes) {
299
+ const percentage = Math.round(bytesProcessed / totalBytes * 100);
300
+ const bytesColor = spinner.__reTheme?.bytesColor ?? identity;
301
+ const percent = spinner.__reTheme?.percent ?? identity;
302
+ progressText += ` (${bytesColor(prettyBytes(bytesProcessed))}/${bytesColor(prettyBytes(totalBytes))} - ${percent(`${percentage}%`)})`;
115
303
  } else {
116
- const loadingDots = ".".repeat(Math.floor(state.indicatorTimer)).slice(0, 3);
117
- process.stdout.write(`${frame} ${state.text}${loadingDots}`);
304
+ const bytesColor = spinner.__reTheme?.bytesColor ?? identity;
305
+ progressText += ` (${bytesColor(prettyBytes(bytesProcessed))})`;
118
306
  }
119
- frameIndex = (frameIndex + 1) % (options.frames ?? defaultFrames).length;
120
- state.indicatorTimer = state.indicatorTimer < 4 ? state.indicatorTimer + 0.125 : 0;
121
- }, options.delay ?? defaultDelay);
122
- return controls;
123
- },
124
- stop: (text, code = 0) => {
125
- if (!state.isActive) return;
126
- state.isActive = false;
127
- clearInterval(loop);
128
- clearPrevMessage();
129
- const step = code === 0 ? re.green("\u2713") : code === 1 ? re.red("\u2717") : re.red("\u2717");
130
- const finalText = text ?? state.text;
131
- if (options.indicator === "timer") {
132
- process.stdout.write(`${step} ${finalText} ${formatTimer(state.origin)}
133
- `);
134
- } else {
135
- process.stdout.write(`${step} ${finalText}
136
- `);
137
- }
138
- clearHooks();
139
- if (unblock) unblock();
140
- },
141
- setText: (text) => {
142
- state.text = removeTrailingDots(text);
143
- options.text = text;
144
- },
145
- setProgress: (progress) => {
146
- const progressText = formatProgress(progress);
147
- const newText = `${state.text} [${progressText}]`;
148
- state.text = newText;
149
- options.text = newText;
150
- },
151
- succeed: (text) => {
152
- const successText = text ?? options.successText ?? state.text;
153
- controls.stop(successText, 0);
154
- },
155
- fail: (text) => {
156
- const failText = text ?? options.failText ?? state.text;
157
- controls.stop(failText, 2);
158
- },
159
- warn: (text) => {
160
- const warnText = text ?? state.text;
161
- controls.stop(warnText, 0);
162
- },
163
- info: (text) => {
164
- const infoText = text ?? state.text;
165
- controls.stop(infoText, 0);
166
- },
167
- isSpinning: () => {
168
- return state.isActive && !state.isPaused;
169
- },
170
- clear: () => {
171
- clearPrevMessage();
172
- },
173
- getElapsedTime: () => {
174
- if (!state.startTime) return 0;
175
- const currentTime = Date.now();
176
- return currentTime - state.startTime - state.pausedTime;
177
- },
178
- pause: () => {
179
- if (state.isActive && !state.isPaused) {
180
- clearInterval(loop);
181
- state.isPaused = true;
182
307
  }
308
+ spinner.text = progressText;
183
309
  },
184
- resume: () => {
185
- if (state.isActive && state.isPaused) {
186
- loop = setInterval(() => {
187
- }, options.delay ?? defaultDelay);
188
- state.isPaused = false;
310
+ updateRate: (bytesPerSecond) => {
311
+ if (showRate) {
312
+ const currentText = spinner.text;
313
+ const rate = spinner.__reTheme?.rate ?? identity;
314
+ const rateText = rate(`${prettyBytes(bytesPerSecond)}/s`);
315
+ spinner.text = `${currentText} @ ${rateText}`;
189
316
  }
190
317
  },
191
- dispose: () => {
192
- if (state.isActive) {
193
- controls.stop();
194
- }
195
- state.isActive = false;
196
- state.isPaused = false;
318
+ complete: (message) => {
319
+ const elapsed = Date.now() - startTime;
320
+ const timing = prettyMilliseconds(elapsed, { compact: true });
321
+ const dim = spinner.__reTheme?.dim ?? identity;
322
+ const successStyle = spinner.__successStyler ?? identity;
323
+ const successMessage = successStyle(message ?? `${operation} completed successfully`);
324
+ spinner.succeed(`${successMessage} ${dim(`(${timing})`)}`);
197
325
  },
198
- get isCancelled() {
199
- return state.isCancelled;
326
+ error: (error) => {
327
+ const elapsed = Date.now() - startTime;
328
+ const timing = prettyMilliseconds(elapsed, { compact: true });
329
+ const errorMessage = typeof error === "string" ? error : error.message;
330
+ const dim = spinner.__reTheme?.dim ?? identity;
331
+ const failStyle = spinner.__failStyler ?? identity;
332
+ spinner.fail(
333
+ `${failStyle(`${operation} failed: ${errorMessage}`)} ${dim(`(after ${timing})`)}`
334
+ );
200
335
  }
201
336
  };
202
- return controls;
203
337
  }
204
- useSpinner.promise = async (operation, options) => {
205
- const spinner = useSpinner(options).start();
206
- try {
207
- const result = await operation(spinner);
208
- spinner.succeed();
209
- return result;
210
- } catch (error) {
211
- spinner.fail();
212
- throw error;
213
- } finally {
214
- spinner.dispose();
215
- }
216
- };
217
- useSpinner.nested = (parentOptions) => {
218
- const parentSpinner = useSpinner({
219
- ...parentOptions,
220
- silent: true
221
- // Parent is silent, children will show progress
222
- });
338
+ export function createMultiStepSpinner(operationName, steps, options) {
339
+ const startTime = Date.now();
340
+ let currentStepIndex = 0;
341
+ const totalSteps = steps.length;
342
+ const getStepText = (stepIndex) => {
343
+ const step = steps[stepIndex];
344
+ return `${operationName} - ${step} (${stepIndex + 1}/${totalSteps})`;
345
+ };
346
+ const spinner = createSpinner({ text: getStepText(0), ...options }).start();
223
347
  return {
224
- start: () => {
225
- parentSpinner.start();
226
- return {
227
- child: (childOptions) => useSpinner(childOptions),
228
- finish: (success, text) => {
229
- if (success) {
230
- parentSpinner.succeed(text);
231
- } else {
232
- parentSpinner.fail(text);
233
- }
234
- },
235
- dispose: () => parentSpinner.dispose()
236
- };
237
- }
348
+ spinner,
349
+ nextStep: (stepIndex) => {
350
+ if (stepIndex !== void 0) {
351
+ currentStepIndex = Math.min(stepIndex, totalSteps - 1);
352
+ } else {
353
+ currentStepIndex = Math.min(currentStepIndex + 1, totalSteps - 1);
354
+ }
355
+ const text = getStepText(currentStepIndex);
356
+ updateSpinnerText(spinner, text);
357
+ },
358
+ complete: (message) => {
359
+ const elapsed = Date.now() - startTime;
360
+ const timing = prettyMilliseconds(elapsed, { compact: true });
361
+ const dim = spinner.__reTheme?.dim ?? identity;
362
+ const successStyle = spinner.__successStyler ?? identity;
363
+ const successMessage = successStyle(message ?? `${operationName} completed successfully`);
364
+ spinner.succeed(`${successMessage} ${dim(`(${timing})`)}`);
365
+ },
366
+ error: (error, stepIndex) => {
367
+ const elapsed = Date.now() - startTime;
368
+ const timing = prettyMilliseconds(elapsed, { compact: true });
369
+ const errorMessage = typeof error === "string" ? error : error.message;
370
+ const stepInfo = stepIndex !== void 0 ? ` at step ${stepIndex + 1}` : "";
371
+ const dim = spinner.__reTheme?.dim ?? identity;
372
+ const failStyle = spinner.__failStyler ?? identity;
373
+ spinner.fail(
374
+ `${failStyle(`${operationName} failed${stepInfo}: ${errorMessage}`)} ${dim(`(after ${timing})`)}`
375
+ );
376
+ },
377
+ getCurrentStep: () => currentStepIndex
238
378
  };
239
- };
240
- useSpinner.withTiming = async (operation, options) => {
241
- const spinner = useSpinner(options).start();
379
+ }
380
+ export function formatSpinnerTiming(startTime, options) {
381
+ const elapsed = Date.now() - startTime;
382
+ const pmOptions = { compact: !options?.verbose };
383
+ if (options?.verbose !== void 0) pmOptions["verbose"] = options.verbose;
384
+ return prettyMilliseconds(elapsed, pmOptions);
385
+ }
386
+ export function formatSpinnerBytes(bytes, options) {
387
+ return prettyBytes(bytes, options);
388
+ }
389
+ export function formatSpinnerElapsed(elapsed, options) {
390
+ const pmOptions = { compact: !options?.verbose };
391
+ if (options?.verbose !== void 0) pmOptions["verbose"] = options.verbose;
392
+ return prettyMilliseconds(elapsed, pmOptions);
393
+ }
394
+ export function createTransferSpinner(operation, options) {
395
+ const { totalBytes, showRate = true, ...spinnerOptions } = options ?? {};
242
396
  const startTime = Date.now();
243
- try {
244
- const result = await operation(spinner);
245
- const duration = Date.now() - startTime;
246
- spinner.succeed(`${options.successText || options.text} (${duration}ms)`);
247
- return { result, duration };
248
- } catch (error) {
249
- const duration = Date.now() - startTime;
250
- spinner.fail(`${options.failText || options.text} (failed after ${duration}ms)`);
251
- throw error;
252
- } finally {
253
- spinner.dispose();
254
- }
255
- };
397
+ const spinner = createSpinner({ text: operation, ...spinnerOptions }).start();
398
+ return {
399
+ spinner,
400
+ updateBytes: (bytesTransferred, fileName) => {
401
+ let text = operation;
402
+ if (fileName) {
403
+ const info = spinner.__reTheme?.info ?? identity;
404
+ text += ` - ${info(fileName)}`;
405
+ }
406
+ if (totalBytes) {
407
+ const percentage = Math.round(bytesTransferred / totalBytes * 100);
408
+ const bytesColor = spinner.__reTheme?.bytesColor ?? identity;
409
+ const percent = spinner.__reTheme?.percent ?? identity;
410
+ text += ` (${bytesColor(prettyBytes(bytesTransferred))}/${bytesColor(prettyBytes(totalBytes))} - ${percent(`${percentage}%`)})`;
411
+ } else {
412
+ const bytesColor = spinner.__reTheme?.bytesColor ?? identity;
413
+ text += ` (${bytesColor(prettyBytes(bytesTransferred))})`;
414
+ }
415
+ spinner.text = text;
416
+ },
417
+ updateRate: (bytesPerSecond) => {
418
+ if (showRate) {
419
+ const currentText = spinner.text;
420
+ const rate = spinner.__reTheme?.rate ?? identity;
421
+ const rateText = rate(`${prettyBytes(bytesPerSecond)}/s`);
422
+ spinner.text = `${currentText} @ ${rateText}`;
423
+ }
424
+ },
425
+ complete: (message, totalBytesTransferred) => {
426
+ const elapsed = Date.now() - startTime;
427
+ const timing = prettyMilliseconds(elapsed, { compact: true });
428
+ const dim = spinner.__reTheme?.dim ?? identity;
429
+ const successStyle = spinner.__successStyler ?? identity;
430
+ let successMessage = successStyle(message ?? `${operation} completed successfully`);
431
+ if (totalBytesTransferred) {
432
+ const bytesColor = spinner.__reTheme?.bytesColor ?? identity;
433
+ successMessage += ` (${bytesColor(prettyBytes(totalBytesTransferred))})`;
434
+ }
435
+ successMessage += ` ${dim(`in ${timing}`)}`;
436
+ spinner.succeed(successMessage);
437
+ },
438
+ error: (error) => {
439
+ const elapsed = Date.now() - startTime;
440
+ const timing = prettyMilliseconds(elapsed, { compact: true });
441
+ const errorMessage = typeof error === "string" ? error : error.message;
442
+ const dim = spinner.__reTheme?.dim ?? identity;
443
+ const failStyle = spinner.__failStyler ?? identity;
444
+ spinner.fail(
445
+ `${failStyle(`${operation} failed: ${errorMessage}`)} ${dim(`(after ${timing})`)}`
446
+ );
447
+ }
448
+ };
449
+ }
450
+ export const spinners = cliSpinners;
451
+ export { randomSpinner, prettyBytes, prettyMilliseconds };