@seamapi/cli 0.15.0 → 0.17.0
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.
- package/bin/cli.js +7 -4
- package/bin/cli.js.map +1 -1
- package/lib/interact-for-action-attempt-poll.js +3 -5
- package/lib/interact-for-action-attempt-poll.js.map +1 -1
- package/lib/interact-for-array.js +11 -18
- package/lib/interact-for-array.js.map +1 -1
- package/lib/interact-for-blueprint-object.js +19 -31
- package/lib/interact-for-blueprint-object.js.map +1 -1
- package/lib/interact-for-command-selection.js +6 -11
- package/lib/interact-for-command-selection.js.map +1 -1
- package/lib/interact-for-custom-metadata.js +10 -21
- package/lib/interact-for-custom-metadata.js.map +1 -1
- package/lib/interact-for-login.js +3 -5
- package/lib/interact-for-login.js.map +1 -1
- package/lib/interact-for-resource.js +6 -6
- package/lib/interact-for-resource.js.map +1 -1
- package/lib/interact-for-server-selection.js +9 -16
- package/lib/interact-for-server-selection.js.map +1 -1
- package/lib/interact-for-timestamp.d.ts +1 -1
- package/lib/interact-for-timestamp.js +14 -5
- package/lib/interact-for-timestamp.js.map +1 -1
- package/lib/interact-for-use-remote-api-defs.js +14 -18
- package/lib/interact-for-use-remote-api-defs.js.map +1 -1
- package/lib/interact-for-workspace-id.d.ts +1 -1
- package/lib/interact-for-workspace-id.js +8 -11
- package/lib/interact-for-workspace-id.js.map +1 -1
- package/lib/util/prompt.d.ts +44 -6
- package/lib/util/prompt.js +87 -10
- package/lib/util/prompt.js.map +1 -1
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +2 -3
- package/src/bin/cli.ts +12 -4
- package/src/lib/interact-for-action-attempt-poll.ts +3 -5
- package/src/lib/interact-for-array.ts +11 -19
- package/src/lib/interact-for-blueprint-object.ts +35 -46
- package/src/lib/interact-for-command-selection.ts +6 -12
- package/src/lib/interact-for-custom-metadata.ts +10 -22
- package/src/lib/interact-for-login.ts +3 -5
- package/src/lib/interact-for-resource.ts +6 -7
- package/src/lib/interact-for-server-selection.ts +10 -17
- package/src/lib/interact-for-timestamp.ts +14 -5
- package/src/lib/interact-for-use-remote-api-defs.ts +14 -18
- package/src/lib/interact-for-workspace-id.ts +8 -12
- package/src/lib/util/prompt.ts +151 -15
- package/src/lib/version.ts +1 -1
|
@@ -2,7 +2,7 @@ import { isDeepStrictEqual as isEqual } from 'node:util'
|
|
|
2
2
|
|
|
3
3
|
import type { ContextHelpers } from './types.js'
|
|
4
4
|
import { NonInteractiveError } from './util/cli-args.js'
|
|
5
|
-
import {
|
|
5
|
+
import { promptAutocomplete } from './util/prompt.js'
|
|
6
6
|
|
|
7
7
|
const uniqBy = <T>(items: T[], keyOf: (item: T) => unknown): T[] => {
|
|
8
8
|
const seen = new Set<unknown>()
|
|
@@ -92,28 +92,22 @@ export async function interactForCommandSelection(
|
|
|
92
92
|
|
|
93
93
|
const commandPathStr = commandPath.join('/').replace(/-/g, '_')
|
|
94
94
|
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
type: 'autocomplete',
|
|
95
|
+
const selectedCommand = await promptAutocomplete({
|
|
96
|
+
message: `Select a command: /${commandPathStr}`,
|
|
98
97
|
choices: [
|
|
99
98
|
...possibleCommands.map((cmd) => ({
|
|
100
|
-
|
|
99
|
+
label:
|
|
101
100
|
cmd?.[commandPath.length] ?? `[Call /${commandPathStr} Directly]`,
|
|
102
101
|
value: cmd?.[commandPath.length] ?? '<none>',
|
|
103
102
|
})),
|
|
104
103
|
].sort((a, b) => ergonomicSort(a.value, b.value)),
|
|
105
|
-
message: `Select a command: /${commandPathStr}`,
|
|
106
104
|
})
|
|
107
105
|
|
|
108
|
-
if (
|
|
109
|
-
throw new Error('Bailed')
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if (res?.Command === '<none>') {
|
|
106
|
+
if (selectedCommand === '<none>') {
|
|
113
107
|
return commandPath
|
|
114
108
|
}
|
|
115
109
|
|
|
116
|
-
const newCommandPath = [...commandPath,
|
|
110
|
+
const newCommandPath = [...commandPath, selectedCommand]
|
|
117
111
|
|
|
118
112
|
const fullCommand = possibleCommands.find((cmd) =>
|
|
119
113
|
isEqual(newCommandPath, cmd),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getOutput } from './output/get-output.js'
|
|
2
|
-
import {
|
|
2
|
+
import { promptSelect, promptText } from './util/prompt.js'
|
|
3
3
|
|
|
4
4
|
// Structurally the CustomMetadata of @seamapi/types, spelled out here so the
|
|
5
5
|
// published declarations do not depend on a development-only package.
|
|
@@ -31,29 +31,21 @@ export const interactForCustomMetadata = async (
|
|
|
31
31
|
do {
|
|
32
32
|
displayCurrentCustomMetadata()
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
type: 'select',
|
|
36
|
-
name: 'action',
|
|
34
|
+
action = await promptSelect({
|
|
37
35
|
message: 'Choose an action:',
|
|
38
36
|
choices: [
|
|
39
|
-
{
|
|
40
|
-
{
|
|
41
|
-
{
|
|
37
|
+
{ label: 'Add an item to params', value: 'add' },
|
|
38
|
+
{ label: 'Remove an item from params', value: 'remove' },
|
|
39
|
+
{ label: 'Finish editing params', value: 'done' },
|
|
42
40
|
],
|
|
43
41
|
})
|
|
44
42
|
|
|
45
|
-
action = response.action
|
|
46
|
-
|
|
47
43
|
if (action === 'add') {
|
|
48
|
-
const
|
|
49
|
-
type: 'text',
|
|
50
|
-
name: 'newKey',
|
|
44
|
+
const newKey = await promptText({
|
|
51
45
|
message: 'Enter a key to add or edit:',
|
|
52
46
|
})
|
|
53
47
|
|
|
54
|
-
let
|
|
55
|
-
type: 'text',
|
|
56
|
-
name: 'newValue',
|
|
48
|
+
let newValue: string | boolean = await promptText({
|
|
57
49
|
message: 'Enter the new value to add or edit (or null to delete):',
|
|
58
50
|
})
|
|
59
51
|
if (newKey) {
|
|
@@ -67,21 +59,17 @@ export const interactForCustomMetadata = async (
|
|
|
67
59
|
}
|
|
68
60
|
}
|
|
69
61
|
} else if (action === 'remove') {
|
|
70
|
-
const
|
|
71
|
-
type: 'select',
|
|
72
|
-
name: 'customKeyToRemove',
|
|
62
|
+
const customKeyToRemove = await promptSelect({
|
|
73
63
|
message: 'Choose a key-value pair to remove from params:',
|
|
74
64
|
choices: Object.keys(updatedCustomMetadata).map((customMetadataKey) => {
|
|
75
65
|
return {
|
|
76
|
-
|
|
66
|
+
label: `${customMetadataKey}: ${updatedCustomMetadata[customMetadataKey]}`,
|
|
77
67
|
value: customMetadataKey,
|
|
78
68
|
}
|
|
79
69
|
}),
|
|
80
70
|
})
|
|
81
71
|
|
|
82
|
-
|
|
83
|
-
delete customMetadata[customKeyToRemove]
|
|
84
|
-
}
|
|
72
|
+
delete customMetadata[customKeyToRemove]
|
|
85
73
|
}
|
|
86
74
|
} while (action !== 'done')
|
|
87
75
|
|
|
@@ -6,7 +6,7 @@ import { assertEnvVarUnset, getTokenFromEnv, tokenEnvVar } from './env.js'
|
|
|
6
6
|
import { getServer } from './get-server.js'
|
|
7
7
|
import { interactForWorkspaceId } from './interact-for-workspace-id.js'
|
|
8
8
|
import { getOutput } from './output/get-output.js'
|
|
9
|
-
import {
|
|
9
|
+
import { promptText } from './util/prompt.js'
|
|
10
10
|
import { withLoading } from './util/with-loading.js'
|
|
11
11
|
import { validateToken } from './validate-token.js'
|
|
12
12
|
|
|
@@ -32,12 +32,10 @@ export const interactForLogin = async () => {
|
|
|
32
32
|
),
|
|
33
33
|
)
|
|
34
34
|
|
|
35
|
-
const
|
|
36
|
-
name: 'pat',
|
|
37
|
-
type: 'text',
|
|
35
|
+
const pat = await promptText({
|
|
38
36
|
message: 'Personal Access Token:',
|
|
39
37
|
})
|
|
40
|
-
const token = pat
|
|
38
|
+
const token = pat.trim()
|
|
41
39
|
|
|
42
40
|
if (!token) {
|
|
43
41
|
throw new Error('No token provided')
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { promptAutocomplete } from './util/prompt.js'
|
|
2
2
|
import { withLoading } from './util/with-loading.js'
|
|
3
3
|
|
|
4
4
|
export interface ResourceChoice {
|
|
@@ -22,12 +22,11 @@ export const interactForResource = async <Resource>({
|
|
|
22
22
|
`Fetching ${resourceName.replace(/_/g, ' ')}s...`,
|
|
23
23
|
fetchResources,
|
|
24
24
|
)
|
|
25
|
-
|
|
26
|
-
name: 'resourceId',
|
|
27
|
-
type: 'autocomplete',
|
|
25
|
+
return await promptAutocomplete({
|
|
28
26
|
message,
|
|
29
|
-
choices: resources.map(
|
|
27
|
+
choices: resources.map((resource) => {
|
|
28
|
+
const { title, value, description } = toChoice(resource)
|
|
29
|
+
return { label: title, value, hint: description }
|
|
30
|
+
}),
|
|
30
31
|
})
|
|
31
|
-
|
|
32
|
-
return resourceId as string
|
|
33
32
|
}
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
} from './env.js'
|
|
11
11
|
import { getServer } from './get-server.js'
|
|
12
12
|
import { getOutput } from './output/get-output.js'
|
|
13
|
-
import {
|
|
13
|
+
import { promptAutocomplete, promptText } from './util/prompt.js'
|
|
14
14
|
|
|
15
15
|
export async function interactForServerSelection() {
|
|
16
16
|
assertEnvVarUnset(endpointEnvVar, getEndpointFromEnv(), 'select a server')
|
|
@@ -21,26 +21,19 @@ export async function interactForServerSelection() {
|
|
|
21
21
|
'https://fakeseamconnect.seam.vc',
|
|
22
22
|
]
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
choices: servers.map((server) => ({ title: server, value: server })),
|
|
30
|
-
},
|
|
31
|
-
])
|
|
24
|
+
// Searchable, as selecting a device or a command is.
|
|
25
|
+
const server = await promptAutocomplete({
|
|
26
|
+
message: 'Select a server:',
|
|
27
|
+
choices: servers.map((server) => ({ label: server, value: server })),
|
|
28
|
+
})
|
|
32
29
|
|
|
33
30
|
const config = getConfigStore()
|
|
34
31
|
const output = getOutput()
|
|
35
32
|
if (server === servers[2]) {
|
|
36
|
-
let
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
message:
|
|
41
|
-
'You can input a custom server URL or leave this field empty to use a new fakeserver.',
|
|
42
|
-
},
|
|
43
|
-
])
|
|
33
|
+
let userUrlSeed = await promptText({
|
|
34
|
+
message:
|
|
35
|
+
'You can input a custom server URL or leave this field empty to use a new fakeserver.',
|
|
36
|
+
})
|
|
44
37
|
|
|
45
38
|
if (userUrlSeed.trim().length === 0) {
|
|
46
39
|
userUrlSeed = randomBytes(5).toString('hex')
|
|
@@ -1,10 +1,19 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { promptText } from './util/prompt.js'
|
|
2
|
+
|
|
2
3
|
export const interactForTimestamp = async () => {
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
type: 'date',
|
|
4
|
+
const now = new Date().toISOString()
|
|
5
|
+
const timestamp = await promptText({
|
|
6
6
|
message: 'Enter a timestamp:',
|
|
7
|
+
placeholder: now,
|
|
8
|
+
defaultValue: now,
|
|
9
|
+
validate: (value) => {
|
|
10
|
+
if (value == null || value === '') return undefined
|
|
11
|
+
if (Number.isNaN(new Date(value).getTime())) {
|
|
12
|
+
return `Enter a valid timestamp, e.g. ${now}`
|
|
13
|
+
}
|
|
14
|
+
return undefined
|
|
15
|
+
},
|
|
7
16
|
})
|
|
8
17
|
|
|
9
|
-
return timestamp.toISOString()
|
|
18
|
+
return new Date(timestamp).toISOString()
|
|
10
19
|
}
|
|
@@ -1,25 +1,21 @@
|
|
|
1
1
|
import { getConfigStore } from './config/index.js'
|
|
2
2
|
import { getOutput } from './output/get-output.js'
|
|
3
|
-
import {
|
|
3
|
+
import { promptSelect } from './util/prompt.js'
|
|
4
4
|
|
|
5
5
|
export async function interactForUseRemoteApiDefs() {
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
},
|
|
20
|
-
],
|
|
21
|
-
},
|
|
22
|
-
])
|
|
6
|
+
const useRemoteApiDefs = await promptSelect({
|
|
7
|
+
message: 'Always use remote API Definitions?',
|
|
8
|
+
choices: [
|
|
9
|
+
{
|
|
10
|
+
label: 'Yes',
|
|
11
|
+
value: true,
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
label: 'No',
|
|
15
|
+
value: false,
|
|
16
|
+
},
|
|
17
|
+
],
|
|
18
|
+
})
|
|
23
19
|
|
|
24
20
|
const config = getConfigStore()
|
|
25
21
|
config.set('use_remote_api_defs', useRemoteApiDefs)
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
} from './env.js'
|
|
9
9
|
import { getSeamMultiWorkspace } from './get-seam.js'
|
|
10
10
|
import { getServer } from './get-server.js'
|
|
11
|
-
import {
|
|
11
|
+
import { promptAutocomplete } from './util/prompt.js'
|
|
12
12
|
import { withLoading } from './util/with-loading.js'
|
|
13
13
|
|
|
14
14
|
export const interactForWorkspaceId = async (personalAccessToken?: string) => {
|
|
@@ -29,21 +29,17 @@ export const interactForWorkspaceId = async (personalAccessToken?: string) => {
|
|
|
29
29
|
const workspaces = await withLoading('Fetching workspaces...', () =>
|
|
30
30
|
seam.workspaces.list(),
|
|
31
31
|
)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
// Searchable, as selecting a device or a command is: an account may have
|
|
33
|
+
// more workspaces than fit on a screen.
|
|
34
|
+
const workspaceId = await promptAutocomplete<string>({
|
|
35
35
|
message: 'Select a workspace:',
|
|
36
36
|
choices: workspaces.map((workspace: any) => ({
|
|
37
|
-
|
|
37
|
+
label: workspace.name,
|
|
38
38
|
value: workspace.workspace_id,
|
|
39
|
-
|
|
39
|
+
hint: workspace.workspace_id,
|
|
40
40
|
})),
|
|
41
41
|
})
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
return workspaceId
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
throw new Error('Bailed')
|
|
43
|
+
config.set('current_workspace_id', workspaceId)
|
|
44
|
+
return workspaceId
|
|
49
45
|
}
|
package/src/lib/util/prompt.ts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {
|
|
2
|
+
autocomplete,
|
|
3
|
+
autocompleteMultiselect,
|
|
4
|
+
confirm,
|
|
5
|
+
isCancel,
|
|
6
|
+
type Option,
|
|
7
|
+
select,
|
|
8
|
+
text,
|
|
9
|
+
} from '@clack/prompts'
|
|
2
10
|
|
|
3
11
|
import { NonInteractiveError } from './cli-args.js'
|
|
4
12
|
|
|
@@ -13,26 +21,154 @@ import { NonInteractiveError } from './cli-args.js'
|
|
|
13
21
|
export const canPrompt = (): boolean =>
|
|
14
22
|
process.stdin.isTTY === true && process.stderr.isTTY === true
|
|
15
23
|
|
|
16
|
-
/**
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
/** The user dismissed a prompt with ctrl-c or escape instead of answering. */
|
|
25
|
+
export class PromptCancelledError extends Error {
|
|
26
|
+
constructor() {
|
|
27
|
+
super('Cancelled')
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface PromptChoice<Value> {
|
|
32
|
+
label: string
|
|
33
|
+
value: Value
|
|
34
|
+
hint?: string | undefined
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const ensureInteractive = (): void => {
|
|
26
38
|
if (!canPrompt()) {
|
|
27
39
|
throw new NonInteractiveError(
|
|
28
40
|
'Cannot prompt without a terminal: pass the missing arguments, or pipe them in as JSON',
|
|
29
41
|
)
|
|
30
42
|
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const unwrap = <Value>(value: Value | symbol): Value => {
|
|
46
|
+
if (isCancel(value)) throw new PromptCancelledError()
|
|
47
|
+
return value as Value
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Prompts are rendered to stderr: a selection is not a command result,
|
|
51
|
+
// so it must not end up in stdout when the CLI is piped.
|
|
52
|
+
const output = process.stderr
|
|
53
|
+
|
|
54
|
+
const toOptions = <Value>(
|
|
55
|
+
choices: Array<PromptChoice<Value>>,
|
|
56
|
+
): Array<Option<Value>> =>
|
|
57
|
+
choices.map(
|
|
58
|
+
({ label, value, hint }) =>
|
|
59
|
+
(hint === undefined
|
|
60
|
+
? { label, value }
|
|
61
|
+
: { label, value, hint }) as Option<Value>,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
export const promptText = async (options: {
|
|
65
|
+
message: string
|
|
66
|
+
placeholder?: string
|
|
67
|
+
defaultValue?: string
|
|
68
|
+
validate?: (value: string | undefined) => string | undefined
|
|
69
|
+
}): Promise<string> => {
|
|
70
|
+
ensureInteractive()
|
|
71
|
+
return unwrap(await text({ ...options, output }))
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export const promptNumber = async (options: {
|
|
75
|
+
message: string
|
|
76
|
+
validate?: (value: number) => string | undefined
|
|
77
|
+
}): Promise<number> => {
|
|
78
|
+
ensureInteractive()
|
|
79
|
+
const value = unwrap(
|
|
80
|
+
await text({
|
|
81
|
+
message: options.message,
|
|
82
|
+
validate: (value) => {
|
|
83
|
+
if (value == null || value.trim() === '') return 'Enter a number'
|
|
84
|
+
const parsed = Number(value)
|
|
85
|
+
if (Number.isNaN(parsed)) return 'Enter a number'
|
|
86
|
+
return options.validate?.(parsed)
|
|
87
|
+
},
|
|
88
|
+
output,
|
|
89
|
+
}),
|
|
90
|
+
)
|
|
91
|
+
return Number(value)
|
|
92
|
+
}
|
|
31
93
|
|
|
32
|
-
|
|
94
|
+
export const promptConfirm = async (options: {
|
|
95
|
+
message: string
|
|
96
|
+
initialValue?: boolean
|
|
97
|
+
active?: string
|
|
98
|
+
inactive?: string
|
|
99
|
+
}): Promise<boolean> => {
|
|
100
|
+
ensureInteractive()
|
|
101
|
+
return unwrap(await confirm({ ...options, output }))
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export const promptSelect = async <Value>(options: {
|
|
105
|
+
message: string
|
|
106
|
+
choices: Array<PromptChoice<Value>>
|
|
107
|
+
}): Promise<Value> => {
|
|
108
|
+
ensureInteractive()
|
|
109
|
+
return unwrap(
|
|
110
|
+
await select<Value>({
|
|
111
|
+
message: options.message,
|
|
112
|
+
options: toOptions(options.choices),
|
|
113
|
+
output,
|
|
114
|
+
}),
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export const promptAutocomplete = async <Value>(options: {
|
|
119
|
+
message: string
|
|
120
|
+
choices: Array<PromptChoice<Value>>
|
|
121
|
+
}): Promise<Value> => {
|
|
122
|
+
ensureInteractive()
|
|
123
|
+
return unwrap(
|
|
124
|
+
await autocomplete<Value>({
|
|
125
|
+
message: options.message,
|
|
126
|
+
options: toOptions(options.choices),
|
|
127
|
+
// Search a list by any part of a name or hint, rather than only by
|
|
128
|
+
// the label, which is all clack matches for itself.
|
|
129
|
+
filter: searchChoices,
|
|
130
|
+
output,
|
|
131
|
+
}),
|
|
132
|
+
)
|
|
133
|
+
}
|
|
33
134
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
135
|
+
export const promptAutocompleteMultiselect = async <Value>(options: {
|
|
136
|
+
message: string
|
|
137
|
+
choices: Array<PromptChoice<Value>>
|
|
138
|
+
}): Promise<Value[]> => {
|
|
139
|
+
ensureInteractive()
|
|
140
|
+
return unwrap(
|
|
141
|
+
await autocompleteMultiselect<Value>({
|
|
142
|
+
message: options.message,
|
|
143
|
+
options: toOptions(options.choices),
|
|
144
|
+
filter: searchChoices,
|
|
145
|
+
output,
|
|
146
|
+
}),
|
|
37
147
|
)
|
|
38
148
|
}
|
|
149
|
+
|
|
150
|
+
export interface SearchableChoice {
|
|
151
|
+
label?: string | undefined
|
|
152
|
+
hint?: string | undefined
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Match a choice by every whitespace separated term of the input, matched
|
|
157
|
+
* case insensitively against the label and the hint.
|
|
158
|
+
*/
|
|
159
|
+
export const searchChoices = (
|
|
160
|
+
input: string,
|
|
161
|
+
choice: SearchableChoice,
|
|
162
|
+
): boolean => {
|
|
163
|
+
const terms = input
|
|
164
|
+
.toLowerCase()
|
|
165
|
+
.split(/\s+/)
|
|
166
|
+
.filter((term) => term.length > 0)
|
|
167
|
+
|
|
168
|
+
if (terms.length === 0) return true
|
|
169
|
+
|
|
170
|
+
const searchable = `${choice.label ?? ''} ${choice.hint ?? ''}`
|
|
171
|
+
.toLowerCase()
|
|
172
|
+
.trim()
|
|
173
|
+
return terms.every((term) => searchable.includes(term))
|
|
174
|
+
}
|
package/src/lib/version.ts
CHANGED