Skip to content

Commit 5f695f3

Browse files
committed
Fix(@inquirer/expand): [Typescript] Enforce valid keys
1 parent 7fb8aea commit 5f695f3

File tree

3 files changed

+75
-19
lines changed

3 files changed

+75
-19
lines changed

packages/expand/README.md

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -86,13 +86,31 @@ const answer = await expand({
8686

8787
## Options
8888

89-
| Property | Type | Required | Description |
90-
| -------- | ------------------------------------------------------ | -------- | ----------------------------------------------------------------------------------------- |
91-
| message | `string` | yes | The question to ask |
92-
| choices | `Array<{ key: string, name: string, value?: string }>` | yes | Array of the different allowed choices. The `h`/help option is always provided by default |
93-
| default | `string` | no | Default choices to be selected. (value must be one of the choices `key`) |
94-
| expanded | `boolean` | no | Expand the choices by default |
95-
| theme | [See Theming](#Theming) | no | Customize look of the prompt. |
89+
| Property | Type | Required | Description |
90+
| -------- | ----------------------- | -------- | ----------------------------------------------------------------------------------------- |
91+
| message | `string` | yes | The question to ask |
92+
| choices | `Choice[]` | yes | Array of the different allowed choices. The `h`/help option is always provided by default |
93+
| default | `string` | no | Default choices to be selected. (value must be one of the choices `key`) |
94+
| expanded | `boolean` | no | Expand the choices by default |
95+
| theme | [See Theming](#Theming) | no | Customize look of the prompt. |
96+
97+
### `Choice` object
98+
99+
The `Choice` object is typed as
100+
101+
```ts
102+
type Choice<Value> = {
103+
value: Value;
104+
name?: string;
105+
key: string;
106+
};
107+
```
108+
109+
Here's each property:
110+
111+
- `value`: The value is what will be returned by `await expand()`.
112+
- `name`: The string displayed in the choice list. It'll default to the stringify `value`.
113+
- `key`: The input the use must provide to select the choice. Must be a lowercase single alpha-numeric character string.
96114

97115
## Theming
98116

packages/expand/expand.test.mts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,22 @@ import expand from './src/index.mjs';
44

55
const overwriteChoices = [
66
{
7-
key: 'y',
7+
key: 'y' as const,
88
name: 'Overwrite',
99
value: 'overwrite',
1010
},
1111
{
12-
key: 'a',
12+
key: 'a' as const,
1313
name: 'Overwrite this one and all next',
1414
value: 'overwrite_all',
1515
},
1616
{
17-
key: 'd',
17+
key: 'd' as const,
1818
name: 'Show diff',
1919
value: 'diff',
2020
},
2121
{
22-
key: 'x',
22+
key: 'x' as const,
2323
name: 'Abort',
2424
value: 'abort',
2525
},

packages/expand/src/index.mts

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,39 +11,77 @@ import {
1111
import type { PartialDeep } from '@inquirer/type';
1212
import colors from 'yoctocolors-cjs';
1313

14+
type Key =
15+
| 'a'
16+
| 'b'
17+
| 'c'
18+
| 'd'
19+
| 'e'
20+
| 'f'
21+
| 'g'
22+
// | 'h' // Help is excluded since it's a reserved key
23+
| 'i'
24+
| 'j'
25+
| 'k'
26+
| 'l'
27+
| 'm'
28+
| 'n'
29+
| 'o'
30+
| 'p'
31+
| 'q'
32+
| 'r'
33+
| 's'
34+
| 't'
35+
| 'u'
36+
| 'v'
37+
| 'w'
38+
| 'x'
39+
| 'y'
40+
| 'z'
41+
| '0'
42+
| '1'
43+
| '2'
44+
| '3'
45+
| '4'
46+
| '5'
47+
| '6'
48+
| '7'
49+
| '8'
50+
| '9';
51+
1452
type Choice<Value> =
15-
| { key: string; value: Value }
16-
| { key: string; name: string; value: Value };
53+
| { key: Key; value: Value }
54+
| { key: Key; name: string; value: Value };
1755

1856
type NormalizedChoice<Value> = {
1957
value: Value;
2058
name: string;
21-
key: string;
59+
key: Key;
2260
};
2361

2462
type ExpandConfig<
2563
Value,
26-
ChoicesObject = readonly { key: string; name: string }[] | readonly Choice<Value>[],
64+
ChoicesObject = readonly { key: Key; name: string }[] | readonly Choice<Value>[],
2765
> = {
2866
message: string;
29-
choices: ChoicesObject extends readonly { key: string; name: string }[]
67+
choices: ChoicesObject extends readonly { key: Key; name: string }[]
3068
? ChoicesObject
3169
: readonly Choice<Value>[];
32-
default?: string;
70+
default?: Key | 'h';
3371
expanded?: boolean;
3472
theme?: PartialDeep<Theme>;
3573
};
3674

3775
function normalizeChoices<Value>(
38-
choices: readonly { key: string; name: string }[] | readonly Choice<Value>[],
76+
choices: readonly { key: Key; name: string }[] | readonly Choice<Value>[],
3977
): NormalizedChoice<Value>[] {
4078
return choices.map((choice) => {
4179
const name: string = 'name' in choice ? choice.name : String(choice.value);
4280
const value = 'value' in choice ? choice.value : name;
4381
return {
4482
value: value as Value,
4583
name,
46-
key: choice.key.toLowerCase(),
84+
key: choice.key.toLowerCase() as Key,
4785
};
4886
});
4987
}

0 commit comments

Comments
 (0)