-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathprompts.ts
More file actions
153 lines (144 loc) · 5.9 KB
/
prompts.ts
File metadata and controls
153 lines (144 loc) · 5.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
import type { Readable, Writable } from 'node:stream'
import * as clack from '@clack/prompts'
import { DEFAULT_EXIT_CODE, createContextError } from './error.js'
import { resolveClackBase } from './resolve-defaults.js'
import type { Prompts } from './types.js'
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
/**
* Per-call defaults merged into every clack prompt invocation.
*/
export interface PromptDefaults {
/** Show navigation guide hints. */
readonly guide?: boolean
/** Custom input stream. */
readonly input?: Readable
/** Custom output stream. */
readonly output?: Writable
}
/**
* Options for {@link createContextPrompts}.
*/
export interface CreateContextPromptsOptions {
/** Per-call defaults merged into every prompt. Method-level options win. */
readonly defaults?: PromptDefaults
}
/**
* Create the interactive prompt methods for a context.
*
* Each method delegates to `@clack/prompts` and unwraps cancel signals
* into a ContextError so the CLI runner can exit cleanly.
*
* When `defaults` are provided, they are spread as the base of every clack
* call. Method-level options always take precedence.
*
* @param options - Optional configuration with per-call defaults.
* @returns A Prompts instance backed by clack.
*/
export function createContextPrompts(options?: CreateContextPromptsOptions): Prompts {
const base = resolveClackBase(options?.defaults)
return {
async confirm(opts): Promise<boolean> {
const result = await clack.confirm({ ...base, ...opts })
return unwrapCancelSignal(result)
},
async multiselect<Type>(opts: Parameters<Prompts['multiselect']>[0]): Promise<Type[]> {
const result = await clack.multiselect<Type>(
// Accepted exception: generic context assembly requires casting through unknown.
{ ...base, ...opts } as unknown as Parameters<typeof clack.multiselect<Type>>[0]
)
return unwrapCancelSignal(result)
},
async password(opts): Promise<string> {
const result = await clack.password({ ...base, ...opts })
return unwrapCancelSignal(result)
},
async select<Type>(opts: Parameters<Prompts['select']>[0]): Promise<Type> {
const result = await clack.select<Type>(
// Accepted exception: generic context assembly requires casting through unknown.
{ ...base, ...opts } as unknown as Parameters<typeof clack.select<Type>>[0]
)
return unwrapCancelSignal(result)
},
async text(opts): Promise<string> {
const result = await clack.text({ ...base, ...opts })
return unwrapCancelSignal(result)
},
async autocomplete<Type>(opts: Parameters<Prompts['autocomplete']>[0]): Promise<Type> {
const result = await clack.autocomplete<Type>(
// Accepted exception: generic context assembly requires casting through unknown.
{ ...base, ...opts } as unknown as Parameters<typeof clack.autocomplete<Type>>[0]
)
return unwrapCancelSignal(result)
},
async autocompleteMultiselect<Type>(
opts: Parameters<Prompts['autocompleteMultiselect']>[0]
): Promise<Type[]> {
const result = await clack.autocompleteMultiselect<Type>(
// Accepted exception: generic context assembly requires casting through unknown.
{ ...base, ...opts } as unknown as Parameters<typeof clack.autocompleteMultiselect<Type>>[0]
)
return unwrapCancelSignal(result)
},
async groupMultiselect<Type>(
opts: Parameters<Prompts['groupMultiselect']>[0]
): Promise<Type[]> {
const result = await clack.groupMultiselect<Type>(
// Accepted exception: generic context assembly requires casting through unknown.
{ ...base, ...opts } as unknown as Parameters<typeof clack.groupMultiselect<Type>>[0]
)
return unwrapCancelSignal(result)
},
async selectKey<Type extends string>(opts: Parameters<Prompts['selectKey']>[0]): Promise<Type> {
const result = await clack.selectKey<Type>(
// Accepted exception: generic context assembly requires casting through unknown.
{ ...base, ...opts } as unknown as Parameters<typeof clack.selectKey<Type>>[0]
)
return unwrapCancelSignal(result)
},
async path(opts): Promise<string> {
// Accepted exception: generic context assembly requires casting through unknown.
const result = await clack.path({ ...base, ...opts } as unknown as Parameters<
typeof clack.path
>[0])
return unwrapCancelSignal(result)
},
async group(prompts, opts) {
const result = await clack.group(prompts as Parameters<typeof clack.group>[0], {
onCancel: opts?.onCancel as Parameters<typeof clack.group>[1] extends infer O
? O extends { onCancel?: infer F }
? F
: never
: never,
})
// Accepted exception: generic context assembly requires type assertion.
return result as Awaited<ReturnType<Prompts['group']>>
},
} as Prompts
}
// ---------------------------------------------------------------------------
// Private helpers
// ---------------------------------------------------------------------------
/**
* Unwrap a prompt result that may be a cancel symbol.
*
* If the user cancelled (Ctrl-C), throws a ContextError. Otherwise returns
* the typed result value.
*
* @private
* @param result - The raw prompt result (value or cancel symbol).
* @returns The unwrapped typed value.
*/
function unwrapCancelSignal<Type>(result: Type | symbol): Type {
if (clack.isCancel(result)) {
clack.cancel('Operation cancelled.')
// Accepted exception: prompt cancellation must propagate as an unwind.
// The runner catches the thrown ContextError at the CLI boundary.
throw createContextError('Prompt cancelled by user', {
code: 'PROMPT_CANCELLED',
exitCode: DEFAULT_EXIT_CODE,
})
}
return result as Type
}