@seamapi/cli 0.24.1 → 0.26.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/README.md +8 -4
- package/bin/cli.js +8 -2
- package/bin/cli.js.map +1 -1
- package/lib/args/parse.js +1 -0
- package/lib/args/parse.js.map +1 -1
- package/lib/auth/validate-token.js +1 -1
- package/lib/auth/validate-token.js.map +1 -1
- package/lib/blueprint/source-remote.js +1 -1
- package/lib/blueprint/source-remote.js.map +1 -1
- package/lib/commands/local/wizard.d.ts +8 -2
- package/lib/commands/local/wizard.js +28 -9
- package/lib/commands/local/wizard.js.map +1 -1
- package/lib/commands/spec.js +8 -0
- package/lib/commands/spec.js.map +1 -1
- package/lib/http/api.d.ts +1 -1
- package/lib/http/api.js +1 -1
- package/lib/http/api.js.map +1 -1
- package/lib/http/client.d.ts +1 -1
- package/lib/http/client.js +1 -1
- package/lib/http/client.js.map +1 -1
- package/lib/http/memory-seam-api.js +1 -1
- package/lib/http/memory-seam-api.js.map +1 -1
- package/lib/http/request.js +1 -1
- package/lib/http/request.js.map +1 -1
- package/lib/interactions/access-code.d.ts +1 -1
- package/lib/interactions/access-code.js +2 -1
- package/lib/interactions/access-code.js.map +1 -1
- package/lib/interactions/acs-entrance.d.ts +1 -1
- package/lib/interactions/acs-entrance.js +2 -1
- package/lib/interactions/acs-entrance.js.map +1 -1
- package/lib/interactions/acs-system.d.ts +4 -1
- package/lib/interactions/acs-system.js +2 -1
- package/lib/interactions/acs-system.js.map +1 -1
- package/lib/interactions/acs-user.d.ts +1 -1
- package/lib/interactions/acs-user.js +5 -2
- package/lib/interactions/acs-user.js.map +1 -1
- package/lib/interactions/action-attempt-poll.d.ts +1 -1
- package/lib/interactions/blueprint-object.js +41 -13
- package/lib/interactions/blueprint-object.js.map +1 -1
- package/lib/interactions/connected-account.d.ts +1 -1
- package/lib/interactions/connected-account.js +2 -1
- package/lib/interactions/connected-account.js.map +1 -1
- package/lib/interactions/custom-metadata.js +4 -0
- package/lib/interactions/custom-metadata.js.map +1 -1
- package/lib/interactions/device.d.ts +1 -1
- package/lib/interactions/device.js +2 -1
- package/lib/interactions/device.js.map +1 -1
- package/lib/interactions/login.js +1 -1
- package/lib/interactions/login.js.map +1 -1
- package/lib/interactions/resource.d.ts +3 -1
- package/lib/interactions/resource.js +2 -1
- package/lib/interactions/resource.js.map +1 -1
- package/lib/interactions/timestamp.d.ts +1 -1
- package/lib/interactions/timestamp.js +4 -1
- package/lib/interactions/timestamp.js.map +1 -1
- package/lib/interactions/user-identity.d.ts +1 -1
- package/lib/interactions/user-identity.js +2 -1
- package/lib/interactions/user-identity.js.map +1 -1
- package/lib/interactions/workspace-id.js +1 -1
- package/lib/interactions/workspace-id.js.map +1 -1
- package/lib/memory-prompt.d.ts +10 -7
- package/lib/memory-prompt.js +12 -6
- package/lib/memory-prompt.js.map +1 -1
- package/lib/prompt.d.ts +27 -3
- package/lib/prompt.js +26 -1
- package/lib/prompt.js.map +1 -1
- package/lib/render/help.js +1 -1
- package/lib/render/help.js.map +1 -1
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +11 -13
- package/src/bin/cli.ts +9 -2
- package/src/lib/args/parse.ts +1 -0
- package/src/lib/auth/validate-token.ts +1 -1
- package/src/lib/blueprint/source-remote.ts +1 -1
- package/src/lib/commands/local/wizard.ts +46 -4
- package/src/lib/commands/spec.ts +8 -0
- package/src/lib/http/api.ts +1 -1
- package/src/lib/http/client.ts +1 -1
- package/src/lib/http/memory-seam-api.ts +1 -4
- package/src/lib/http/request.ts +1 -4
- package/src/lib/interactions/access-code.ts +11 -7
- package/src/lib/interactions/acs-entrance.ts +2 -1
- package/src/lib/interactions/acs-system.ts +8 -1
- package/src/lib/interactions/acs-user.ts +5 -4
- package/src/lib/interactions/action-attempt-poll.ts +1 -1
- package/src/lib/interactions/blueprint-object.ts +62 -13
- package/src/lib/interactions/connected-account.ts +2 -1
- package/src/lib/interactions/custom-metadata.ts +6 -0
- package/src/lib/interactions/device.ts +2 -1
- package/src/lib/interactions/login.ts +1 -1
- package/src/lib/interactions/resource.ts +4 -0
- package/src/lib/interactions/timestamp.ts +4 -1
- package/src/lib/interactions/user-identity.ts +2 -1
- package/src/lib/interactions/workspace-id.ts +1 -1
- package/src/lib/memory-prompt.ts +31 -9
- package/src/lib/prompt.ts +65 -4
- package/src/lib/render/help.ts +1 -1
- package/src/lib/version.ts +1 -1
|
@@ -207,32 +207,47 @@ export const interactForBlueprintObject = async (
|
|
|
207
207
|
}
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
+
const current = args.params[paramToEdit]
|
|
211
|
+
|
|
210
212
|
if (paramToEdit === 'device_id') {
|
|
211
|
-
args.params[paramToEdit] = await interactForDevice()
|
|
213
|
+
args.params[paramToEdit] = await interactForDevice(toText(current))
|
|
212
214
|
return interactForBlueprintObject(args, ctx)
|
|
213
215
|
} else if (paramToEdit === 'access_code_id') {
|
|
214
|
-
args.params[paramToEdit] = await interactForAccessCode(
|
|
216
|
+
args.params[paramToEdit] = await interactForAccessCode(
|
|
217
|
+
args.params as any,
|
|
218
|
+
toText(current),
|
|
219
|
+
)
|
|
215
220
|
return interactForBlueprintObject(args, ctx)
|
|
216
221
|
} else if (paramToEdit === 'connected_account_id') {
|
|
217
|
-
const connectedAccountId = await interactForConnectedAccount(
|
|
222
|
+
const connectedAccountId = await interactForConnectedAccount(
|
|
223
|
+
toText(current),
|
|
224
|
+
)
|
|
218
225
|
args.params[paramToEdit] = connectedAccountId
|
|
219
226
|
return interactForBlueprintObject(args, ctx)
|
|
220
227
|
} else if (
|
|
221
228
|
paramToEdit === 'user_identity_id' ||
|
|
222
229
|
paramToEdit === 'user_identity_ids'
|
|
223
230
|
) {
|
|
224
|
-
const userIdentityId = await interactForUserIdentity(
|
|
231
|
+
const userIdentityId = await interactForUserIdentity(
|
|
232
|
+
paramToEdit === 'user_identity_ids'
|
|
233
|
+
? toTextList(current)[0]
|
|
234
|
+
: toText(current),
|
|
235
|
+
)
|
|
225
236
|
args.params[paramToEdit] =
|
|
226
237
|
paramToEdit === 'user_identity_ids' ? [userIdentityId] : userIdentityId
|
|
227
238
|
return interactForBlueprintObject(args, ctx)
|
|
228
239
|
} else if (paramToEdit.endsWith('acs_system_id')) {
|
|
229
|
-
args.params[paramToEdit] = await interactForAcsSystem(
|
|
240
|
+
args.params[paramToEdit] = await interactForAcsSystem({
|
|
241
|
+
initialValue: toText(current),
|
|
242
|
+
})
|
|
230
243
|
return interactForBlueprintObject(args, ctx)
|
|
231
244
|
} else if (paramToEdit.endsWith('acs_user_id')) {
|
|
232
|
-
args.params[paramToEdit] = await interactForAcsUser()
|
|
245
|
+
args.params[paramToEdit] = await interactForAcsUser(toText(current))
|
|
233
246
|
return interactForBlueprintObject(args, ctx)
|
|
234
247
|
} else if (paramToEdit.endsWith('acs_entrance_id')) {
|
|
235
|
-
args.params['acs_entrance_id'] = await interactForAcsEntrance(
|
|
248
|
+
args.params['acs_entrance_id'] = await interactForAcsEntrance(
|
|
249
|
+
toText(args.params['acs_entrance_id']),
|
|
250
|
+
)
|
|
236
251
|
return interactForBlueprintObject(args, ctx)
|
|
237
252
|
} else if (
|
|
238
253
|
paramToEdit.endsWith('_at') ||
|
|
@@ -240,14 +255,14 @@ export const interactForBlueprintObject = async (
|
|
|
240
255
|
paramToEdit.endsWith('_before') ||
|
|
241
256
|
paramToEdit.endsWith('_after')
|
|
242
257
|
) {
|
|
243
|
-
args.params[paramToEdit] = await interactForTimestamp()
|
|
258
|
+
args.params[paramToEdit] = await interactForTimestamp(toText(current))
|
|
244
259
|
return interactForBlueprintObject(args, ctx)
|
|
245
260
|
} else if (
|
|
246
261
|
paramToEdit === 'custom_metadata' ||
|
|
247
262
|
paramToEdit === 'custom_metadata_has'
|
|
248
263
|
) {
|
|
249
264
|
args.params[paramToEdit] = await interactForCustomMetadata(
|
|
250
|
-
|
|
265
|
+
toRecord(current),
|
|
251
266
|
)
|
|
252
267
|
return interactForBlueprintObject(args, ctx)
|
|
253
268
|
}
|
|
@@ -256,10 +271,11 @@ export const interactForBlueprintObject = async (
|
|
|
256
271
|
if (['string', 'id', 'datetime'].includes(prop.format)) {
|
|
257
272
|
let value
|
|
258
273
|
if (prop.format === 'datetime') {
|
|
259
|
-
value = await interactForTimestamp()
|
|
274
|
+
value = await interactForTimestamp(toText(current))
|
|
260
275
|
} else {
|
|
261
276
|
value = await promptText({
|
|
262
277
|
message: withBackHint(`${paramToEdit}:`),
|
|
278
|
+
initialValue: toText(current),
|
|
263
279
|
})
|
|
264
280
|
}
|
|
265
281
|
args.params[paramToEdit] = value
|
|
@@ -271,13 +287,14 @@ export const interactForBlueprintObject = async (
|
|
|
271
287
|
label: v.name,
|
|
272
288
|
value: v.name,
|
|
273
289
|
})),
|
|
290
|
+
initialValue: toText(current),
|
|
274
291
|
})
|
|
275
292
|
args.params[paramToEdit] = value
|
|
276
293
|
return interactForBlueprintObject(args, ctx)
|
|
277
294
|
} else if (prop.format === 'boolean') {
|
|
278
295
|
const value = await promptConfirm({
|
|
279
296
|
message: withBackHint(`${paramToEdit}:`),
|
|
280
|
-
initialValue: true,
|
|
297
|
+
initialValue: toBoolean(current) ?? true,
|
|
281
298
|
active: 'true',
|
|
282
299
|
inactive: 'false',
|
|
283
300
|
})
|
|
@@ -292,12 +309,13 @@ export const interactForBlueprintObject = async (
|
|
|
292
309
|
label: v.name,
|
|
293
310
|
value: v.name,
|
|
294
311
|
})),
|
|
312
|
+
initialValues: toTextList(current),
|
|
295
313
|
})
|
|
296
314
|
args.params[paramToEdit] = value
|
|
297
315
|
return interactForBlueprintObject(args, ctx)
|
|
298
316
|
} else if (prop.format === 'list') {
|
|
299
317
|
args.params[paramToEdit] = await interactForArray(
|
|
300
|
-
|
|
318
|
+
toTextList(current),
|
|
301
319
|
`Edit the list for ${paramToEdit}`,
|
|
302
320
|
)
|
|
303
321
|
return interactForBlueprintObject(args, ctx)
|
|
@@ -305,7 +323,7 @@ export const interactForBlueprintObject = async (
|
|
|
305
323
|
args.params[paramToEdit] = await interactForBlueprintObject(
|
|
306
324
|
{
|
|
307
325
|
command: args.command,
|
|
308
|
-
params:
|
|
326
|
+
params: toRecord(current),
|
|
309
327
|
parameters: prop.parameters,
|
|
310
328
|
isSubProperty: true,
|
|
311
329
|
subPropertyPath: paramToEdit,
|
|
@@ -316,6 +334,7 @@ export const interactForBlueprintObject = async (
|
|
|
316
334
|
} else if (prop.format === 'number') {
|
|
317
335
|
const value = await promptNumber({
|
|
318
336
|
message: withBackHint(`${paramToEdit}:`),
|
|
337
|
+
initialValue: toNumber(current),
|
|
319
338
|
})
|
|
320
339
|
|
|
321
340
|
args.params[paramToEdit] = value
|
|
@@ -332,3 +351,33 @@ export const interactForBlueprintObject = async (
|
|
|
332
351
|
`Didn't know how to handle Blueprint parameter for property: "${paramToEdit}"`,
|
|
333
352
|
)
|
|
334
353
|
}
|
|
354
|
+
|
|
355
|
+
/*
|
|
356
|
+
* A parameter's current value in the shape its editor starts from, so that
|
|
357
|
+
* editing a parameter that already has a value continues from that value
|
|
358
|
+
* instead of from nothing — whether it came from an argument, from the JSON
|
|
359
|
+
* piped in, or from an earlier trip through this menu.
|
|
360
|
+
*
|
|
361
|
+
* Params are passed through as given, so a value need not match the format
|
|
362
|
+
* its parameter documents: the JSON piped in is arbitrary, and a schema can
|
|
363
|
+
* change under a script. A value the editor cannot start from is left out of
|
|
364
|
+
* it, which opens the editor as an unset parameter does, rather than
|
|
365
|
+
* rendering a value of the wrong type as text to edit.
|
|
366
|
+
*/
|
|
367
|
+
|
|
368
|
+
const toText = (value: unknown): string | undefined =>
|
|
369
|
+
typeof value === 'string' ? value : undefined
|
|
370
|
+
|
|
371
|
+
const toNumber = (value: unknown): number | undefined =>
|
|
372
|
+
typeof value === 'number' ? value : undefined
|
|
373
|
+
|
|
374
|
+
const toBoolean = (value: unknown): boolean | undefined =>
|
|
375
|
+
typeof value === 'boolean' ? value : undefined
|
|
376
|
+
|
|
377
|
+
const toTextList = (value: unknown): string[] =>
|
|
378
|
+
Array.isArray(value) ? value.filter((item) => typeof item === 'string') : []
|
|
379
|
+
|
|
380
|
+
const toRecord = (value: unknown): Record<string, any> =>
|
|
381
|
+
typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
382
|
+
? (value as Record<string, any>)
|
|
383
|
+
: {}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { getSeam } from 'lib/http/client.js'
|
|
2
2
|
|
|
3
3
|
import { interactForResource } from './resource.js'
|
|
4
|
-
export const interactForConnectedAccount = async () => {
|
|
4
|
+
export const interactForConnectedAccount = async (initialValue?: string) => {
|
|
5
5
|
const seam = await getSeam()
|
|
6
6
|
|
|
7
7
|
return interactForResource({
|
|
8
8
|
resourceName: 'connected_account',
|
|
9
9
|
fetchResources: () => seam.connectedAccounts.list(),
|
|
10
|
+
initialValue,
|
|
10
11
|
toChoice: (connectedAccount) => {
|
|
11
12
|
const identifiers = Object.values(
|
|
12
13
|
connectedAccount.user_identifier ?? {},
|
|
@@ -53,10 +53,16 @@ export const interactForCustomMetadata = async (
|
|
|
53
53
|
message: withBackHint('Enter a key to add or edit:'),
|
|
54
54
|
})
|
|
55
55
|
|
|
56
|
+
// Editing an existing key continues from the value it has, so a value
|
|
57
|
+
// is corrected rather than retyped.
|
|
58
|
+
const currentValue = updatedCustomMetadata[newKey]
|
|
59
|
+
|
|
56
60
|
let newValue: string | boolean = await promptText({
|
|
57
61
|
message: withBackHint(
|
|
58
62
|
'Enter the new value to add or edit (or null to delete):',
|
|
59
63
|
),
|
|
64
|
+
initialValue:
|
|
65
|
+
currentValue == null ? undefined : currentValue.toString(),
|
|
60
66
|
})
|
|
61
67
|
if (newKey) {
|
|
62
68
|
if (newValue === 'false' || newValue === 'true') {
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { getSeam } from 'lib/http/client.js'
|
|
2
2
|
|
|
3
3
|
import { interactForResource } from './resource.js'
|
|
4
|
-
export const interactForDevice = async () => {
|
|
4
|
+
export const interactForDevice = async (initialValue?: string) => {
|
|
5
5
|
const seam = await getSeam()
|
|
6
6
|
|
|
7
7
|
return interactForResource({
|
|
8
8
|
resourceName: 'device',
|
|
9
9
|
fetchResources: () => seam.devices.list(),
|
|
10
|
+
initialValue,
|
|
10
11
|
toChoice: (device) => ({
|
|
11
12
|
title: device.properties.name ?? '<No Name>',
|
|
12
13
|
value: device.device_id,
|
|
@@ -12,11 +12,14 @@ export const interactForResource = async <Resource>({
|
|
|
12
12
|
fetchResources,
|
|
13
13
|
toChoice,
|
|
14
14
|
message = `Select a ${resourceName}:`,
|
|
15
|
+
initialValue,
|
|
15
16
|
}: {
|
|
16
17
|
resourceName: string
|
|
17
18
|
fetchResources: () => Promise<Resource[]>
|
|
18
19
|
toChoice: (resource: Resource) => ResourceChoice
|
|
19
20
|
message?: string | undefined
|
|
21
|
+
/** The resource already chosen, for the list to open on. */
|
|
22
|
+
initialValue?: string | undefined
|
|
20
23
|
}) => {
|
|
21
24
|
const resources = await withLoading(
|
|
22
25
|
`Fetching ${resourceName.replace(/_/g, ' ')}s...`,
|
|
@@ -30,5 +33,6 @@ export const interactForResource = async <Resource>({
|
|
|
30
33
|
const { title, value, description } = toChoice(resource)
|
|
31
34
|
return { label: title, value, hint: description }
|
|
32
35
|
}),
|
|
36
|
+
initialValue,
|
|
33
37
|
})
|
|
34
38
|
}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { promptText, withBackHint } from 'lib/prompt.js'
|
|
2
2
|
|
|
3
|
-
export const interactForTimestamp = async () => {
|
|
3
|
+
export const interactForTimestamp = async (currentValue?: string) => {
|
|
4
4
|
const now = new Date().toISOString()
|
|
5
5
|
const timestamp = await promptText({
|
|
6
6
|
message: withBackHint('Enter a timestamp:'),
|
|
7
7
|
placeholder: now,
|
|
8
8
|
defaultValue: now,
|
|
9
|
+
// A timestamp already given is offered for editing, rather than making
|
|
10
|
+
// the user type it out again to shift it by an hour.
|
|
11
|
+
initialValue: currentValue,
|
|
9
12
|
validate: (value) => {
|
|
10
13
|
if (value == null || value === '') return undefined
|
|
11
14
|
if (Number.isNaN(new Date(value).getTime())) {
|
|
@@ -2,12 +2,13 @@ import { getSeam } from 'lib/http/client.js'
|
|
|
2
2
|
|
|
3
3
|
import { interactForResource } from './resource.js'
|
|
4
4
|
|
|
5
|
-
export const interactForUserIdentity = async () => {
|
|
5
|
+
export const interactForUserIdentity = async (initialValue?: string) => {
|
|
6
6
|
const seam = await getSeam()
|
|
7
7
|
|
|
8
8
|
return interactForResource({
|
|
9
9
|
resourceName: 'user_identity',
|
|
10
10
|
fetchResources: () => seam.userIdentities.list(),
|
|
11
|
+
initialValue,
|
|
11
12
|
toChoice: (userIdentity) => ({
|
|
12
13
|
title: `${userIdentity.email_address} "${userIdentity.full_name}: ${userIdentity.user_identity_key}`,
|
|
13
14
|
value: userIdentity.user_identity_id,
|
package/src/lib/memory-prompt.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type {
|
|
|
3
3
|
PromptChoice,
|
|
4
4
|
PromptClient,
|
|
5
5
|
PromptConfirmOptions,
|
|
6
|
+
PromptMultiselectOptions,
|
|
6
7
|
PromptNumberOptions,
|
|
7
8
|
PromptSelectOptions,
|
|
8
9
|
PromptTextOptions,
|
|
@@ -19,6 +20,9 @@ export interface PromptQuestion {
|
|
|
19
20
|
| 'autocompleteMultiselect'
|
|
20
21
|
message: string
|
|
21
22
|
choices?: Array<PromptChoice<unknown>>
|
|
23
|
+
/** What the question was seeded with — what the user sees to edit. */
|
|
24
|
+
initialValue?: unknown
|
|
25
|
+
initialValues?: unknown[] | undefined
|
|
22
26
|
}
|
|
23
27
|
|
|
24
28
|
/** Scripted in place of an answer to dismiss that prompt. */
|
|
@@ -44,35 +48,53 @@ export class MemoryPromptClient implements PromptClient {
|
|
|
44
48
|
|
|
45
49
|
canPrompt = (): boolean => true
|
|
46
50
|
|
|
47
|
-
text = async ({
|
|
48
|
-
|
|
51
|
+
text = async ({
|
|
52
|
+
message,
|
|
53
|
+
initialValue,
|
|
54
|
+
}: PromptTextOptions): Promise<string> =>
|
|
55
|
+
this.answer({ kind: 'text', message, initialValue }) as string
|
|
49
56
|
|
|
50
|
-
number = async ({
|
|
51
|
-
|
|
57
|
+
number = async ({
|
|
58
|
+
message,
|
|
59
|
+
initialValue,
|
|
60
|
+
}: PromptNumberOptions): Promise<number> =>
|
|
61
|
+
this.answer({ kind: 'number', message, initialValue }) as number
|
|
52
62
|
|
|
53
|
-
confirm = async ({
|
|
54
|
-
|
|
63
|
+
confirm = async ({
|
|
64
|
+
message,
|
|
65
|
+
initialValue,
|
|
66
|
+
}: PromptConfirmOptions): Promise<boolean> =>
|
|
67
|
+
this.answer({ kind: 'confirm', message, initialValue }) as boolean
|
|
55
68
|
|
|
56
69
|
select = async <Value>({
|
|
57
70
|
message,
|
|
58
71
|
choices,
|
|
72
|
+
initialValue,
|
|
59
73
|
}: PromptSelectOptions<Value>): Promise<Value> =>
|
|
60
|
-
this.answer({ kind: 'select', message, choices }) as Value
|
|
74
|
+
this.answer({ kind: 'select', message, choices, initialValue }) as Value
|
|
61
75
|
|
|
62
76
|
autocomplete = async <Value>({
|
|
63
77
|
message,
|
|
64
78
|
choices,
|
|
79
|
+
initialValue,
|
|
65
80
|
}: PromptSelectOptions<Value>): Promise<Value> =>
|
|
66
|
-
this.answer({
|
|
81
|
+
this.answer({
|
|
82
|
+
kind: 'autocomplete',
|
|
83
|
+
message,
|
|
84
|
+
choices,
|
|
85
|
+
initialValue,
|
|
86
|
+
}) as Value
|
|
67
87
|
|
|
68
88
|
autocompleteMultiselect = async <Value>({
|
|
69
89
|
message,
|
|
70
90
|
choices,
|
|
71
|
-
|
|
91
|
+
initialValues,
|
|
92
|
+
}: PromptMultiselectOptions<Value>): Promise<Value[]> =>
|
|
72
93
|
this.answer({
|
|
73
94
|
kind: 'autocompleteMultiselect',
|
|
74
95
|
message,
|
|
75
96
|
choices,
|
|
97
|
+
initialValues,
|
|
76
98
|
}) as Value[]
|
|
77
99
|
|
|
78
100
|
private answer(question: PromptQuestion): unknown {
|
package/src/lib/prompt.ts
CHANGED
|
@@ -24,11 +24,15 @@ export interface PromptTextOptions {
|
|
|
24
24
|
message: string
|
|
25
25
|
placeholder?: string
|
|
26
26
|
defaultValue?: string
|
|
27
|
+
/** Editable text the prompt opens with, for editing a value in place. */
|
|
28
|
+
initialValue?: string | undefined
|
|
27
29
|
validate?: (value: string | undefined) => string | undefined
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
export interface PromptNumberOptions {
|
|
31
33
|
message: string
|
|
34
|
+
/** Editable text the prompt opens with, for editing a value in place. */
|
|
35
|
+
initialValue?: number | undefined
|
|
32
36
|
validate?: (value: number) => string | undefined
|
|
33
37
|
}
|
|
34
38
|
|
|
@@ -42,6 +46,15 @@ export interface PromptConfirmOptions {
|
|
|
42
46
|
export interface PromptSelectOptions<Value> {
|
|
43
47
|
message: string
|
|
44
48
|
choices: Array<PromptChoice<Value>>
|
|
49
|
+
/** The choice to open on, for editing a value in place. */
|
|
50
|
+
initialValue?: Value | undefined
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface PromptMultiselectOptions<Value> {
|
|
54
|
+
message: string
|
|
55
|
+
choices: Array<PromptChoice<Value>>
|
|
56
|
+
/** The choices to open selected, for editing a value in place. */
|
|
57
|
+
initialValues?: Value[] | undefined
|
|
45
58
|
}
|
|
46
59
|
|
|
47
60
|
/**
|
|
@@ -60,7 +73,7 @@ export interface PromptClient {
|
|
|
60
73
|
select: <Value>(options: PromptSelectOptions<Value>) => Promise<Value>
|
|
61
74
|
autocomplete: <Value>(options: PromptSelectOptions<Value>) => Promise<Value>
|
|
62
75
|
autocompleteMultiselect: <Value>(
|
|
63
|
-
options:
|
|
76
|
+
options: PromptMultiselectOptions<Value>,
|
|
64
77
|
) => Promise<Value[]>
|
|
65
78
|
}
|
|
66
79
|
|
|
@@ -133,6 +146,37 @@ const toOptions = <Value>(
|
|
|
133
146
|
: { label, value, hint }) as Option<Value>,
|
|
134
147
|
)
|
|
135
148
|
|
|
149
|
+
/**
|
|
150
|
+
* A value as the choice a list prompt opens on: the value when the list
|
|
151
|
+
* offers it, and otherwise nothing.
|
|
152
|
+
*
|
|
153
|
+
* A value the list does not offer, such as an id passed as an argument that
|
|
154
|
+
* is not among the resources fetched, would leave clack holding a selection
|
|
155
|
+
* with no choice behind it — so the prompt opens on its first choice, the
|
|
156
|
+
* same as one with no value to start from.
|
|
157
|
+
*/
|
|
158
|
+
export const offeredValue = <Value>(
|
|
159
|
+
choices: Array<PromptChoice<Value>>,
|
|
160
|
+
value: Value | undefined,
|
|
161
|
+
): Value | undefined =>
|
|
162
|
+
choices.some((choice) => choice.value === value) ? value : undefined
|
|
163
|
+
|
|
164
|
+
/** Every one of the values a list prompt offers, in the order given. */
|
|
165
|
+
export const offeredValues = <Value>(
|
|
166
|
+
choices: Array<PromptChoice<Value>>,
|
|
167
|
+
values: Value[] | undefined,
|
|
168
|
+
): Value[] =>
|
|
169
|
+
(values ?? []).filter((value) => offeredValue(choices, value) !== undefined)
|
|
170
|
+
|
|
171
|
+
// Options a clack prompt is given cannot carry an explicit undefined: it
|
|
172
|
+
// declares each optional field without it, and the CLI type-checks with
|
|
173
|
+
// exactOptionalPropertyTypes.
|
|
174
|
+
const optional = <Key extends string, Value>(
|
|
175
|
+
key: Key,
|
|
176
|
+
value: Value | undefined,
|
|
177
|
+
): Partial<Record<Key, Value>> =>
|
|
178
|
+
value === undefined ? {} : ({ [key]: value } as Record<Key, Value>)
|
|
179
|
+
|
|
136
180
|
export class TerminalPromptClient implements PromptClient {
|
|
137
181
|
/**
|
|
138
182
|
* Prompts read raw keypresses and render an interface, so they need a
|
|
@@ -145,7 +189,14 @@ export class TerminalPromptClient implements PromptClient {
|
|
|
145
189
|
|
|
146
190
|
text = async (options: PromptTextOptions): Promise<string> => {
|
|
147
191
|
installArrowKeyAliases()
|
|
148
|
-
|
|
192
|
+
const { initialValue, ...rest } = options
|
|
193
|
+
return unwrap(
|
|
194
|
+
await text({
|
|
195
|
+
...rest,
|
|
196
|
+
...optional('initialValue', initialValue),
|
|
197
|
+
output,
|
|
198
|
+
}),
|
|
199
|
+
)
|
|
149
200
|
}
|
|
150
201
|
|
|
151
202
|
number = async (options: PromptNumberOptions): Promise<number> => {
|
|
@@ -153,6 +204,7 @@ export class TerminalPromptClient implements PromptClient {
|
|
|
153
204
|
const value = unwrap(
|
|
154
205
|
await text({
|
|
155
206
|
message: options.message,
|
|
207
|
+
...optional('initialValue', options.initialValue?.toString()),
|
|
156
208
|
validate: (value) => {
|
|
157
209
|
if (value == null || value.trim() === '') return 'Enter a number'
|
|
158
210
|
const parsed = Number(value)
|
|
@@ -178,6 +230,10 @@ export class TerminalPromptClient implements PromptClient {
|
|
|
178
230
|
await select<Value>({
|
|
179
231
|
message: options.message,
|
|
180
232
|
options: toOptions(options.choices),
|
|
233
|
+
...optional(
|
|
234
|
+
'initialValue',
|
|
235
|
+
offeredValue(options.choices, options.initialValue),
|
|
236
|
+
),
|
|
181
237
|
output,
|
|
182
238
|
}),
|
|
183
239
|
)
|
|
@@ -191,6 +247,10 @@ export class TerminalPromptClient implements PromptClient {
|
|
|
191
247
|
await autocomplete<Value>({
|
|
192
248
|
message: options.message,
|
|
193
249
|
options: toOptions(options.choices),
|
|
250
|
+
...optional(
|
|
251
|
+
'initialValue',
|
|
252
|
+
offeredValue(options.choices, options.initialValue),
|
|
253
|
+
),
|
|
194
254
|
// Search a list by any part of a name or hint, rather than only by
|
|
195
255
|
// the label, which is all clack matches for itself.
|
|
196
256
|
filter: searchChoices,
|
|
@@ -200,13 +260,14 @@ export class TerminalPromptClient implements PromptClient {
|
|
|
200
260
|
}
|
|
201
261
|
|
|
202
262
|
autocompleteMultiselect = async <Value>(
|
|
203
|
-
options:
|
|
263
|
+
options: PromptMultiselectOptions<Value>,
|
|
204
264
|
): Promise<Value[]> => {
|
|
205
265
|
installArrowKeyAliases()
|
|
206
266
|
return unwrap(
|
|
207
267
|
await autocompleteMultiselect<Value>({
|
|
208
268
|
message: options.message,
|
|
209
269
|
options: toOptions(options.choices),
|
|
270
|
+
initialValues: offeredValues(options.choices, options.initialValues),
|
|
210
271
|
filter: searchChoices,
|
|
211
272
|
output,
|
|
212
273
|
}),
|
|
@@ -273,7 +334,7 @@ export const promptAutocomplete = async <Value>(
|
|
|
273
334
|
}
|
|
274
335
|
|
|
275
336
|
export const promptAutocompleteMultiselect = async <Value>(
|
|
276
|
-
options:
|
|
337
|
+
options: PromptMultiselectOptions<Value>,
|
|
277
338
|
): Promise<Value[]> => {
|
|
278
339
|
ensureInteractive()
|
|
279
340
|
return await client.autocompleteMultiselect(options)
|
package/src/lib/render/help.ts
CHANGED
|
@@ -36,7 +36,7 @@ const outputSection = {
|
|
|
36
36
|
content: [
|
|
37
37
|
'Only the response is written to stdout, so it is safe to pipe. Prompts, progress, and other information are written to stderr.',
|
|
38
38
|
'The response is trimmed to the response key and pagination.',
|
|
39
|
-
'Request params may be piped or redirected in as a JSON object. Params given as arguments win over
|
|
39
|
+
'Request params may be piped or redirected in as a JSON object, or passed inline with --raw. Params given as arguments win over raw or stdin params.',
|
|
40
40
|
],
|
|
41
41
|
}
|
|
42
42
|
|
package/src/lib/version.ts
CHANGED