@heroku-cli/command
Version:
base class for Heroku CLI commands
104 lines (103 loc) • 3.96 kB
JavaScript
/* eslint-disable complexity */
import { run } from '@oclif/core';
import inquirer from 'inquirer';
// eslint-disable-next-line valid-jsdoc
/**
* Interactively prompts the user for command arguments and flags,
* then re-runs the command with the collected inputs.
*/
export async function promptAndRun(options) {
const { argv, commandId, config } = options;
const commandMeta = config.findCommand(commandId);
if (!commandMeta)
return;
// Prompt for args and flags
const userInputByArg = new Map();
const userInputByFlag = new Map();
// Prompt for arguments
for (const [argKey, argDef] of Object.entries(commandMeta.args)) {
const { description, required } = argDef;
if (!description)
continue;
// eslint-disable-next-line no-await-in-loop
const { input } = await inquirer.prompt([{
message: `${description} (${required ? 'required' : 'optional - press "Enter" to bypass'})`,
name: 'input',
type: 'input',
validate(value) {
if (!required || value.trim()) {
return true;
}
return `${description} is required`;
},
}]);
if (input && input.trim()) {
userInputByArg.set(argKey, input);
}
}
// Prompt for flags
for (const [, flagDef] of Object.entries(commandMeta.flags)) {
const { char, description, hidden, name, options, type } = flagDef;
// Skip hidden flags, the prompt flag itself, and flags without descriptions
if (!description || hidden || name === 'prompt')
continue;
// Skip flags that already have values from command line
if (argv.includes(`--${name}`) || (char && argv.includes(`-${char}`))) {
continue;
}
if (type === 'boolean') {
// eslint-disable-next-line no-await-in-loop
const { value } = await inquirer.prompt([{
choices: [{ name: 'yes', value: true }, { name: 'no', value: false }],
message: description,
name: 'value',
type: 'list',
}]);
if (value) {
userInputByFlag.set(name, true);
}
// eslint-disable-next-line unicorn/consistent-destructuring
}
else if (options?.length > 0) {
// eslint-disable-next-line no-await-in-loop
const { value } = await inquirer.prompt([{
choices: options.map((opt) => ({ name: opt, value: opt })),
message: `Select the ${description}`,
name: 'value',
type: 'list',
}]);
if (value) {
userInputByFlag.set(name, value);
}
}
else {
// eslint-disable-next-line no-await-in-loop
const { value } = await inquirer.prompt([{
message: `${description} (optional - press "Enter" to bypass)`,
name: 'value',
type: 'input',
}]);
if (value && value.trim()) {
userInputByFlag.set(name, value);
}
}
}
// Build the new argv with collected inputs
const newArgv = [commandId];
for (const [, value] of userInputByArg) {
newArgv.push(value);
}
for (const [flagName, flagValue] of userInputByFlag) {
if (flagValue === true) {
newArgv.push(`--${flagName}`);
}
else {
newArgv.push(`--${flagName}`, flagValue);
}
}
// Re-run the command with the collected inputs
await run(newArgv, config);
// Exit to prevent the original command from running
// eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit
process.exit(0);
}