247 lines
11 KiB
JavaScript
247 lines
11 KiB
JavaScript
/*!
|
|
* Copyright (C) Microsoft Corporation. All rights reserved.
|
|
*/
|
|
import { initializePlayerServices, updateEnvironmentName, updateRegion, } from '@microsoft/power-apps-actions';
|
|
import { Command } from 'commander';
|
|
import packageJson from '../package.json' with { type: 'json' };
|
|
import { getSettings } from './CliSettings.js';
|
|
import { createDefaultStringOption } from './CliUtils.js';
|
|
import { HelpStrings } from './Constants/HelpStrings.js';
|
|
import { UsageError } from './Errors/CliError.js';
|
|
import { buildVerbDescriptionTable } from './Utils/BuildVerbDescriptionTable.js';
|
|
import { GlobalArguments } from './Verbs/VerbConstants.js';
|
|
export class ArgumentProvider {
|
|
_httpClient;
|
|
_logger;
|
|
_authenticationProvider;
|
|
_environmentId;
|
|
_region;
|
|
_verb;
|
|
_program;
|
|
_options = {};
|
|
constructor(_httpClient, _logger, _authenticationProvider) {
|
|
this._httpClient = _httpClient;
|
|
this._logger = _logger;
|
|
this._authenticationProvider = _authenticationProvider;
|
|
this._verb = (process.argv[2] || process.env.VERB);
|
|
this._program = new Command();
|
|
// Add the --non-interactive switch as a global option
|
|
this._program.option('--non-interactive', 'Run in non-interactive mode (no prompts, all parameters must be provided via flags or environment variables)');
|
|
this._program.option('--json', 'Format output as JSON');
|
|
this._program.option('--no-color', 'Disable colored output');
|
|
// If a specific verb is detected (and it's not a flag like --help), we configure the program
|
|
// to behave as if that verb is the command itself (e.g., "power-apps-cli add-data-source").
|
|
// This allows us to display help specific to that verb (options, description) when --help is used,
|
|
// rather than showing the generic top-level help. We omit the [verb] argument definition here
|
|
// because the verb is already "consumed" into the program name for this execution context.
|
|
if (this._verb && !this._verb.startsWith('-')) {
|
|
this._program.name(`power-apps ${this._verb}`);
|
|
}
|
|
else {
|
|
this._program.name('power-apps');
|
|
this._program.usage('[option] [command] [<args>]');
|
|
}
|
|
this._program.version(packageJson.version, '-v, --version', 'output the current version');
|
|
this._program.description(HelpStrings.Global.ProgramDescription);
|
|
// Register custom help text to show verb table on global --help
|
|
// This uses Commander's addHelpText hook which runs when help is displayed
|
|
this._program.addHelpText('after', (_context) => {
|
|
// Only show verb table for global help (not verb-specific help)
|
|
if (!this._verb || this._verb.startsWith('-')) {
|
|
return '\n' + buildVerbDescriptionTable() + '\n';
|
|
}
|
|
return '';
|
|
});
|
|
const settings = getSettings();
|
|
this._environmentId = settings.appConfig?.environmentId;
|
|
this._region = settings.appConfig?.region;
|
|
if (!this._region) {
|
|
// Do not provide a prompt, default to prod
|
|
this.addOption({
|
|
flags: {
|
|
key: GlobalArguments.Cloud.name,
|
|
},
|
|
env: GlobalArguments.Cloud.envVar,
|
|
default: 'prod',
|
|
description: HelpStrings.Global.Cloud,
|
|
});
|
|
}
|
|
// If we do not have an environmentId, add an option to get one
|
|
if (!this._environmentId) {
|
|
this.addOption(createDefaultStringOption({
|
|
flags: {
|
|
key: GlobalArguments.EnvironmentId.name,
|
|
alias: GlobalArguments.EnvironmentId.alias,
|
|
},
|
|
env: GlobalArguments.EnvironmentId.envVar,
|
|
message: 'Please provide the environment ID:',
|
|
description: HelpStrings.Global.EnvironmentId,
|
|
}));
|
|
}
|
|
initializePlayerServices({
|
|
httpClient: this._httpClient,
|
|
logger: this._logger,
|
|
// Default region is 'prod' if not specified
|
|
region: this._region || 'prod',
|
|
// Do a temporary variable
|
|
environmentName: this._environmentId || 'default',
|
|
});
|
|
}
|
|
addSwitch(cliOption) {
|
|
this._createOption(cliOption, false);
|
|
}
|
|
addSwitches(cliOptions) {
|
|
cliOptions.forEach((cliOption) => this._createOption(cliOption, false));
|
|
}
|
|
addOption(cliOption) {
|
|
this._createOption(cliOption);
|
|
}
|
|
addOptions(cliOptions) {
|
|
cliOptions.forEach((cliOption) => this._createOption(cliOption));
|
|
}
|
|
async runVerb() {
|
|
// Commander needs to see only the verb-specific arguments, so strip the verb token
|
|
// when a verb was supplied explicitly; global flag paths skip this method entirely.
|
|
if (this._verb && !this._verb.startsWith('-') && process.argv[2] === this._verb) {
|
|
const args = [process.argv[0], process.argv[1], ...process.argv.slice(3)];
|
|
this._program.parse(args);
|
|
}
|
|
else {
|
|
this._program.parse();
|
|
}
|
|
await this._getRequiredVariables();
|
|
if (!this._region) {
|
|
throw new UsageError('Region is not set. Use --cloud or set the CLOUD_INSTANCE environment variable.');
|
|
}
|
|
await this._authenticationProvider.initAsync(this._region);
|
|
await this._logger.initializeTelemetry(this._authenticationProvider);
|
|
this._logger.trackActivityEvent('Initialize', {
|
|
verb: this._verb,
|
|
});
|
|
}
|
|
// Handles scenarios where only global flags (e.g., --help/--version) are provided.
|
|
// Returns 'help' when no verb is present, 'flag' when a flag verb needs Commander handling,
|
|
// and 'none' when execution should continue to verb-specific logic.
|
|
handleGlobalHelpOrFlagsAndExit() {
|
|
if (!this._verb) {
|
|
this._program.help(); // This will display help and exit
|
|
}
|
|
if (this._verb.startsWith('-')) {
|
|
this._program.parse(process.argv);
|
|
}
|
|
}
|
|
getVerb() {
|
|
if (!this._verb || this._verb.startsWith('-')) {
|
|
throw new UsageError('No command specified. Run "power-apps --help" for available commands.');
|
|
}
|
|
return this._verb;
|
|
}
|
|
async getOption(key, ignorePrompt) {
|
|
const settings = getSettings();
|
|
let value = this._program.getOptionValue(key);
|
|
const option = this._options[key];
|
|
// Fallback to environment variable if not provided via CLI
|
|
if (value === undefined && option && option.env) {
|
|
value = process.env[option.env];
|
|
}
|
|
// Fallback to default value if not provided via CLI or Env
|
|
if (value === undefined && option && option.default !== undefined) {
|
|
value = option.default;
|
|
}
|
|
if (!value && !ignorePrompt && option?.customPrompt) {
|
|
if (!settings.interactive) {
|
|
if (option.optionalInNonInteractive) {
|
|
// Option is optional — return undefined and let the actions layer handle it.
|
|
}
|
|
else {
|
|
const flagName = option.flags.key;
|
|
const alias = option.flags.alias;
|
|
const flagHint = alias ? `--${flagName} (-${alias})` : `--${flagName}`;
|
|
const envHint = option.env ? ` or environment variable (${option.env})` : '';
|
|
throw new UsageError(`Missing required option ${flagHint}${envHint}. In non-interactive mode, all parameters must be provided via flags or environment variables.`);
|
|
}
|
|
}
|
|
else {
|
|
value = await option.customPrompt();
|
|
}
|
|
}
|
|
// Option could be undefined by design, so just return it.
|
|
return value;
|
|
}
|
|
getAuthenticationProvider() {
|
|
return this._authenticationProvider;
|
|
}
|
|
/**
|
|
* Sets the description for the current command context.
|
|
* This is used to display help information specific to the verb being executed.
|
|
*
|
|
* @param {string} description - The description to set for the program.
|
|
*/
|
|
setDescription(description) {
|
|
this._program.description(description);
|
|
}
|
|
isJsonMode() {
|
|
return this._program.opts().json === true;
|
|
}
|
|
addExamples(examples) {
|
|
if (!examples || examples.length === 0)
|
|
return;
|
|
this._program.addHelpText('after', () => {
|
|
const lines = ['', 'Examples:'];
|
|
examples.forEach((example) => {
|
|
lines.push(` $ ${example}`);
|
|
});
|
|
return lines.join('\n');
|
|
});
|
|
}
|
|
getEnvironmentId() {
|
|
return this._environmentId;
|
|
}
|
|
async _getRequiredVariables() {
|
|
// We always need a region and environmentId, which we usually get from the config.
|
|
// If there is no config we prompt for them, except for init/telemetry which manage their own state.
|
|
if (this._verb !== 'telemetry' && this._verb !== 'logout') {
|
|
if (!this._environmentId) {
|
|
const environmentId = await this.getOption('environmentId');
|
|
if (environmentId) {
|
|
this._environmentId = environmentId;
|
|
updateEnvironmentName(this._environmentId);
|
|
}
|
|
}
|
|
}
|
|
if (!this._region) {
|
|
const region = await this.getOption('cloud');
|
|
if (region) {
|
|
this._region = region;
|
|
updateRegion(region);
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* @param {CliOption} cliOption
|
|
* @param {boolean} [hasValue=true] true if the option requires a value, false for a switch such as --help
|
|
*/
|
|
_createOption(cliOption, hasValue = true) {
|
|
const option = this._program.createOption(hasValue ? this._createFlags(cliOption.flags) : this._createSwitchFlags(cliOption.flags), cliOption.description);
|
|
// We do not set the env or default here because we want to handle it manually in getOption
|
|
// This allows us to hide the env variable from the help text
|
|
// Hide global options from help output when showing global help
|
|
// This prevents --environment-id and --cloud from showing up in the global help
|
|
if (!this._verb || this._verb.startsWith('-')) {
|
|
option.hideHelp();
|
|
}
|
|
this._program.addOption(option);
|
|
// Map to CliOption using Commander's camelCase attribute name so
|
|
// getOption('environmentId') finds the option registered as --environment-id.
|
|
this._options[option.attributeName()] = cliOption;
|
|
}
|
|
_createFlags(flags) {
|
|
const { key, alias } = flags;
|
|
return alias ? `-${alias}, --${key} <${key}>` : `--${key} <${key}>`;
|
|
}
|
|
_createSwitchFlags(flags) {
|
|
const { key, alias } = flags;
|
|
return alias ? `-${alias}, --${key}` : `--${key}`;
|
|
}
|
|
}
|
|
//# sourceMappingURL=ArgumentProvider.js.map
|