@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
@@ -1,4 +1,12 @@
1
- import prompts, { type Answers, type Options, type PromptObject } from 'prompts'
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,62 +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
- * Ask the user a question.
18
- *
19
- * Prompts are rendered to stderr: a selection is not a command result,
20
- * so it must not end up in stdout when the CLI is piped.
21
- */
22
- export const prompt = async <T extends string = string>(
23
- questions: PromptObject<T> | Array<PromptObject<T>>,
24
- options?: Options,
25
- ): Promise<Answers<T>> => {
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
+ )
31
63
 
32
- const questionList = Array.isArray(questions) ? questions : [questions]
33
-
34
- return await prompts(
35
- questionList.map((question) => ({
36
- ...question,
37
- // Search a list by any part of a name, rather than only by what it
38
- // starts with, which is all prompts does for itself.
39
- ...(question.type === 'autocomplete' && question.suggest == null
40
- ? { suggest: searchChoices }
41
- : {}),
42
- stdout: process.stderr,
43
- })),
44
- options,
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
+ }
93
+
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
+ }
134
+
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
+ }),
45
147
  )
46
148
  }
47
149
 
48
150
  export interface SearchableChoice {
49
- title?: string | undefined
50
- description?: string | undefined
151
+ label?: string | undefined
152
+ hint?: string | undefined
51
153
  }
52
154
 
53
155
  /**
54
- * Filter choices by every whitespace separated term of the input, matched
55
- * case insensitively against the title and the description.
156
+ * Match a choice by every whitespace separated term of the input, matched
157
+ * case insensitively against the label and the hint.
56
158
  */
57
- export const searchChoices = async <Choice extends SearchableChoice>(
159
+ export const searchChoices = (
58
160
  input: string,
59
- choices: Choice[],
60
- ): Promise<Choice[]> => {
161
+ choice: SearchableChoice,
162
+ ): boolean => {
61
163
  const terms = input
62
164
  .toLowerCase()
63
165
  .split(/\s+/)
64
166
  .filter((term) => term.length > 0)
65
167
 
66
- if (terms.length === 0) return choices
168
+ if (terms.length === 0) return true
67
169
 
68
- return choices.filter((choice) => {
69
- const searchable = `${choice.title ?? ''} ${choice.description ?? ''}`
70
- .toLowerCase()
71
- .trim()
72
- return terms.every((term) => searchable.includes(term))
73
- })
170
+ const searchable = `${choice.label ?? ''} ${choice.hint ?? ''}`
171
+ .toLowerCase()
172
+ .trim()
173
+ return terms.every((term) => searchable.includes(term))
74
174
  }
@@ -1,5 +1,5 @@
1
1
  // Versions are replaced with generated values when the package is packed.
2
- const seamapiCliVersion = '0.16.0'
2
+ const seamapiCliVersion = '0.17.1'
3
3
  const seamapiBlueprintVersion = '1.2.0'
4
4
 
5
5
  export { seamapiBlueprintVersion }