Skip to content

Commit 729bbb6

Browse files
dreyfus9243081jnatemoo-re
authored
feat: add customizable spinner cancel and error messages (#278)
Co-authored-by: James Garbutt <43081j@users.noreply.github.com> Co-authored-by: Nate Moore <natemoo-re@users.noreply.github.com>
1 parent 99c3530 commit 729bbb6

File tree

6 files changed

+370
-20
lines changed

6 files changed

+370
-20
lines changed

.changeset/five-chairs-poke.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
---
2+
"@clack/prompts": minor
3+
"@clack/core": minor
4+
---
5+
6+
Add support for customizable spinner cancel and error messages. Users can now customize these messages either per spinner instance or globally via the `updateSettings` function to support multilingual CLIs.
7+
8+
This update also improves the architecture by exposing the core settings to the prompts package, enabling more consistent default message handling across the codebase.
9+
10+
```ts
11+
// Per-instance customization
12+
const spinner = prompts.spinner({
13+
cancelMessage: 'Operación cancelada', // "Operation cancelled" in Spanish
14+
errorMessage: 'Se produjo un error' // "An error occurred" in Spanish
15+
});
16+
17+
// Global customization via updateSettings
18+
prompts.updateSettings({
19+
messages: {
20+
cancel: 'Operación cancelada', // "Operation cancelled" in Spanish
21+
error: 'Se produjo un error' // "An error occurred" in Spanish
22+
}
23+
});
24+
25+
// Settings can now be accessed directly
26+
console.log(prompts.settings.messages.cancel); // "Operación cancelada"
27+
28+
// Direct options take priority over global settings
29+
const spinner = prompts.spinner({
30+
cancelMessage: 'Cancelled', // This will be used instead of the global setting
31+
});
32+
```

packages/core/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@ export { default as SelectPrompt } from './prompts/select.js';
1010
export { default as SelectKeyPrompt } from './prompts/select-key.js';
1111
export { default as TextPrompt } from './prompts/text.js';
1212
export { block, isCancel } from './utils/index.js';
13-
export { updateSettings } from './utils/settings.js';
13+
export { updateSettings, settings } from './utils/settings.js';

packages/core/src/utils/settings.ts

Lines changed: 46 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ export type Action = (typeof actions)[number];
55
interface InternalClackSettings {
66
actions: Set<Action>;
77
aliases: Map<string, Action>;
8+
messages: {
9+
cancel: string;
10+
error: string;
11+
};
812
}
913

1014
export const settings: InternalClackSettings = {
@@ -19,6 +23,10 @@ export const settings: InternalClackSettings = {
1923
// opinionated defaults!
2024
['escape', 'cancel'],
2125
]),
26+
messages: {
27+
cancel: 'Canceled',
28+
error: 'Something went wrong',
29+
},
2230
};
2331

2432
export interface ClackSettings {
@@ -29,27 +37,50 @@ export interface ClackSettings {
2937
* @param aliases - An object that maps aliases to actions
3038
* @default { k: 'up', j: 'down', h: 'left', l: 'right', '\x03': 'cancel', 'escape': 'cancel' }
3139
*/
32-
aliases: Record<string, Action>;
40+
aliases?: Record<string, Action>;
41+
42+
/**
43+
* Custom messages for prompts
44+
*/
45+
messages?: {
46+
/**
47+
* Custom message to display when a spinner is cancelled
48+
* @default "Canceled"
49+
*/
50+
cancel?: string;
51+
/**
52+
* Custom message to display when a spinner encounters an error
53+
* @default "Something went wrong"
54+
*/
55+
error?: string;
56+
};
3357
}
3458

3559
export function updateSettings(updates: ClackSettings) {
36-
for (const _key in updates) {
37-
const key = _key as keyof ClackSettings;
38-
if (!Object.hasOwn(updates, key)) continue;
39-
const value = updates[key];
40-
41-
switch (key) {
42-
case 'aliases': {
43-
for (const alias in value) {
44-
if (!Object.hasOwn(value, alias)) continue;
45-
if (!settings.aliases.has(alias)) {
46-
settings.aliases.set(alias, value[alias]);
47-
}
48-
}
49-
break;
60+
// Handle each property in the updates
61+
if (updates.aliases !== undefined) {
62+
const aliases = updates.aliases;
63+
for (const alias in aliases) {
64+
if (!Object.hasOwn(aliases, alias)) continue;
65+
66+
const action = aliases[alias];
67+
if (!settings.actions.has(action)) continue;
68+
69+
if (!settings.aliases.has(alias)) {
70+
settings.aliases.set(alias, action);
5071
}
5172
}
5273
}
74+
75+
if (updates.messages !== undefined) {
76+
const messages = updates.messages;
77+
if (messages.cancel !== undefined) {
78+
settings.messages.cancel = messages.cancel;
79+
}
80+
if (messages.error !== undefined) {
81+
settings.messages.error = messages.error;
82+
}
83+
}
5384
}
5485

5586
/**

packages/prompts/src/__snapshots__/index.test.ts.snap

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1612,6 +1612,83 @@ exports[`prompts (isCI = false) > spinner > message > sets message for next fram
16121612
]
16131613
`;
16141614

1615+
exports[`prompts (isCI = false) > spinner > process exit handling > prioritizes direct options over global settings 1`] = `
1616+
[
1617+
"[?25l",
1618+
"│
1619+
",
1620+
"■ Spinner cancel message
1621+
",
1622+
"[?25h",
1623+
]
1624+
`;
1625+
1626+
exports[`prompts (isCI = false) > spinner > process exit handling > prioritizes direct options over global settings 2`] = `
1627+
[
1628+
"[?25l",
1629+
"│
1630+
",
1631+
"▲ Spinner error message
1632+
",
1633+
"[?25h",
1634+
]
1635+
`;
1636+
1637+
exports[`prompts (isCI = false) > spinner > process exit handling > uses custom cancel message when provided directly 1`] = `
1638+
[
1639+
"[?25l",
1640+
"│
1641+
",
1642+
"■ Custom cancel message
1643+
",
1644+
"[?25h",
1645+
]
1646+
`;
1647+
1648+
exports[`prompts (isCI = false) > spinner > process exit handling > uses custom error message when provided directly 1`] = `
1649+
[
1650+
"[?25l",
1651+
"│
1652+
",
1653+
"▲ Custom error message
1654+
",
1655+
"[?25h",
1656+
]
1657+
`;
1658+
1659+
exports[`prompts (isCI = false) > spinner > process exit handling > uses default cancel message 1`] = `
1660+
[
1661+
"[?25l",
1662+
"│
1663+
",
1664+
"■ Canceled
1665+
",
1666+
"[?25h",
1667+
]
1668+
`;
1669+
1670+
exports[`prompts (isCI = false) > spinner > process exit handling > uses global custom cancel message from settings 1`] = `
1671+
[
1672+
"[?25l",
1673+
"│
1674+
",
1675+
"■ Global cancel message
1676+
",
1677+
"[?25h",
1678+
]
1679+
`;
1680+
1681+
exports[`prompts (isCI = false) > spinner > process exit handling > uses global custom error message from settings 1`] = `
1682+
[
1683+
"[?25l",
1684+
"│
1685+
",
1686+
"▲ Global error message
1687+
",
1688+
"[?25h",
1689+
]
1690+
`;
1691+
16151692
exports[`prompts (isCI = false) > spinner > start > renders frames at interval 1`] = `
16161693
[
16171694
"[?25l",
@@ -3529,6 +3606,83 @@ exports[`prompts (isCI = true) > spinner > message > sets message for next frame
35293606
]
35303607
`;
35313608

3609+
exports[`prompts (isCI = true) > spinner > process exit handling > prioritizes direct options over global settings 1`] = `
3610+
[
3611+
"[?25l",
3612+
"│
3613+
",
3614+
"■ Spinner cancel message
3615+
",
3616+
"[?25h",
3617+
]
3618+
`;
3619+
3620+
exports[`prompts (isCI = true) > spinner > process exit handling > prioritizes direct options over global settings 2`] = `
3621+
[
3622+
"[?25l",
3623+
"│
3624+
",
3625+
"▲ Spinner error message
3626+
",
3627+
"[?25h",
3628+
]
3629+
`;
3630+
3631+
exports[`prompts (isCI = true) > spinner > process exit handling > uses custom cancel message when provided directly 1`] = `
3632+
[
3633+
"[?25l",
3634+
"│
3635+
",
3636+
"■ Custom cancel message
3637+
",
3638+
"[?25h",
3639+
]
3640+
`;
3641+
3642+
exports[`prompts (isCI = true) > spinner > process exit handling > uses custom error message when provided directly 1`] = `
3643+
[
3644+
"[?25l",
3645+
"│
3646+
",
3647+
"▲ Custom error message
3648+
",
3649+
"[?25h",
3650+
]
3651+
`;
3652+
3653+
exports[`prompts (isCI = true) > spinner > process exit handling > uses default cancel message 1`] = `
3654+
[
3655+
"[?25l",
3656+
"│
3657+
",
3658+
"■ Canceled
3659+
",
3660+
"[?25h",
3661+
]
3662+
`;
3663+
3664+
exports[`prompts (isCI = true) > spinner > process exit handling > uses global custom cancel message from settings 1`] = `
3665+
[
3666+
"[?25l",
3667+
"│
3668+
",
3669+
"■ Global cancel message
3670+
",
3671+
"[?25h",
3672+
]
3673+
`;
3674+
3675+
exports[`prompts (isCI = true) > spinner > process exit handling > uses global custom error message from settings 1`] = `
3676+
[
3677+
"[?25l",
3678+
"│
3679+
",
3680+
"▲ Global error message
3681+
",
3682+
"[?25h",
3683+
]
3684+
`;
3685+
35323686
exports[`prompts (isCI = true) > spinner > start > renders frames at interval 1`] = `
35333687
[
35343688
"[?25l",

0 commit comments

Comments
 (0)