@seamapi/cli 0.18.0 → 0.18.1
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/lib/interact-for-array.js +37 -22
- package/lib/interact-for-array.js.map +1 -1
- package/lib/interact-for-blueprint-object.js +164 -146
- package/lib/interact-for-blueprint-object.js.map +1 -1
- package/lib/interact-for-command-selection.js +25 -10
- package/lib/interact-for-command-selection.js.map +1 -1
- package/lib/interact-for-custom-metadata.js +50 -35
- package/lib/interact-for-custom-metadata.js.map +1 -1
- package/lib/interact-for-resource.js +4 -2
- package/lib/interact-for-resource.js.map +1 -1
- package/lib/interact-for-timestamp.js +2 -2
- package/lib/interact-for-timestamp.js.map +1 -1
- package/lib/util/prompt.d.ts +9 -0
- package/lib/util/prompt.js +10 -0
- package/lib/util/prompt.js.map +1 -1
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +1 -1
- package/src/lib/interact-for-array.ts +41 -24
- package/src/lib/interact-for-blueprint-object.ts +173 -155
- package/src/lib/interact-for-command-selection.ts +29 -11
- package/src/lib/interact-for-custom-metadata.ts +58 -36
- package/src/lib/interact-for-resource.ts +4 -2
- package/src/lib/interact-for-timestamp.ts +2 -2
- package/src/lib/util/prompt.ts +12 -0
- package/src/lib/version.ts +1 -1
|
@@ -17,10 +17,12 @@ import { ellipsis } from './util/ellipsis.js'
|
|
|
17
17
|
import {
|
|
18
18
|
promptAutocomplete,
|
|
19
19
|
promptAutocompleteMultiselect,
|
|
20
|
+
PromptCancelledError,
|
|
20
21
|
promptConfirm,
|
|
21
22
|
promptNumber,
|
|
22
23
|
promptSelect,
|
|
23
24
|
promptText,
|
|
25
|
+
withBackHint,
|
|
24
26
|
} from './util/prompt.js'
|
|
25
27
|
|
|
26
28
|
const ergonomicPropOrder = [
|
|
@@ -88,58 +90,67 @@ export const interactForBlueprintObject = async (
|
|
|
88
90
|
return ergonomicPropOrder.indexOf(prop)
|
|
89
91
|
}
|
|
90
92
|
|
|
91
|
-
const parameterSelectionMessage =
|
|
92
|
-
|
|
93
|
-
|
|
93
|
+
const parameterSelectionMessage = withBackHint(
|
|
94
|
+
args.isSubProperty
|
|
95
|
+
? `Editing "${args.subPropertyPath}"`
|
|
96
|
+
: `[${cmdPath}] Parameters`,
|
|
97
|
+
)
|
|
94
98
|
|
|
95
99
|
getOutput().info()
|
|
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
|
-
|
|
100
|
+
let paramToEdit: string
|
|
101
|
+
try {
|
|
102
|
+
paramToEdit = await promptAutocomplete({
|
|
103
|
+
message: parameterSelectionMessage,
|
|
104
|
+
choices: [
|
|
105
|
+
...(haveAllRequiredParams && !args.isSubProperty
|
|
106
|
+
? [
|
|
107
|
+
{
|
|
108
|
+
value: 'done',
|
|
109
|
+
label: `[Make API Call] ${cmdPath}`,
|
|
110
|
+
},
|
|
111
|
+
]
|
|
112
|
+
: []),
|
|
113
|
+
...(haveAllRequiredParams && args.isSubProperty
|
|
114
|
+
? [
|
|
115
|
+
{
|
|
116
|
+
label: `[Save]`,
|
|
117
|
+
value: 'done',
|
|
118
|
+
},
|
|
119
|
+
]
|
|
120
|
+
: []),
|
|
121
|
+
...Object.keys(properties)
|
|
122
|
+
.map((k) => {
|
|
123
|
+
return {
|
|
124
|
+
label: k + (required.includes(k) ? '*' : ''),
|
|
125
|
+
value: k,
|
|
126
|
+
hint:
|
|
127
|
+
args.params[k] !== undefined
|
|
128
|
+
? typeof args.params[k] === 'object'
|
|
129
|
+
? ellipsis(JSON.stringify(args.params[k]), 60)
|
|
130
|
+
: `[${args.params[k]}]`
|
|
131
|
+
: undefined,
|
|
132
|
+
}
|
|
133
|
+
})
|
|
134
|
+
.sort((a, b) => propSortScore(b.value) - propSortScore(a.value)),
|
|
135
|
+
...(args.isSubProperty
|
|
136
|
+
? [
|
|
137
|
+
{
|
|
138
|
+
label: `[Leave Empty]`,
|
|
139
|
+
value: 'empty',
|
|
140
|
+
},
|
|
141
|
+
]
|
|
142
|
+
: []),
|
|
143
|
+
{
|
|
144
|
+
label: `[Back]`,
|
|
145
|
+
value: 'back',
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
})
|
|
149
|
+
} catch (error) {
|
|
150
|
+
// Dismissing the menu means the same as choosing to go back.
|
|
151
|
+
if (!(error instanceof PromptCancelledError)) throw error
|
|
152
|
+
paramToEdit = 'back'
|
|
153
|
+
}
|
|
143
154
|
|
|
144
155
|
if (paramToEdit === 'empty') {
|
|
145
156
|
return undefined
|
|
@@ -160,121 +171,128 @@ export const interactForBlueprintObject = async (
|
|
|
160
171
|
|
|
161
172
|
const prop = properties[paramToEdit]
|
|
162
173
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
return interactForBlueprintObject(args, ctx)
|
|
169
|
-
} else if (paramToEdit === 'connected_account_id') {
|
|
170
|
-
const connectedAccountId = await interactForConnectedAccount()
|
|
171
|
-
args.params[paramToEdit] = connectedAccountId
|
|
172
|
-
return interactForBlueprintObject(args, ctx)
|
|
173
|
-
} else if (
|
|
174
|
-
paramToEdit === 'user_identity_id' ||
|
|
175
|
-
paramToEdit === 'user_identity_ids'
|
|
176
|
-
) {
|
|
177
|
-
const userIdentityId = await interactForUserIdentity()
|
|
178
|
-
args.params[paramToEdit] =
|
|
179
|
-
paramToEdit === 'user_identity_ids' ? [userIdentityId] : userIdentityId
|
|
180
|
-
return interactForBlueprintObject(args, ctx)
|
|
181
|
-
} else if (paramToEdit.endsWith('acs_system_id')) {
|
|
182
|
-
args.params[paramToEdit] = await interactForAcsSystem()
|
|
183
|
-
return interactForBlueprintObject(args, ctx)
|
|
184
|
-
} else if (paramToEdit.endsWith('acs_user_id')) {
|
|
185
|
-
args.params[paramToEdit] = await interactForAcsUser()
|
|
186
|
-
return interactForBlueprintObject(args, ctx)
|
|
187
|
-
} else if (paramToEdit.endsWith('acs_entrance_id')) {
|
|
188
|
-
args.params['acs_entrance_id'] = await interactForAcsEntrance()
|
|
189
|
-
return interactForBlueprintObject(args, ctx)
|
|
190
|
-
} else if (
|
|
191
|
-
paramToEdit.endsWith('_at') ||
|
|
192
|
-
paramToEdit === 'since' ||
|
|
193
|
-
paramToEdit.endsWith('_before') ||
|
|
194
|
-
paramToEdit.endsWith('_after')
|
|
195
|
-
) {
|
|
196
|
-
args.params[paramToEdit] = await interactForTimestamp()
|
|
197
|
-
return interactForBlueprintObject(args, ctx)
|
|
198
|
-
} else if (
|
|
199
|
-
paramToEdit === 'custom_metadata' ||
|
|
200
|
-
paramToEdit === 'custom_metadata_has'
|
|
201
|
-
) {
|
|
202
|
-
args.params[paramToEdit] = await interactForCustomMetadata(
|
|
203
|
-
args.params[paramToEdit] || {},
|
|
204
|
-
)
|
|
205
|
-
return interactForBlueprintObject(args, ctx)
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
if (prop) {
|
|
209
|
-
if (['string', 'id', 'datetime'].includes(prop.format)) {
|
|
210
|
-
let value
|
|
211
|
-
if (prop.format === 'datetime') {
|
|
212
|
-
value = await interactForTimestamp()
|
|
213
|
-
} else {
|
|
214
|
-
value = await promptText({
|
|
215
|
-
message: `${paramToEdit}:`,
|
|
216
|
-
})
|
|
217
|
-
}
|
|
218
|
-
args.params[paramToEdit] = value
|
|
174
|
+
// Dismissing any prompt below returns to the parameter menu with the
|
|
175
|
+
// parameter left as it was, rather than ending the whole command.
|
|
176
|
+
try {
|
|
177
|
+
if (paramToEdit === 'device_id') {
|
|
178
|
+
args.params[paramToEdit] = await interactForDevice()
|
|
219
179
|
return interactForBlueprintObject(args, ctx)
|
|
220
|
-
} else if (
|
|
221
|
-
|
|
222
|
-
message: `${paramToEdit}:`,
|
|
223
|
-
choices: prop.values.map((v) => ({
|
|
224
|
-
label: v.name,
|
|
225
|
-
value: v.name,
|
|
226
|
-
})),
|
|
227
|
-
})
|
|
228
|
-
args.params[paramToEdit] = value
|
|
180
|
+
} else if (paramToEdit === 'access_code_id') {
|
|
181
|
+
args.params[paramToEdit] = await interactForAccessCode(args.params as any)
|
|
229
182
|
return interactForBlueprintObject(args, ctx)
|
|
230
|
-
} else if (
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
initialValue: true,
|
|
234
|
-
active: 'true',
|
|
235
|
-
inactive: 'false',
|
|
236
|
-
})
|
|
237
|
-
|
|
238
|
-
args.params[paramToEdit] = value
|
|
239
|
-
|
|
183
|
+
} else if (paramToEdit === 'connected_account_id') {
|
|
184
|
+
const connectedAccountId = await interactForConnectedAccount()
|
|
185
|
+
args.params[paramToEdit] = connectedAccountId
|
|
240
186
|
return interactForBlueprintObject(args, ctx)
|
|
241
|
-
} else if (
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
})
|
|
249
|
-
args.params[paramToEdit] = value
|
|
187
|
+
} else if (
|
|
188
|
+
paramToEdit === 'user_identity_id' ||
|
|
189
|
+
paramToEdit === 'user_identity_ids'
|
|
190
|
+
) {
|
|
191
|
+
const userIdentityId = await interactForUserIdentity()
|
|
192
|
+
args.params[paramToEdit] =
|
|
193
|
+
paramToEdit === 'user_identity_ids' ? [userIdentityId] : userIdentityId
|
|
250
194
|
return interactForBlueprintObject(args, ctx)
|
|
251
|
-
} else if (
|
|
252
|
-
args.params[paramToEdit] = await
|
|
253
|
-
args.params[paramToEdit] || [],
|
|
254
|
-
`Edit the list for ${paramToEdit}`,
|
|
255
|
-
)
|
|
195
|
+
} else if (paramToEdit.endsWith('acs_system_id')) {
|
|
196
|
+
args.params[paramToEdit] = await interactForAcsSystem()
|
|
256
197
|
return interactForBlueprintObject(args, ctx)
|
|
257
|
-
} else if (
|
|
258
|
-
args.params[paramToEdit] = await
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
198
|
+
} else if (paramToEdit.endsWith('acs_user_id')) {
|
|
199
|
+
args.params[paramToEdit] = await interactForAcsUser()
|
|
200
|
+
return interactForBlueprintObject(args, ctx)
|
|
201
|
+
} else if (paramToEdit.endsWith('acs_entrance_id')) {
|
|
202
|
+
args.params['acs_entrance_id'] = await interactForAcsEntrance()
|
|
203
|
+
return interactForBlueprintObject(args, ctx)
|
|
204
|
+
} else if (
|
|
205
|
+
paramToEdit.endsWith('_at') ||
|
|
206
|
+
paramToEdit === 'since' ||
|
|
207
|
+
paramToEdit.endsWith('_before') ||
|
|
208
|
+
paramToEdit.endsWith('_after')
|
|
209
|
+
) {
|
|
210
|
+
args.params[paramToEdit] = await interactForTimestamp()
|
|
211
|
+
return interactForBlueprintObject(args, ctx)
|
|
212
|
+
} else if (
|
|
213
|
+
paramToEdit === 'custom_metadata' ||
|
|
214
|
+
paramToEdit === 'custom_metadata_has'
|
|
215
|
+
) {
|
|
216
|
+
args.params[paramToEdit] = await interactForCustomMetadata(
|
|
217
|
+
args.params[paramToEdit] || {},
|
|
267
218
|
)
|
|
268
219
|
return interactForBlueprintObject(args, ctx)
|
|
269
|
-
}
|
|
270
|
-
const value = await promptNumber({
|
|
271
|
-
message: `${paramToEdit}:`,
|
|
272
|
-
})
|
|
220
|
+
}
|
|
273
221
|
|
|
274
|
-
|
|
222
|
+
if (prop) {
|
|
223
|
+
if (['string', 'id', 'datetime'].includes(prop.format)) {
|
|
224
|
+
let value
|
|
225
|
+
if (prop.format === 'datetime') {
|
|
226
|
+
value = await interactForTimestamp()
|
|
227
|
+
} else {
|
|
228
|
+
value = await promptText({
|
|
229
|
+
message: withBackHint(`${paramToEdit}:`),
|
|
230
|
+
})
|
|
231
|
+
}
|
|
232
|
+
args.params[paramToEdit] = value
|
|
233
|
+
return interactForBlueprintObject(args, ctx)
|
|
234
|
+
} else if (prop.format === 'enum') {
|
|
235
|
+
const value = await promptSelect({
|
|
236
|
+
message: withBackHint(`${paramToEdit}:`),
|
|
237
|
+
choices: prop.values.map((v) => ({
|
|
238
|
+
label: v.name,
|
|
239
|
+
value: v.name,
|
|
240
|
+
})),
|
|
241
|
+
})
|
|
242
|
+
args.params[paramToEdit] = value
|
|
243
|
+
return interactForBlueprintObject(args, ctx)
|
|
244
|
+
} else if (prop.format === 'boolean') {
|
|
245
|
+
const value = await promptConfirm({
|
|
246
|
+
message: withBackHint(`${paramToEdit}:`),
|
|
247
|
+
initialValue: true,
|
|
248
|
+
active: 'true',
|
|
249
|
+
inactive: 'false',
|
|
250
|
+
})
|
|
275
251
|
|
|
276
|
-
|
|
252
|
+
args.params[paramToEdit] = value
|
|
253
|
+
|
|
254
|
+
return interactForBlueprintObject(args, ctx)
|
|
255
|
+
} else if (prop.format === 'list' && prop.itemFormat === 'enum') {
|
|
256
|
+
const value = await promptAutocompleteMultiselect({
|
|
257
|
+
message: withBackHint(`${paramToEdit}:`),
|
|
258
|
+
choices: prop.itemEnumValues.map((v) => ({
|
|
259
|
+
label: v.name,
|
|
260
|
+
value: v.name,
|
|
261
|
+
})),
|
|
262
|
+
})
|
|
263
|
+
args.params[paramToEdit] = value
|
|
264
|
+
return interactForBlueprintObject(args, ctx)
|
|
265
|
+
} else if (prop.format === 'list') {
|
|
266
|
+
args.params[paramToEdit] = await interactForArray(
|
|
267
|
+
args.params[paramToEdit] || [],
|
|
268
|
+
`Edit the list for ${paramToEdit}`,
|
|
269
|
+
)
|
|
270
|
+
return interactForBlueprintObject(args, ctx)
|
|
271
|
+
} else if (prop.format === 'object') {
|
|
272
|
+
args.params[paramToEdit] = await interactForBlueprintObject(
|
|
273
|
+
{
|
|
274
|
+
command: args.command,
|
|
275
|
+
params: {},
|
|
276
|
+
parameters: prop.parameters,
|
|
277
|
+
isSubProperty: true,
|
|
278
|
+
subPropertyPath: paramToEdit,
|
|
279
|
+
},
|
|
280
|
+
ctx,
|
|
281
|
+
)
|
|
282
|
+
return interactForBlueprintObject(args, ctx)
|
|
283
|
+
} else if (prop.format === 'number') {
|
|
284
|
+
const value = await promptNumber({
|
|
285
|
+
message: withBackHint(`${paramToEdit}:`),
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
args.params[paramToEdit] = value
|
|
289
|
+
|
|
290
|
+
return interactForBlueprintObject(args, ctx)
|
|
291
|
+
}
|
|
277
292
|
}
|
|
293
|
+
} catch (error) {
|
|
294
|
+
if (!(error instanceof PromptCancelledError)) throw error
|
|
295
|
+
return interactForBlueprintObject(args, ctx)
|
|
278
296
|
}
|
|
279
297
|
|
|
280
298
|
throw new Error(
|
|
@@ -2,7 +2,11 @@ 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 {
|
|
6
|
+
promptAutocomplete,
|
|
7
|
+
PromptCancelledError,
|
|
8
|
+
withBackHint,
|
|
9
|
+
} from './util/prompt.js'
|
|
6
10
|
|
|
7
11
|
const uniqBy = <T>(items: T[], keyOf: (item: T) => unknown): T[] => {
|
|
8
12
|
const seen = new Set<unknown>()
|
|
@@ -92,16 +96,30 @@ export async function interactForCommandSelection(
|
|
|
92
96
|
|
|
93
97
|
const commandPathStr = commandPath.join('/').replace(/-/g, '_')
|
|
94
98
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
99
|
+
// Only a sub-command menu has a level to go back to, so only it says so.
|
|
100
|
+
const selectMessage = `Select a command: /${commandPathStr}`
|
|
101
|
+
|
|
102
|
+
let selectedCommand: string
|
|
103
|
+
try {
|
|
104
|
+
selectedCommand = await promptAutocomplete({
|
|
105
|
+
message:
|
|
106
|
+
commandPath.length > 0 ? withBackHint(selectMessage) : selectMessage,
|
|
107
|
+
choices: [
|
|
108
|
+
...possibleCommands.map((cmd) => ({
|
|
109
|
+
label:
|
|
110
|
+
cmd?.[commandPath.length] ?? `[Call /${commandPathStr} Directly]`,
|
|
111
|
+
value: cmd?.[commandPath.length] ?? '<none>',
|
|
112
|
+
})),
|
|
113
|
+
].sort((a, b) => ergonomicSort(a.value, b.value)),
|
|
114
|
+
})
|
|
115
|
+
} catch (error) {
|
|
116
|
+
if (!(error instanceof PromptCancelledError)) throw error
|
|
117
|
+
// Dismissing the menu means the same as its [Back] entry, which is only
|
|
118
|
+
// offered when there is a level to go back to. At the top there is none,
|
|
119
|
+
// so dismissing it stops the CLI as it always has.
|
|
120
|
+
if (commandPath.length === 0) throw error
|
|
121
|
+
selectedCommand = '[Back]'
|
|
122
|
+
}
|
|
105
123
|
|
|
106
124
|
if (selectedCommand === '<none>') {
|
|
107
125
|
return commandPath
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { getOutput } from './output/get-output.js'
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
PromptCancelledError,
|
|
4
|
+
promptSelect,
|
|
5
|
+
promptText,
|
|
6
|
+
withBackHint,
|
|
7
|
+
} from './util/prompt.js'
|
|
3
8
|
|
|
4
9
|
// Structurally the CustomMetadata of @seamapi/types, spelled out here so the
|
|
5
10
|
// published declarations do not depend on a development-only package.
|
|
@@ -31,45 +36,62 @@ export const interactForCustomMetadata = async (
|
|
|
31
36
|
do {
|
|
32
37
|
displayCurrentCustomMetadata()
|
|
33
38
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if (action === 'add') {
|
|
44
|
-
const newKey = await promptText({
|
|
45
|
-
message: 'Enter a key to add or edit:',
|
|
39
|
+
try {
|
|
40
|
+
action = await promptSelect({
|
|
41
|
+
message: withBackHint('Choose an action:'),
|
|
42
|
+
choices: [
|
|
43
|
+
{ label: 'Add an item to params', value: 'add' },
|
|
44
|
+
{ label: 'Remove an item from params', value: 'remove' },
|
|
45
|
+
{ label: 'Finish editing params', value: 'done' },
|
|
46
|
+
],
|
|
46
47
|
})
|
|
48
|
+
} catch (error) {
|
|
49
|
+
if (!(error instanceof PromptCancelledError)) throw error
|
|
50
|
+
// Dismissing the action menu finishes editing, keeping the changes.
|
|
51
|
+
break
|
|
52
|
+
}
|
|
47
53
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const customKeyToRemove = await promptSelect({
|
|
63
|
-
message: 'Choose a key-value pair to remove from params:',
|
|
64
|
-
choices: Object.keys(updatedCustomMetadata).map((customMetadataKey) => {
|
|
65
|
-
return {
|
|
66
|
-
label: `${customMetadataKey}: ${updatedCustomMetadata[customMetadataKey]}`,
|
|
67
|
-
value: customMetadataKey,
|
|
54
|
+
try {
|
|
55
|
+
if (action === 'add') {
|
|
56
|
+
const newKey = await promptText({
|
|
57
|
+
message: withBackHint('Enter a key to add or edit:'),
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
let newValue: string | boolean = await promptText({
|
|
61
|
+
message: withBackHint(
|
|
62
|
+
'Enter the new value to add or edit (or null to delete):',
|
|
63
|
+
),
|
|
64
|
+
})
|
|
65
|
+
if (newKey) {
|
|
66
|
+
if (newValue === 'false' || newValue === 'true') {
|
|
67
|
+
newValue = newValue === 'true'
|
|
68
68
|
}
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
if (newValue === 'null') {
|
|
70
|
+
updatedCustomMetadata[newKey] = null
|
|
71
|
+
} else {
|
|
72
|
+
updatedCustomMetadata[newKey] = newValue
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
} else if (action === 'remove') {
|
|
76
|
+
const customKeyToRemove = await promptSelect({
|
|
77
|
+
message: withBackHint(
|
|
78
|
+
'Choose a key-value pair to remove from params:',
|
|
79
|
+
),
|
|
80
|
+
choices: Object.keys(updatedCustomMetadata).map(
|
|
81
|
+
(customMetadataKey) => {
|
|
82
|
+
return {
|
|
83
|
+
label: `${customMetadataKey}: ${updatedCustomMetadata[customMetadataKey]}`,
|
|
84
|
+
value: customMetadataKey,
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
),
|
|
88
|
+
})
|
|
71
89
|
|
|
72
|
-
|
|
90
|
+
delete updatedCustomMetadata[customKeyToRemove]
|
|
91
|
+
}
|
|
92
|
+
} catch (error) {
|
|
93
|
+
if (!(error instanceof PromptCancelledError)) throw error
|
|
94
|
+
// Dismissing an inner prompt returns to the action menu.
|
|
73
95
|
}
|
|
74
96
|
} while (action !== 'done')
|
|
75
97
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { promptAutocomplete } from './util/prompt.js'
|
|
1
|
+
import { promptAutocomplete, withBackHint } from './util/prompt.js'
|
|
2
2
|
import { withLoading } from './util/with-loading.js'
|
|
3
3
|
|
|
4
4
|
export interface ResourceChoice {
|
|
@@ -23,7 +23,9 @@ export const interactForResource = async <Resource>({
|
|
|
23
23
|
fetchResources,
|
|
24
24
|
)
|
|
25
25
|
return await promptAutocomplete({
|
|
26
|
-
|
|
26
|
+
// Resource pickers are only reached from the parameter flow, which
|
|
27
|
+
// returns to its menu when one is dismissed.
|
|
28
|
+
message: withBackHint(message),
|
|
27
29
|
choices: resources.map((resource) => {
|
|
28
30
|
const { title, value, description } = toChoice(resource)
|
|
29
31
|
return { label: title, value, hint: description }
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { promptText } from './util/prompt.js'
|
|
1
|
+
import { promptText, withBackHint } from './util/prompt.js'
|
|
2
2
|
|
|
3
3
|
export const interactForTimestamp = async () => {
|
|
4
4
|
const now = new Date().toISOString()
|
|
5
5
|
const timestamp = await promptText({
|
|
6
|
-
message: 'Enter a timestamp:',
|
|
6
|
+
message: withBackHint('Enter a timestamp:'),
|
|
7
7
|
placeholder: now,
|
|
8
8
|
defaultValue: now,
|
|
9
9
|
validate: (value) => {
|
package/src/lib/util/prompt.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
select,
|
|
11
11
|
text,
|
|
12
12
|
} from '@clack/prompts'
|
|
13
|
+
import chalk from 'chalk'
|
|
13
14
|
|
|
14
15
|
import { NonInteractiveError } from './cli-args.js'
|
|
15
16
|
|
|
@@ -37,6 +38,17 @@ export interface PromptChoice<Value> {
|
|
|
37
38
|
hint?: string | undefined
|
|
38
39
|
}
|
|
39
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Note on a prompt message that dismissing it returns to the previous step.
|
|
43
|
+
*
|
|
44
|
+
* Only for prompts whose caller catches the dismissal: elsewhere it still
|
|
45
|
+
* stops the CLI, and saying otherwise would mislead. The note goes in the
|
|
46
|
+
* message because clack renders its own keyboard hints from a hardcoded list
|
|
47
|
+
* that a caller cannot add to.
|
|
48
|
+
*/
|
|
49
|
+
export const withBackHint = (message: string): string =>
|
|
50
|
+
`${message} ${chalk.dim('· Esc: go back')}`
|
|
51
|
+
|
|
40
52
|
const ensureInteractive = (): void => {
|
|
41
53
|
if (!canPrompt()) {
|
|
42
54
|
throw new NonInteractiveError(
|
package/src/lib/version.ts
CHANGED