heroku 10.7.1-beta.0 → 10.8.0-alpha.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 CHANGED
@@ -36,6 +36,7 @@ For other issues, [submit a support ticket](https://help.heroku.com/).
36
36
  * [`heroku access`](docs/access.md) - manage user access to apps
37
37
  * [`heroku accounts`](docs/accounts.md) - list the Heroku accounts in your cache
38
38
  * [`heroku addons`](docs/addons.md) - tools and services for developing, extending, and operating your app
39
+ * [`heroku ai`](docs/ai.md) - manage Heroku AI models
39
40
  * [`heroku apps`](docs/apps.md) - manage apps on Heroku
40
41
  * [`heroku auth`](docs/auth.md) - manage authentication for your Heroku account
41
42
  * [`heroku authorizations`](docs/authorizations.md) - OAuth authorizations
@@ -0,0 +1,235 @@
1
+ const fs = require('node:fs')
2
+ const inquirer = require('inquirer')
3
+
4
+ function choicesPrompt(description, choices, required, defaultValue) {
5
+ return inquirer.prompt([{
6
+ type: 'list',
7
+ name: 'choices',
8
+ message: description,
9
+ choices,
10
+ default: defaultValue,
11
+ validate(input) {
12
+ if (!required || input) {
13
+ return true
14
+ }
15
+
16
+ return `${description} is required`
17
+ },
18
+ }])
19
+ }
20
+
21
+ function prompt(description, required) {
22
+ return inquirer.prompt([{
23
+ type: 'input',
24
+ name: 'input',
25
+ message: description,
26
+ validate(input) {
27
+ if (!required || input.trim()) {
28
+ return true
29
+ }
30
+
31
+ return `${description} is required`
32
+ },
33
+ }])
34
+ }
35
+
36
+ function filePrompt(description, defaultPath) {
37
+ return inquirer.prompt([{
38
+ type: 'input',
39
+ name: 'path',
40
+ message: description,
41
+ default: defaultPath,
42
+ validate(input) {
43
+ if (fs.existsSync(input)) {
44
+ return true
45
+ }
46
+
47
+ return 'File does not exist. Please enter a valid file path.'
48
+ },
49
+ }])
50
+ }
51
+
52
+ const showBooleanPrompt = async (commandFlag, userInputMap, defaultOption) => {
53
+ const {description, default: defaultValue, name: flagOrArgName} = commandFlag
54
+ const choice = await choicesPrompt(description, [
55
+ {name: 'yes', value: true},
56
+ {name: 'no', value: false},
57
+ ], defaultOption)
58
+
59
+ // user cancelled
60
+ if (choice === undefined || choice === 'Cancel') {
61
+ return true
62
+ }
63
+
64
+ if (choice === 'Yes') {
65
+ userInputMap.set(flagOrArgName, defaultValue)
66
+ }
67
+
68
+ return false
69
+ }
70
+
71
+ const showOtherDialog = async (commandFlagOrArg, userInputMap) => {
72
+ const {description, default: defaultValue, options, required, name: flagOrArgName} = commandFlagOrArg
73
+
74
+ let input
75
+ const isFileInput = description?.includes('absolute path')
76
+ if (isFileInput) {
77
+ input = await filePrompt(description, '')
78
+ } else if (options) {
79
+ const choices = options.map(option => ({name: option, value: option}))
80
+ input = await choicesPrompt(`Select the ${description}`, choices, required, defaultValue)
81
+ } else {
82
+ input = await prompt(`${description.slice(0, 1).toUpperCase()}${description.slice(1)} (${required ? 'required' : 'optional - press "Enter" to bypass'})`, required)
83
+ }
84
+
85
+ if (input === undefined) {
86
+ return true
87
+ }
88
+
89
+ if (input !== '') {
90
+ userInputMap.set(flagOrArgName, input)
91
+ }
92
+
93
+ return false
94
+ }
95
+
96
+ function collectInputsFromManifest(flagsOrArgsManifest, omitOptional) {
97
+ const requiredInputs = []
98
+ const optionalInputs = []
99
+
100
+ // Prioritize options over booleans to
101
+ // prevent the user from yo-yo back and
102
+ // forth between the different input dialogs
103
+ const keysByType = Object.keys(flagsOrArgsManifest).sort((a, b) => {
104
+ const {type: aType} = flagsOrArgsManifest[a]
105
+ const {type: bType} = flagsOrArgsManifest[b]
106
+ if (aType === bType) {
107
+ return 0
108
+ }
109
+
110
+ if (aType === 'option') {
111
+ return -1
112
+ }
113
+
114
+ if (bType === 'option') {
115
+ return 1
116
+ }
117
+
118
+ return 0
119
+ })
120
+
121
+ keysByType.forEach(key => {
122
+ const isRequired = Reflect.get(flagsOrArgsManifest[key], 'required');
123
+ (isRequired ? requiredInputs : optionalInputs).push(key)
124
+ })
125
+ // Prioritize required inputs
126
+ // over optional inputs when
127
+ // prompting the user.
128
+ // required inputs are sorted
129
+ // alphabetically. optional
130
+ // inputs are sorted alphabetically
131
+ // and then pushed to the end of
132
+ // the list.
133
+ requiredInputs.sort((a, b) => {
134
+ if (a < b) {
135
+ return -1
136
+ }
137
+
138
+ if (a > b) {
139
+ return 1
140
+ }
141
+
142
+ return 0
143
+ })
144
+ // Include optional only when not explicitly omitted
145
+ return omitOptional ? requiredInputs : [...requiredInputs, ...optionalInputs]
146
+ }
147
+
148
+ async function getInput(flagsOrArgsManifest, userInputMap, omitOptional) {
149
+ const flagsOrArgs = collectInputsFromManifest(flagsOrArgsManifest, omitOptional)
150
+
151
+ for (const flagOrArg of flagsOrArgs) {
152
+ const {name, description, type, hidden} = flagsOrArgsManifest[flagOrArg]
153
+ if (userInputMap.has(name)) {
154
+ continue
155
+ }
156
+
157
+ // hidden args and flags may be exposed later
158
+ // based on the user type. For now, skip them.
159
+ if (!description || hidden) {
160
+ continue
161
+ }
162
+
163
+ const cancelled = await (type === 'boolean' ? showBooleanPrompt : showOtherDialog)(flagsOrArgsManifest[flagOrArg], userInputMap)
164
+ if (cancelled) {
165
+ return true
166
+ }
167
+ }
168
+
169
+ return false
170
+ }
171
+
172
+ async function promptForInputs(commandName, commandManifest, userArgs, userFlags) {
173
+ const {args, flags} = commandManifest
174
+
175
+ const userInputByArg = new Map()
176
+ Object.keys(args).forEach((argKey, index) => {
177
+ if (userArgs[index]) {
178
+ userInputByArg.set(argKey, userArgs[index])
179
+ }
180
+ })
181
+
182
+ let cancelled = await getInput(args, userInputByArg)
183
+ if (cancelled) {
184
+ return {userInputByArg}
185
+ }
186
+
187
+ const userInputByFlag = new Map()
188
+ Object.keys(flags).forEach(flagKey => {
189
+ const {name, char} = flags[flagKey]
190
+ if (userFlags[name] || userFlags[char]) {
191
+ userInputByFlag.set(flagKey, userFlags[flagKey])
192
+ }
193
+ })
194
+ cancelled = await getInput(flags, userInputByFlag)
195
+ if (cancelled) {
196
+ return
197
+ }
198
+
199
+ return {userInputByArg, userInputByFlag}
200
+ }
201
+
202
+ module.exports.promptUser = async (config, commandName, args, flags) => {
203
+ const commandMeta = config.findCommand(commandName)
204
+ if (!commandMeta) {
205
+ process.stderr.write(`"${commandName}" not a valid command\n$ `)
206
+ return
207
+ }
208
+
209
+ const {userInputByArg, userInputByFlag} = await promptForInputs(commandName, commandMeta, args, flags)
210
+
211
+ try {
212
+ for (const [, {input: argValue}] of userInputByArg) {
213
+ if (argValue) {
214
+ args.push(argValue)
215
+ }
216
+ }
217
+
218
+ for (const [flagName, {input: flagValue}] of userInputByFlag) {
219
+ if (!flagValue) {
220
+ continue
221
+ }
222
+
223
+ if (flagValue === true) {
224
+ args.push(`--${flagName}`)
225
+ continue
226
+ }
227
+
228
+ args.push(`--${flagName}`, flagValue)
229
+ }
230
+
231
+ return args
232
+ } catch (error) {
233
+ process.stderr.write(error.message)
234
+ }
235
+ }