From 5f695f3def75355a62e0dd7f09f13836e4047b6f Mon Sep 17 00:00:00 2001 From: Simon Boudrias Date: Sun, 1 Sep 2024 16:10:13 -0400 Subject: [PATCH] Fix(@inquirer/expand): [Typescript] Enforce valid keys --- packages/expand/README.md | 32 ++++++++++++++----- packages/expand/expand.test.mts | 8 ++--- packages/expand/src/index.mts | 54 ++++++++++++++++++++++++++++----- 3 files changed, 75 insertions(+), 19 deletions(-) diff --git a/packages/expand/README.md b/packages/expand/README.md index 0273fc917..a62195dc5 100644 --- a/packages/expand/README.md +++ b/packages/expand/README.md @@ -86,13 +86,31 @@ const answer = await expand({ ## Options -| Property | Type | Required | Description | -| -------- | ------------------------------------------------------ | -------- | ----------------------------------------------------------------------------------------- | -| message | `string` | yes | The question to ask | -| choices | `Array<{ key: string, name: string, value?: string }>` | yes | Array of the different allowed choices. The `h`/help option is always provided by default | -| default | `string` | no | Default choices to be selected. (value must be one of the choices `key`) | -| expanded | `boolean` | no | Expand the choices by default | -| theme | [See Theming](#Theming) | no | Customize look of the prompt. | +| Property | Type | Required | Description | +| -------- | ----------------------- | -------- | ----------------------------------------------------------------------------------------- | +| message | `string` | yes | The question to ask | +| choices | `Choice[]` | yes | Array of the different allowed choices. The `h`/help option is always provided by default | +| default | `string` | no | Default choices to be selected. (value must be one of the choices `key`) | +| expanded | `boolean` | no | Expand the choices by default | +| theme | [See Theming](#Theming) | no | Customize look of the prompt. | + +### `Choice` object + +The `Choice` object is typed as + +```ts +type Choice = { + value: Value; + name?: string; + key: string; +}; +``` + +Here's each property: + +- `value`: The value is what will be returned by `await expand()`. +- `name`: The string displayed in the choice list. It'll default to the stringify `value`. +- `key`: The input the use must provide to select the choice. Must be a lowercase single alpha-numeric character string. ## Theming diff --git a/packages/expand/expand.test.mts b/packages/expand/expand.test.mts index 6b098b5a4..c33b35856 100644 --- a/packages/expand/expand.test.mts +++ b/packages/expand/expand.test.mts @@ -4,22 +4,22 @@ import expand from './src/index.mjs'; const overwriteChoices = [ { - key: 'y', + key: 'y' as const, name: 'Overwrite', value: 'overwrite', }, { - key: 'a', + key: 'a' as const, name: 'Overwrite this one and all next', value: 'overwrite_all', }, { - key: 'd', + key: 'd' as const, name: 'Show diff', value: 'diff', }, { - key: 'x', + key: 'x' as const, name: 'Abort', value: 'abort', }, diff --git a/packages/expand/src/index.mts b/packages/expand/src/index.mts index 8a2633f6d..d1ec68aef 100644 --- a/packages/expand/src/index.mts +++ b/packages/expand/src/index.mts @@ -11,31 +11,69 @@ import { import type { PartialDeep } from '@inquirer/type'; import colors from 'yoctocolors-cjs'; +type Key = + | 'a' + | 'b' + | 'c' + | 'd' + | 'e' + | 'f' + | 'g' + // | 'h' // Help is excluded since it's a reserved key + | 'i' + | 'j' + | 'k' + | 'l' + | 'm' + | 'n' + | 'o' + | 'p' + | 'q' + | 'r' + | 's' + | 't' + | 'u' + | 'v' + | 'w' + | 'x' + | 'y' + | 'z' + | '0' + | '1' + | '2' + | '3' + | '4' + | '5' + | '6' + | '7' + | '8' + | '9'; + type Choice = - | { key: string; value: Value } - | { key: string; name: string; value: Value }; + | { key: Key; value: Value } + | { key: Key; name: string; value: Value }; type NormalizedChoice = { value: Value; name: string; - key: string; + key: Key; }; type ExpandConfig< Value, - ChoicesObject = readonly { key: string; name: string }[] | readonly Choice[], + ChoicesObject = readonly { key: Key; name: string }[] | readonly Choice[], > = { message: string; - choices: ChoicesObject extends readonly { key: string; name: string }[] + choices: ChoicesObject extends readonly { key: Key; name: string }[] ? ChoicesObject : readonly Choice[]; - default?: string; + default?: Key | 'h'; expanded?: boolean; theme?: PartialDeep; }; function normalizeChoices( - choices: readonly { key: string; name: string }[] | readonly Choice[], + choices: readonly { key: Key; name: string }[] | readonly Choice[], ): NormalizedChoice[] { return choices.map((choice) => { const name: string = 'name' in choice ? choice.name : String(choice.value); @@ -43,7 +81,7 @@ function normalizeChoices( return { value: value as Value, name, - key: choice.key.toLowerCase(), + key: choice.key.toLowerCase() as Key, }; }); }