@heroku-cli/command
Version:
base class for Heroku CLI commands
117 lines (116 loc) • 4.76 kB
JavaScript
import { Command as Base, Flags, } from '@oclif/core';
import parser from 'yargs-parser';
import unparser from 'yargs-unparser';
import { APIClient } from './api-client.js';
import { promptAndRun } from './prompt.js';
export class Command extends Base {
/**
* Base flags that includes the prompt flag by default
* Subclasses can override this to customize base flags
*/
static baseFlags = {
prompt: Flags.boolean({
description: 'interactively prompt for command arguments and flags',
helpGroup: 'GLOBAL',
}),
};
/**
* Set this to false in a command class to disable the --prompt flag for that command
*/
static promptFlagActive = true;
allowArbitraryFlags = false;
_heroku;
/* eslint-disable valid-jsdoc */
/**
* Helper function to get baseFlags without the prompt flag
* Use this when you want to remove the prompt flag in a specific command:
*
* @example
* export default class MyCommand extends Command {
* static baseFlags = Command.baseFlagsWithoutPrompt()
* static flags = { ... }
* }
*/
static baseFlagsWithoutPrompt() {
// Destructure to remove the prompt flag
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { prompt, ...rest } = this.baseFlags;
return rest;
}
get heroku() {
if (this._heroku)
return this._heroku;
const options = {
debug: process.env.HEROKU_DEBUG === '1' || process.env.HEROKU_DEBUG?.toUpperCase() === 'TRUE',
debugHeaders: process.env.HEROKU_DEBUG_HEADERS === '1' || process.env.HEROKU_DEBUG_HEADERS?.toUpperCase() === 'TRUE',
};
this._heroku = new APIClient(this.config, options);
return this._heroku;
}
async init() {
await super.init();
// Check if the command has opted out of the prompt flag
const CommandClass = this.constructor;
if (CommandClass.promptFlagActive === false) {
return;
}
// Check if --prompt flag is present in argv
if (!this.argv.includes('--prompt')) {
return;
}
// If we get here, we need to prompt for inputs
const commandId = this.id;
if (!commandId)
return;
await promptAndRun({
argv: this.argv,
commandId,
config: this.config,
});
}
async parse(options, argv) {
if (this.allowArbitraryFlags) {
try {
return await super.parse(options, argv);
}
catch (error) {
const { flags: nonExistentFlags } = error;
const parsed = parser(this.argv);
const nonExistentFlagsWithValues = { ...parsed };
if (nonExistentFlags && nonExistentFlags.length > 0) {
this.warn(`You're using a deprecated syntax with the [${nonExistentFlags}] flag.\nAdd a '--' (end of options) separator before the flags you're passing through.`);
for (const flag of nonExistentFlags) {
const key = flag.replace('--', '');
delete parsed[key];
}
}
for (const key in parsed) {
if (Reflect.has(parsed, key)) {
delete nonExistentFlagsWithValues[key];
}
}
this.argv = unparser(parsed);
const result = await super.parse(options, argv);
result.nonExistentFlags = unparser(nonExistentFlagsWithValues);
for (let index = 0; index < result.nonExistentFlags.length; index++) {
const positionalValue = result.nonExistentFlags[index];
const doubleHyphenRegex = /^--/;
const positionalValueIsFlag = doubleHyphenRegex.test(positionalValue);
if (positionalValueIsFlag) {
const nextElement = result.nonExistentFlags[index + 1] ?? '';
const nextElementIsFlag = doubleHyphenRegex.test(nextElement);
// eslint-disable-next-line max-depth
if (nextElement && !nextElementIsFlag) {
result.argv.push(`${positionalValue}=${nextElement}`);
}
else if (!nextElement || nextElementIsFlag) {
result.argv.push(`${positionalValue}=${true}`);
}
}
}
return result;
}
}
return super.parse(options, argv);
}
}