@seamapi/cli 0.16.0 → 0.17.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.
Files changed (46) hide show
  1. package/bin/cli.js +9 -5
  2. package/bin/cli.js.map +1 -1
  3. package/lib/interact-for-action-attempt-poll.js +3 -5
  4. package/lib/interact-for-action-attempt-poll.js.map +1 -1
  5. package/lib/interact-for-array.js +11 -18
  6. package/lib/interact-for-array.js.map +1 -1
  7. package/lib/interact-for-blueprint-object.js +24 -34
  8. package/lib/interact-for-blueprint-object.js.map +1 -1
  9. package/lib/interact-for-command-selection.js +6 -11
  10. package/lib/interact-for-command-selection.js.map +1 -1
  11. package/lib/interact-for-custom-metadata.js +11 -22
  12. package/lib/interact-for-custom-metadata.js.map +1 -1
  13. package/lib/interact-for-login.js +3 -5
  14. package/lib/interact-for-login.js.map +1 -1
  15. package/lib/interact-for-resource.js +6 -6
  16. package/lib/interact-for-resource.js.map +1 -1
  17. package/lib/interact-for-server-selection.js +9 -17
  18. package/lib/interact-for-server-selection.js.map +1 -1
  19. package/lib/interact-for-timestamp.d.ts +1 -1
  20. package/lib/interact-for-timestamp.js +14 -5
  21. package/lib/interact-for-timestamp.js.map +1 -1
  22. package/lib/interact-for-use-remote-api-defs.js +14 -18
  23. package/lib/interact-for-use-remote-api-defs.js.map +1 -1
  24. package/lib/interact-for-workspace-id.d.ts +1 -1
  25. package/lib/interact-for-workspace-id.js +8 -13
  26. package/lib/interact-for-workspace-id.js.map +1 -1
  27. package/lib/util/prompt.d.ts +42 -13
  28. package/lib/util/prompt.js +79 -28
  29. package/lib/util/prompt.js.map +1 -1
  30. package/lib/version.d.ts +1 -1
  31. package/lib/version.js +1 -1
  32. package/package.json +2 -3
  33. package/src/bin/cli.ts +14 -5
  34. package/src/lib/interact-for-action-attempt-poll.ts +3 -5
  35. package/src/lib/interact-for-array.ts +11 -19
  36. package/src/lib/interact-for-blueprint-object.ts +43 -49
  37. package/src/lib/interact-for-command-selection.ts +6 -12
  38. package/src/lib/interact-for-custom-metadata.ts +11 -23
  39. package/src/lib/interact-for-login.ts +3 -5
  40. package/src/lib/interact-for-resource.ts +6 -7
  41. package/src/lib/interact-for-server-selection.ts +10 -18
  42. package/src/lib/interact-for-timestamp.ts +14 -5
  43. package/src/lib/interact-for-use-remote-api-defs.ts +14 -18
  44. package/src/lib/interact-for-workspace-id.ts +8 -14
  45. package/src/lib/util/prompt.ts +138 -38
  46. package/src/lib/version.ts +1 -1
@@ -14,7 +14,14 @@ import { getOutput } from './output/get-output.js'
14
14
  import type { ContextHelpers } from './types.js'
15
15
  import { NonInteractiveError, toArgName } from './util/cli-args.js'
16
16
  import { ellipsis } from './util/ellipsis.js'
17
- import { prompt } from './util/prompt.js'
17
+ import {
18
+ promptAutocomplete,
19
+ promptAutocompleteMultiselect,
20
+ promptConfirm,
21
+ promptNumber,
22
+ promptSelect,
23
+ promptText,
24
+ } from './util/prompt.js'
18
25
 
19
26
  const ergonomicPropOrder = [
20
27
  'name',
@@ -47,7 +54,9 @@ export const interactForBlueprintObject = async (
47
54
  .filter((parameter) => parameter.isRequired)
48
55
  .map((parameter) => parameter.name)
49
56
 
50
- const haveAllRequiredParams = required.every((k) => args.params[k])
57
+ const isSupplied = (k: string): boolean => args.params[k] !== undefined
58
+
59
+ const haveAllRequiredParams = required.every(isSupplied)
51
60
 
52
61
  const cmdPath = `/${args.command.join('/').replace(/-/g, '_')}`
53
62
 
@@ -60,7 +69,7 @@ export const interactForBlueprintObject = async (
60
69
  }
61
70
 
62
71
  if (ctx.interactivity === 'non-interactive') {
63
- const missing = required.filter((k) => !args.params[k])
72
+ const missing = required.filter((k) => !isSupplied(k))
64
73
  const target = args.isSubProperty ? `"${args.subPropertyPath}"` : cmdPath
65
74
  throw new NonInteractiveError(
66
75
  missing.length > 0
@@ -84,23 +93,21 @@ export const interactForBlueprintObject = async (
84
93
  : `[${cmdPath}] Parameters`
85
94
 
86
95
  getOutput().info()
87
- const { paramToEdit } = await prompt({
88
- name: 'paramToEdit',
96
+ const paramToEdit = await promptAutocomplete({
89
97
  message: parameterSelectionMessage,
90
- type: 'autocomplete',
91
98
  choices: [
92
99
  ...(haveAllRequiredParams && !args.isSubProperty
93
100
  ? [
94
101
  {
95
102
  value: 'done',
96
- title: `[Make API Call] ${cmdPath}`,
103
+ label: `[Make API Call] ${cmdPath}`,
97
104
  },
98
105
  ]
99
106
  : []),
100
107
  ...(haveAllRequiredParams && args.isSubProperty
101
108
  ? [
102
109
  {
103
- title: `[Save]`,
110
+ label: `[Save]`,
104
111
  value: 'done',
105
112
  },
106
113
  ]
@@ -108,9 +115,9 @@ export const interactForBlueprintObject = async (
108
115
  ...Object.keys(properties)
109
116
  .map((k) => {
110
117
  return {
111
- title: k + (required.includes(k) ? '*' : ''),
118
+ label: k + (required.includes(k) ? '*' : ''),
112
119
  value: k,
113
- description:
120
+ hint:
114
121
  args.params[k] !== undefined
115
122
  ? typeof args.params[k] === 'object'
116
123
  ? ellipsis(JSON.stringify(args.params[k]), 60)
@@ -122,13 +129,13 @@ export const interactForBlueprintObject = async (
122
129
  ...(args.isSubProperty
123
130
  ? [
124
131
  {
125
- title: `[Leave Empty]`,
132
+ label: `[Leave Empty]`,
126
133
  value: 'empty',
127
134
  },
128
135
  ]
129
136
  : []),
130
137
  {
131
- title: `[Back]`,
138
+ label: `[Back]`,
132
139
  value: 'back',
133
140
  },
134
141
  ],
@@ -188,7 +195,10 @@ export const interactForBlueprintObject = async (
188
195
  ) {
189
196
  args.params[paramToEdit] = await interactForTimestamp()
190
197
  return interactForBlueprintObject(args, ctx)
191
- } else if (paramToEdit === 'custom_metadata') {
198
+ } else if (
199
+ paramToEdit === 'custom_metadata' ||
200
+ paramToEdit === 'custom_metadata_has'
201
+ ) {
192
202
  args.params[paramToEdit] = await interactForCustomMetadata(
193
203
  args.params[paramToEdit] || {},
194
204
  )
@@ -201,36 +211,26 @@ export const interactForBlueprintObject = async (
201
211
  if (prop.format === 'datetime') {
202
212
  value = await interactForTimestamp()
203
213
  } else {
204
- value = (
205
- await prompt({
206
- name: 'value',
207
- message: `${paramToEdit}:`,
208
- type: 'text',
209
- })
210
- ).value
214
+ value = await promptText({
215
+ message: `${paramToEdit}:`,
216
+ })
211
217
  }
212
218
  args.params[paramToEdit] = value
213
219
  return interactForBlueprintObject(args, ctx)
214
220
  } else if (prop.format === 'enum') {
215
- const value = (
216
- await prompt({
217
- name: 'value',
218
- message: `${paramToEdit}:`,
219
- type: 'select',
220
- choices: prop.values.map((v) => ({
221
- title: v.name,
222
- value: v.name,
223
- })),
224
- })
225
- ).value
221
+ const value = await promptSelect({
222
+ message: `${paramToEdit}:`,
223
+ choices: prop.values.map((v) => ({
224
+ label: v.name,
225
+ value: v.name,
226
+ })),
227
+ })
226
228
  args.params[paramToEdit] = value
227
229
  return interactForBlueprintObject(args, ctx)
228
230
  } else if (prop.format === 'boolean') {
229
- const { value } = await prompt({
230
- name: 'value',
231
+ const value = await promptConfirm({
231
232
  message: `${paramToEdit}:`,
232
- type: 'toggle',
233
- initial: true,
233
+ initialValue: true,
234
234
  active: 'true',
235
235
  inactive: 'false',
236
236
  })
@@ -239,17 +239,13 @@ export const interactForBlueprintObject = async (
239
239
 
240
240
  return interactForBlueprintObject(args, ctx)
241
241
  } else if (prop.format === 'list' && prop.itemFormat === 'enum') {
242
- const value = (
243
- await prompt({
244
- name: 'value',
245
- message: `${paramToEdit}:`,
246
- type: 'autocompleteMultiselect',
247
- choices: prop.itemEnumValues.map((v) => ({
248
- title: v.name,
249
- value: v.name,
250
- })),
251
- })
252
- ).value
242
+ const value = await promptAutocompleteMultiselect({
243
+ message: `${paramToEdit}:`,
244
+ choices: prop.itemEnumValues.map((v) => ({
245
+ label: v.name,
246
+ value: v.name,
247
+ })),
248
+ })
253
249
  args.params[paramToEdit] = value
254
250
  return interactForBlueprintObject(args, ctx)
255
251
  } else if (prop.format === 'list') {
@@ -271,10 +267,8 @@ export const interactForBlueprintObject = async (
271
267
  )
272
268
  return interactForBlueprintObject(args, ctx)
273
269
  } else if (prop.format === 'number') {
274
- const { value } = await prompt({
275
- name: 'value',
270
+ const value = await promptNumber({
276
271
  message: `${paramToEdit}:`,
277
- type: 'number',
278
272
  })
279
273
 
280
274
  args.params[paramToEdit] = value
@@ -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 { prompt } from './util/prompt.js'
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 res = await prompt({
96
- name: 'Command',
97
- type: 'autocomplete',
95
+ const selectedCommand = await promptAutocomplete({
96
+ message: `Select a command: /${commandPathStr}`,
98
97
  choices: [
99
98
  ...possibleCommands.map((cmd) => ({
100
- title:
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 (res?.Command === undefined) {
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, res.Command]
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 { prompt } from './util/prompt.js'
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,34 +31,26 @@ export const interactForCustomMetadata = async (
31
31
  do {
32
32
  displayCurrentCustomMetadata()
33
33
 
34
- const response = await prompt({
35
- type: 'select',
36
- name: 'action',
34
+ action = await promptSelect({
37
35
  message: 'Choose an action:',
38
36
  choices: [
39
- { title: 'Add an item to params', value: 'add' },
40
- { title: 'Remove an item from params', value: 'remove' },
41
- { title: 'Finish editing params', value: 'done' },
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 { newKey } = await prompt({
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 { newValue } = await prompt({
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) {
60
52
  if (newValue === 'false' || newValue === 'true') {
61
- newValue = Boolean(newValue)
53
+ newValue = newValue === 'true'
62
54
  }
63
55
  if (newValue === 'null') {
64
56
  updatedCustomMetadata[newKey] = null
@@ -67,21 +59,17 @@ export const interactForCustomMetadata = async (
67
59
  }
68
60
  }
69
61
  } else if (action === 'remove') {
70
- const { customKeyToRemove } = await prompt({
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
- title: `${customMetadataKey}: ${updatedCustomMetadata[customMetadataKey]}`,
66
+ label: `${customMetadataKey}: ${updatedCustomMetadata[customMetadataKey]}`,
77
67
  value: customMetadataKey,
78
68
  }
79
69
  }),
80
70
  })
81
71
 
82
- if (customKeyToRemove) {
83
- delete customMetadata[customKeyToRemove]
84
- }
72
+ delete updatedCustomMetadata[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 { prompt } from './util/prompt.js'
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 { pat } = await prompt({
36
- name: 'pat',
37
- type: 'text',
35
+ const pat = await promptText({
38
36
  message: 'Personal Access Token:',
39
37
  })
40
- const token = pat?.trim()
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 { prompt } from './util/prompt.js'
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
- const { resourceId } = await prompt({
26
- name: 'resourceId',
27
- type: 'autocomplete',
25
+ return await promptAutocomplete({
28
26
  message,
29
- choices: resources.map(toChoice),
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 { prompt } from './util/prompt.js'
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,27 +21,19 @@ export async function interactForServerSelection() {
21
21
  'https://fakeseamconnect.seam.vc',
22
22
  ]
23
23
 
24
- const { server } = await prompt([
25
- {
26
- // Searchable, as selecting a device or a command is.
27
- type: 'autocomplete',
28
- name: 'server',
29
- message: 'Select a server:',
30
- choices: servers.map((server) => ({ title: server, value: server })),
31
- },
32
- ])
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
+ })
33
29
 
34
30
  const config = getConfigStore()
35
31
  const output = getOutput()
36
32
  if (server === servers[2]) {
37
- let { userUrlSeed } = await prompt([
38
- {
39
- type: 'text',
40
- name: 'userUrlSeed',
41
- message:
42
- 'You can input a custom server URL or leave this field empty to use a new fakeserver.',
43
- },
44
- ])
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
+ })
45
37
 
46
38
  if (userUrlSeed.trim().length === 0) {
47
39
  userUrlSeed = randomBytes(5).toString('hex')
@@ -1,10 +1,19 @@
1
- import { prompt } from './util/prompt.js'
1
+ import { promptText } from './util/prompt.js'
2
+
2
3
  export const interactForTimestamp = async () => {
3
- const { timestamp } = await prompt({
4
- name: 'timestamp',
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 { prompt } from './util/prompt.js'
3
+ import { promptSelect } from './util/prompt.js'
4
4
 
5
5
  export async function interactForUseRemoteApiDefs() {
6
- const { useRemoteApiDefs } = await prompt([
7
- {
8
- type: 'select',
9
- name: 'useRemoteApiDefs',
10
- message: 'Always use remote API Definitions?',
11
- choices: [
12
- {
13
- title: 'Yes',
14
- value: true,
15
- },
16
- {
17
- title: 'No',
18
- value: false,
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 { prompt } from './util/prompt.js'
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,23 +29,17 @@ export const interactForWorkspaceId = async (personalAccessToken?: string) => {
29
29
  const workspaces = await withLoading('Fetching workspaces...', () =>
30
30
  seam.workspaces.list(),
31
31
  )
32
- const { workspaceId } = await prompt({
33
- name: 'workspaceId',
34
- // Searchable, as selecting a device or a command is: an account may have
35
- // more workspaces than fit on a screen.
36
- type: 'autocomplete',
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>({
37
35
  message: 'Select a workspace:',
38
36
  choices: workspaces.map((workspace: any) => ({
39
- title: workspace.name,
37
+ label: workspace.name,
40
38
  value: workspace.workspace_id,
41
- description: workspace.workspace_id,
39
+ hint: workspace.workspace_id,
42
40
  })),
43
41
  })
44
42
 
45
- if (workspaceId) {
46
- config.set('current_workspace_id', workspaceId)
47
- return workspaceId
48
- }
49
-
50
- throw new Error('Bailed')
43
+ config.set('current_workspace_id', workspaceId)
44
+ return workspaceId
51
45
  }