/*! * 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] []'); } 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