Skip to content

Commit 38019c7

Browse files
authored
feat: Refactor the API for stopping spinners & progress bars (bombshell-dev#405)
1 parent 69508f4 commit 38019c7

File tree

7 files changed

+224
-19
lines changed

7 files changed

+224
-19
lines changed

.changeset/odd-bikes-nail.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
"@clack/prompts": minor
3+
---
4+
5+
Updates the API for stopping spinners and progress bars to be clearer
6+
7+
Previously, both the spinner and progress bar components used a single `stop` method that accepted a code to indicate success, cancellation, or error. This update separates these into distinct methods: `stop()`, `cancel()`, and `error()`:
8+
9+
```diff
10+
const spinner = prompts.spinner();
11+
spinner.start();
12+
13+
// Cancelling a spinner
14+
- spinner.stop(undefined, 1);
15+
+ spinner.cancel();
16+
17+
// Stopping with an error
18+
- spinner.stop(undefined, 2);
19+
+ spinner.error();
20+
```
21+
22+
As before, you can pass a message to each method to customize the output displayed:
23+
24+
```js
25+
spinner.cancel("Operation cancelled by user");
26+
progressBar.error("An error occurred during processing");
27+
```

packages/prompts/src/progress-bar.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ export function progress({
6363
return {
6464
start,
6565
stop: spin.stop,
66+
cancel: spin.cancel,
67+
error: spin.error,
6668
advance,
6769
isCancelled: spin.isCancelled,
6870
message: (msg: string) => advance(0, msg),

packages/prompts/src/spinner.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ export interface SpinnerOptions extends CommonOptions {
2424

2525
export interface SpinnerResult {
2626
start(msg?: string): void;
27-
stop(msg?: string, code?: number): void;
27+
stop(msg?: string): void;
28+
cancel(msg?: string): void;
29+
error(msg?: string): void;
2830
message(msg?: string): void;
2931
readonly isCancelled: boolean;
3032
}
@@ -61,7 +63,7 @@ export const spinner = ({
6163
: (cancelMessage ?? settings.messages.cancel);
6264
isCancelled = code === 1;
6365
if (isSpinnerActive) {
64-
stop(msg, code);
66+
_stop(msg, code);
6567
if (isCancelled && typeof onCancel === 'function') {
6668
onCancel();
6769
}
@@ -163,7 +165,7 @@ export const spinner = ({
163165
}, delay);
164166
};
165167

166-
const stop = (msg = '', code = 0): void => {
168+
const _stop = (msg = '', code = 0): void => {
167169
if (!isSpinnerActive) return;
168170
isSpinnerActive = false;
169171
clearInterval(loop);
@@ -184,6 +186,10 @@ export const spinner = ({
184186
unblock();
185187
};
186188

189+
const stop = (msg = ''): void => _stop(msg, 0);
190+
const cancel = (msg = ''): void => _stop(msg, 1);
191+
const error = (msg = ''): void => _stop(msg, 2);
192+
187193
const message = (msg = ''): void => {
188194
_message = removeTrailingDots(msg ?? _message);
189195
};
@@ -192,6 +198,8 @@ export const spinner = ({
192198
start,
193199
stop,
194200
message,
201+
cancel,
202+
error,
195203
get isCancelled() {
196204
return isCancelled;
197205
},

packages/prompts/test/__snapshots__/progress-bar.test.ts.snap

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ exports[`prompts - progress (isCI = false) > start > renders timer when indicato
125125
]
126126
`;
127127
128-
exports[`prompts - progress (isCI = false) > stop > renders cancel symbol if code = 1 1`] = `
128+
exports[`prompts - progress (isCI = false) > stop > renders cancel symbol when calling cancel() 1`] = `
129129
[
130130
"<cursor.hide>",
131131
"│
@@ -139,7 +139,7 @@ exports[`prompts - progress (isCI = false) > stop > renders cancel symbol if cod
139139
]
140140
`;
141141
142-
exports[`prompts - progress (isCI = false) > stop > renders error symbol if code > 1 1`] = `
142+
exports[`prompts - progress (isCI = false) > stop > renders error symbol when calling error() 1`] = `
143143
[
144144
"<cursor.hide>",
145145
"│
@@ -167,6 +167,34 @@ exports[`prompts - progress (isCI = false) > stop > renders message 1`] = `
167167
]
168168
`;
169169
170+
exports[`prompts - progress (isCI = false) > stop > renders message when cancelling 1`] = `
171+
[
172+
"<cursor.hide>",
173+
"│
174+
",
175+
"◒ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ",
176+
"<cursor.left count=1>",
177+
"<erase.down>",
178+
"■ cancelled :-(
179+
",
180+
"<cursor.show>",
181+
]
182+
`;
183+
184+
exports[`prompts - progress (isCI = false) > stop > renders message when erroring 1`] = `
185+
[
186+
"<cursor.hide>",
187+
"│
188+
",
189+
"◒ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ",
190+
"<cursor.left count=1>",
191+
"<erase.down>",
192+
"▲ FATAL ERROR!
193+
",
194+
"<cursor.show>",
195+
]
196+
`;
197+
170198
exports[`prompts - progress (isCI = false) > stop > renders message without removing dots 1`] = `
171199
[
172200
"<cursor.hide>",
@@ -382,7 +410,7 @@ exports[`prompts - progress (isCI = true) > start > renders timer when indicator
382410
]
383411
`;
384412
385-
exports[`prompts - progress (isCI = true) > stop > renders cancel symbol if code = 1 1`] = `
413+
exports[`prompts - progress (isCI = true) > stop > renders cancel symbol when calling cancel() 1`] = `
386414
[
387415
"<cursor.hide>",
388416
"│
@@ -398,7 +426,7 @@ exports[`prompts - progress (isCI = true) > stop > renders cancel symbol if code
398426
]
399427
`;
400428
401-
exports[`prompts - progress (isCI = true) > stop > renders error symbol if code > 1 1`] = `
429+
exports[`prompts - progress (isCI = true) > stop > renders error symbol when calling error() 1`] = `
402430
[
403431
"<cursor.hide>",
404432
"│
@@ -430,6 +458,38 @@ exports[`prompts - progress (isCI = true) > stop > renders message 1`] = `
430458
]
431459
`;
432460
461+
exports[`prompts - progress (isCI = true) > stop > renders message when cancelling 1`] = `
462+
[
463+
"<cursor.hide>",
464+
"│
465+
",
466+
"◒ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ...",
467+
"
468+
",
469+
"<cursor.left count=1>",
470+
"<erase.down>",
471+
"■ cancelled :-(
472+
",
473+
"<cursor.show>",
474+
]
475+
`;
476+
477+
exports[`prompts - progress (isCI = true) > stop > renders message when erroring 1`] = `
478+
[
479+
"<cursor.hide>",
480+
"│
481+
",
482+
"◒ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ...",
483+
"
484+
",
485+
"<cursor.left count=1>",
486+
"<erase.down>",
487+
"▲ FATAL ERROR!
488+
",
489+
"<cursor.show>",
490+
]
491+
`;
492+
433493
exports[`prompts - progress (isCI = true) > stop > renders message without removing dots 1`] = `
434494
[
435495
"<cursor.hide>",

packages/prompts/test/__snapshots__/spinner.test.ts.snap

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -461,7 +461,7 @@ exports[`spinner (isCI = false) > start > renders timer when indicator is "timer
461461
]
462462
`;
463463
464-
exports[`spinner (isCI = false) > stop > renders cancel symbol if code = 1 1`] = `
464+
exports[`spinner (isCI = false) > stop > renders cancel symbol when calling cancel() 1`] = `
465465
[
466466
"<cursor.hide>",
467467
"│
@@ -475,7 +475,7 @@ exports[`spinner (isCI = false) > stop > renders cancel symbol if code = 1 1`] =
475475
]
476476
`;
477477
478-
exports[`spinner (isCI = false) > stop > renders error symbol if code > 1 1`] = `
478+
exports[`spinner (isCI = false) > stop > renders error symbol when calling error() 1`] = `
479479
[
480480
"<cursor.hide>",
481481
"│
@@ -503,6 +503,34 @@ exports[`spinner (isCI = false) > stop > renders message 1`] = `
503503
]
504504
`;
505505
506+
exports[`spinner (isCI = false) > stop > renders message when cancelling 1`] = `
507+
[
508+
"<cursor.hide>",
509+
"│
510+
",
511+
"◒ ",
512+
"<cursor.left count=1>",
513+
"<erase.down>",
514+
"■ too dizzy — spinning cancelled
515+
",
516+
"<cursor.show>",
517+
]
518+
`;
519+
520+
exports[`spinner (isCI = false) > stop > renders message when erroring 1`] = `
521+
[
522+
"<cursor.hide>",
523+
"│
524+
",
525+
"◒ ",
526+
"<cursor.left count=1>",
527+
"<erase.down>",
528+
"▲ error: spun too fast!
529+
",
530+
"<cursor.show>",
531+
]
532+
`;
533+
506534
exports[`spinner (isCI = false) > stop > renders message without removing dots 1`] = `
507535
[
508536
"<cursor.hide>",
@@ -789,7 +817,7 @@ exports[`spinner (isCI = true) > start > renders timer when indicator is "timer"
789817
]
790818
`;
791819
792-
exports[`spinner (isCI = true) > stop > renders cancel symbol if code = 1 1`] = `
820+
exports[`spinner (isCI = true) > stop > renders cancel symbol when calling cancel() 1`] = `
793821
[
794822
"<cursor.hide>",
795823
"│
@@ -805,7 +833,7 @@ exports[`spinner (isCI = true) > stop > renders cancel symbol if code = 1 1`] =
805833
]
806834
`;
807835
808-
exports[`spinner (isCI = true) > stop > renders error symbol if code > 1 1`] = `
836+
exports[`spinner (isCI = true) > stop > renders error symbol when calling error() 1`] = `
809837
[
810838
"<cursor.hide>",
811839
"│
@@ -837,6 +865,38 @@ exports[`spinner (isCI = true) > stop > renders message 1`] = `
837865
]
838866
`;
839867
868+
exports[`spinner (isCI = true) > stop > renders message when cancelling 1`] = `
869+
[
870+
"<cursor.hide>",
871+
"│
872+
",
873+
"◒ ...",
874+
"
875+
",
876+
"<cursor.left count=1>",
877+
"<erase.down>",
878+
"■ too dizzy — spinning cancelled
879+
",
880+
"<cursor.show>",
881+
]
882+
`;
883+
884+
exports[`spinner (isCI = true) > stop > renders message when erroring 1`] = `
885+
[
886+
"<cursor.hide>",
887+
"│
888+
",
889+
"◒ ...",
890+
"
891+
",
892+
"<cursor.left count=1>",
893+
"<erase.down>",
894+
"▲ error: spun too fast!
895+
",
896+
"<cursor.show>",
897+
]
898+
`;
899+
840900
exports[`spinner (isCI = true) > stop > renders message without removing dots 1`] = `
841901
[
842902
"<cursor.hide>",

packages/prompts/test/progress-bar.test.ts

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,26 +87,26 @@ describe.each(['true', 'false'])('prompts - progress (isCI = %s)', (isCI) => {
8787
expect(output.buffer).toMatchSnapshot();
8888
});
8989

90-
test('renders cancel symbol if code = 1', () => {
90+
test('renders cancel symbol when calling cancel()', () => {
9191
const result = prompts.progress({ output });
9292

9393
result.start();
9494

9595
vi.advanceTimersByTime(80);
9696

97-
result.stop('', 1);
97+
result.cancel();
9898

9999
expect(output.buffer).toMatchSnapshot();
100100
});
101101

102-
test('renders error symbol if code > 1', () => {
102+
test('renders error symbol when calling error()', () => {
103103
const result = prompts.progress({ output });
104104

105105
result.start();
106106

107107
vi.advanceTimersByTime(80);
108108

109-
result.stop('', 2);
109+
result.error();
110110

111111
expect(output.buffer).toMatchSnapshot();
112112
});
@@ -134,6 +134,30 @@ describe.each(['true', 'false'])('prompts - progress (isCI = %s)', (isCI) => {
134134

135135
expect(output.buffer).toMatchSnapshot();
136136
});
137+
138+
test('renders message when cancelling', () => {
139+
const result = prompts.progress({ output });
140+
141+
result.start();
142+
143+
vi.advanceTimersByTime(80);
144+
145+
result.cancel('cancelled :-(');
146+
147+
expect(output.buffer).toMatchSnapshot();
148+
});
149+
150+
test('renders message when erroring', () => {
151+
const result = prompts.progress({ output });
152+
153+
result.start();
154+
155+
vi.advanceTimersByTime(80);
156+
157+
result.error('FATAL ERROR!');
158+
159+
expect(output.buffer).toMatchSnapshot();
160+
});
137161
});
138162

139163
describe('message', () => {

0 commit comments

Comments
 (0)