diff --git a/package.json b/package.json index 4ed28508..b7df53e6 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "/messages" ], "dependencies": { - "@oclif/core": "^1.16.4", + "@oclif/core": "^1.19.0", "@salesforce/core": "^3.30.9", "@salesforce/kit": "^1.7.0", "@salesforce/ts-types": "^1.5.20", @@ -71,4 +71,4 @@ "publishConfig": { "access": "public" } -} \ No newline at end of file +} diff --git a/src/exported.ts b/src/exported.ts index 446d1808..823d9750 100644 --- a/src/exported.ts +++ b/src/exported.ts @@ -10,7 +10,7 @@ import { Flags as OclifFlags } from '@oclif/core'; export { toHelpSection, parseVarArgs } from './util'; export { Deployable, Deployer, DeployerResult } from './deployer'; export { Deauthorizer } from './deauthorizer'; -export { Progress, Prompter, generateTableChoices } from './ux'; +export { Progress, Prompter, generateTableChoices, Ux } from './ux'; export { SfHook } from './hooks'; export * from './types'; export { SfCommand, SfCommandInterface, StandardColors } from './sfCommand'; diff --git a/src/flags/duration.ts b/src/flags/duration.ts index d9c18ab2..42d901e2 100644 --- a/src/flags/duration.ts +++ b/src/flags/duration.ts @@ -42,7 +42,8 @@ export type DurationFlagConfig = { */ export const durationFlag = Flags.custom({ parse: async (input, _, opts) => validate(input, opts), - default: async (context) => context.options.defaultValue ? toDuration(context.options.defaultValue, context.options.unit) : undefined, + default: async (context) => + context.options.defaultValue ? toDuration(context.options.defaultValue, context.options.unit) : undefined, }); const validate = (input: string, config: DurationFlagConfig): Duration => { diff --git a/src/sfCommand.ts b/src/sfCommand.ts index 9ceb0036..7c6e0999 100644 --- a/src/sfCommand.ts +++ b/src/sfCommand.ts @@ -15,7 +15,10 @@ import { Mode, EnvironmentVariable, SfError, + ConfigAggregator, + SfdxConfigAggregator, } from '@salesforce/core'; +import { env } from '@salesforce/kit'; import { AnyJson } from '@salesforce/ts-types'; import * as chalk from 'chalk'; import { Progress, Prompter, Spinner, Ux } from './ux'; @@ -158,6 +161,34 @@ export abstract class SfCommand extends Command { public progress: Progress; public project!: SfProject; + /** + * ConfigAggregator instance for accessing global and local configuration. + * + * NOTE: If the active executable is sfdx, this will be an instance of SfdxConfigAggregator, which supports + * the deprecated sfdx config vars like defaultusername, defaultdevhubusername, apiversion, etc. Otherwise, + * it will be an instance of ConfigAggregator will only supports the config vars introduce by @salesforce/core@v3. + * + * The executable is determined by `this.config.bin` which is supplied by the base oclif/core Command class. The value + * of `this.config.bin` will be the executable running (e.g. sfdx or sf) or, for local development (e.g. using bin/dev), + * it will be the value of oclif.bin in the plugin's package.json. + * + * If you need to write NUTS for a plugin that needs to work with both sets of config vars you can + * use set the `SF_USE_DEPRECATED_CONFIG_VARS` to `true` to force configAggregator to be an instance of SfdxConfigAggregator or + * `false` to force configAggregator to be an instance of ConfigAggregator. + * + * @example + * ``` + * import { execCmd } from '@salesforce/cli-plugins-testkit'; + * execCmd('config:set defaultusername=test@example.com', { + * env: { + * ...process.env, + * SF_USE_DEPRECATED_CONFIG_VARS: true, + * } + * }) + * ``` + */ + public configAggregator!: ConfigAggregator; + private warnings: SfCommand.Warning[] = []; private ux: Ux; private prompter: Prompter; @@ -166,10 +197,10 @@ export abstract class SfCommand extends Command { public constructor(argv: string[], config: Config) { super(argv, config); const outputEnabled = !this.jsonEnabled(); - this.spinner = new Spinner(outputEnabled); this.progress = new Progress(outputEnabled && envVars.getBoolean(EnvironmentVariable.SF_USE_PROGRESS_BAR, true)); this.ux = new Ux(outputEnabled); - this.prompter = new Prompter(); + this.spinner = this.ux.spinner; + this.prompter = this.ux.prompter; this.lifecycle = Lifecycle.getInstance(); } @@ -329,6 +360,13 @@ export abstract class SfCommand extends Command { } public async _run(): Promise { + this.configAggregator = + this.config.bin === 'sfdx' ?? + env.getBoolean('SF_USE_DEPRECATED_CONFIG_VARS') ?? + env.getBoolean('SFDX_USE_DEPRECATED_CONFIG_VARS') + ? await SfdxConfigAggregator.create() + : await ConfigAggregator.create(); + if (this.statics.requiresProject) { this.project = await this.assignProject(); } diff --git a/src/ux/base.ts b/src/ux/base.ts index b10f46ef..96473d56 100644 --- a/src/ux/base.ts +++ b/src/ux/base.ts @@ -8,7 +8,7 @@ import { AnyFunction } from '@salesforce/ts-types'; export class UxBase { - public constructor(protected outputEnabled: boolean) {} + public constructor(public readonly outputEnabled: boolean) {} protected maybeNoop(fn: AnyFunction): void { if (this.outputEnabled) fn(); diff --git a/src/ux/ux.ts b/src/ux/ux.ts index 77151804..955f8b4e 100644 --- a/src/ux/ux.ts +++ b/src/ux/ux.ts @@ -8,28 +8,100 @@ import { CliUx } from '@oclif/core'; import { AnyJson } from '@salesforce/ts-types'; import { UxBase } from './base'; +import { Prompter } from './prompter'; +import { Spinner } from './spinner'; +/** + * UX methods for plugins. Automatically suppress console output if outputEnabled is set to false. + * + * @example + * ``` + * import { SfCommand, Ux } from '@salesforce/sf-plugins-core'; + * import { AnyJson } from '@salesforce/ts-types'; + * + * class MyCommand extends SfCommand { + * public async run(): Promise { + * const ux = new Ux(!this.jsonEnabled()); + * } + * } + * + * ``` + */ export class Ux extends UxBase { - public constructor(outputEnabled: boolean) { + public spinner: Spinner; + public prompter: Prompter; + + public constructor(public readonly outputEnabled: boolean) { super(outputEnabled); + this.spinner = new Spinner(outputEnabled); + this.prompter = new Prompter(); + } + + /** + * Log a message to the console. This will be automatically suppressed if output is disabled. + * + * @param message Message to log. Formatting is supported. + * @param args Args to be used for formatting. + */ + public log(message?: string, ...args: string[]): void { + this.maybeNoop(() => CliUx.ux.log(message, ...args)); + } + + /** + * Log a warning message to the console. This will be automatically suppressed if output is disabled. + * + * @param message Warning message to log. + */ + public warn(message: string | Error): void { + this.maybeNoop(() => CliUx.ux.warn(message)); } + /** + * Display a table to the console. This will be automatically suppressed if output is disabled. + * + * @param data Data to be displayed + * @param columns Columns to display the data in + * @param options Options for how the table should be displayed + */ public table(data: T[], columns: Ux.Table.Columns, options?: Ux.Table.Options): void { this.maybeNoop(() => CliUx.ux.table(data, columns, options)); } + /** + * Display a url to the console. This will be automatically suppressed if output is disabled. + * + * @param text text to display + * @param uri URL link + * @param params + */ public url(text: string, uri: string, params = {}): void { this.maybeNoop(() => CliUx.ux.url(text, uri, params)); } + /** + * Display stylized JSON to the console. This will be automatically suppressed if output is disabled. + * + * @param obj JSON to display + */ public styledJSON(obj: AnyJson): void { this.maybeNoop(() => CliUx.ux.styledJSON(obj)); } - public styledObject(obj: AnyJson): void { - this.maybeNoop(() => CliUx.ux.styledObject(obj)); + /** + * Display stylized object to the console. This will be automatically suppressed if output is disabled. + * + * @param obj Object to display + * @param keys Keys of object to display + */ + public styledObject(obj: AnyJson, keys?: string[]): void { + this.maybeNoop(() => CliUx.ux.styledObject(obj, keys)); } + /** + * Display stylized header to the console. This will be automatically suppressed if output is disabled. + * + * @param text header to display + */ public styledHeader(text: string): void { this.maybeNoop(() => CliUx.ux.styledHeader(text)); } diff --git a/yarn.lock b/yarn.lock index 9c6e46ed..edc3508e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -540,10 +540,10 @@ is-wsl "^2.1.1" tslib "^2.0.0" -"@oclif/core@^1.16.4", "@oclif/core@^1.18.0": - version "1.18.0" - resolved "https://registry.yarnpkg.com/@oclif/core/-/core-1.18.0.tgz#93c790c663c6b24a609d1ea44362f5438cb19d73" - integrity sha512-12SWbbDbMrhBmEuN9cOQkbN+sFUKlSTw+NlCPVVIg3uEhnmkXgx2wZJtCN4c+IBMJjC27L9lDneeoe70yQuiVg== +"@oclif/core@^1.18.0", "@oclif/core@^1.19.0": + version "1.19.0" + resolved "https://registry.yarnpkg.com/@oclif/core/-/core-1.19.0.tgz#b2e5260c2344e5905312efd7538daacaedb75cbf" + integrity sha512-4R087E3TAG6Q9cvr+4wnlryHbciioXkZmUF/Qmc08dReoFus1BIfwtR5kUa/thNmQBw5oeGnrxnl9yA/TaxAmA== dependencies: "@oclif/linewrap" "^1.0.0" "@oclif/screen" "^3.0.2"