From 29265f02bcafeddec8ca26899033896fcbe41932 Mon Sep 17 00:00:00 2001 From: Mike Donnalley Date: Fri, 14 Oct 2022 11:14:15 -0600 Subject: [PATCH 1/6] feat: export Ux and add configAggregator to SfCommand --- src/exported.ts | 2 +- src/flags/duration.ts | 3 ++- src/sfCommand.ts | 14 ++++++++++++-- src/ux/ux.ts | 10 ++++++++++ 4 files changed, 25 insertions(+), 4 deletions(-) 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..ed9aef5a 100644 --- a/src/sfCommand.ts +++ b/src/sfCommand.ts @@ -15,6 +15,8 @@ import { Mode, EnvironmentVariable, SfError, + ConfigAggregator, + SfdxConfigAggregator, } from '@salesforce/core'; import { AnyJson } from '@salesforce/ts-types'; import * as chalk from 'chalk'; @@ -158,6 +160,11 @@ export abstract class SfCommand extends Command { public progress: Progress; public project!: SfProject; + /** + * ConfigAggregator instance for accessing global and local configuration. + */ + public configAggregator!: ConfigAggregator; + private warnings: SfCommand.Warning[] = []; private ux: Ux; private prompter: Prompter; @@ -166,10 +173,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 +336,9 @@ export abstract class SfCommand extends Command { } public async _run(): Promise { + this.configAggregator = + this.config.bin === 'sfdx' ? await SfdxConfigAggregator.create() : await ConfigAggregator.create(); + if (this.statics.requiresProject) { this.project = await this.assignProject(); } diff --git a/src/ux/ux.ts b/src/ux/ux.ts index 77151804..7f8c7c3f 100644 --- a/src/ux/ux.ts +++ b/src/ux/ux.ts @@ -8,10 +8,20 @@ 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. + */ export class Ux extends UxBase { + public spinner: Spinner; + public prompter: Prompter; + public constructor(outputEnabled: boolean) { super(outputEnabled); + this.spinner = new Spinner(outputEnabled); + this.prompter = new Prompter(); } public table(data: T[], columns: Ux.Table.Columns, options?: Ux.Table.Options): void { From a6ff7397f5e80e9dbd335358512df38ba97aa430 Mon Sep 17 00:00:00 2001 From: Mike Donnalley Date: Fri, 14 Oct 2022 14:06:11 -0600 Subject: [PATCH 2/6] chore: code review --- src/sfCommand.ts | 28 ++++++++++++++++++++++++++-- src/ux/ux.ts | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/src/sfCommand.ts b/src/sfCommand.ts index ed9aef5a..46685464 100644 --- a/src/sfCommand.ts +++ b/src/sfCommand.ts @@ -18,6 +18,7 @@ import { 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'; @@ -162,6 +163,29 @@ export abstract class SfCommand extends Command { /** * 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_BIN_OVERRIDE` to `sfdx` to force configAggregator to be an instance of SfdxConfigAggregator or + * `sf` 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_BIN_OVERRIDE: 'sfdx', + * } + * }) + * ``` */ public configAggregator!: ConfigAggregator; @@ -336,8 +360,8 @@ export abstract class SfCommand extends Command { } public async _run(): Promise { - this.configAggregator = - this.config.bin === 'sfdx' ? await SfdxConfigAggregator.create() : await ConfigAggregator.create(); + const bin = env.getString('SF_BIN_OVERRIDE') ?? env.getString('SFDX_BIN_OVERRIDE') ?? this.config.bin; + this.configAggregator = bin === 'sfdx' ? await SfdxConfigAggregator.create() : await ConfigAggregator.create(); if (this.statics.requiresProject) { this.project = await this.assignProject(); diff --git a/src/ux/ux.ts b/src/ux/ux.ts index 7f8c7c3f..dfe050bc 100644 --- a/src/ux/ux.ts +++ b/src/ux/ux.ts @@ -13,6 +13,19 @@ 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 spinner: Spinner; @@ -24,22 +37,51 @@ export class Ux extends UxBase { this.prompter = new Prompter(); } + /** + * 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)); } + /** + * Display stylized object to the console. This will be automatically suppressed if output is disabled. + * + * @param obj Object to display + */ public styledObject(obj: AnyJson): void { this.maybeNoop(() => CliUx.ux.styledObject(obj)); } + /** + * Display stylized header to the console. This will be automatically suppressed if output is disabled. + * + * @param obj header to display + */ public styledHeader(text: string): void { this.maybeNoop(() => CliUx.ux.styledHeader(text)); } From 95ecd434015aa9f8c54d38b36594da463abfd89e Mon Sep 17 00:00:00 2001 From: Mike Donnalley Date: Mon, 17 Oct 2022 13:10:27 -0600 Subject: [PATCH 3/6] chore: rename env var --- src/sfCommand.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/sfCommand.ts b/src/sfCommand.ts index 46685464..c18aef0f 100644 --- a/src/sfCommand.ts +++ b/src/sfCommand.ts @@ -173,7 +173,7 @@ export abstract class SfCommand extends Command { * 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_BIN_OVERRIDE` to `sfdx` to force configAggregator to be an instance of SfdxConfigAggregator or + * use set the `SF_USE_DEPRECATED_CONFIG_VARS` to `true` to force configAggregator to be an instance of SfdxConfigAggregator or * `sf` to force configAggregator to be an instance of ConfigAggregator. * * @example @@ -182,7 +182,7 @@ export abstract class SfCommand extends Command { * execCmd('config:set defaultusername=test@example.com', { * env: { * ...process.env, - * SF_BIN_OVERRIDE: 'sfdx', + * SF_USE_DEPRECATED_CONFIG_VARS: true, * } * }) * ``` @@ -360,8 +360,12 @@ export abstract class SfCommand extends Command { } public async _run(): Promise { - const bin = env.getString('SF_BIN_OVERRIDE') ?? env.getString('SFDX_BIN_OVERRIDE') ?? this.config.bin; - this.configAggregator = bin === 'sfdx' ? await SfdxConfigAggregator.create() : await ConfigAggregator.create(); + 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(); From 7a8110bc514c55234c6017328a03ba764adfbd04 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Tue, 18 Oct 2022 16:23:34 -0500 Subject: [PATCH 4/6] chore: bump oclif for flag aliases --- package.json | 4 ++-- yarn.lock | 49 +++++-------------------------------------------- 2 files changed, 7 insertions(+), 46 deletions(-) diff --git a/package.json b/package.json index 19f28b68..fdeef273 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.5.44", "@salesforce/ts-types": "^1.5.20", @@ -71,4 +71,4 @@ "publishConfig": { "access": "public" } -} \ No newline at end of file +} diff --git a/yarn.lock b/yarn.lock index 703b46de..2a56fe7d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -545,44 +545,10 @@ is-wsl "^2.1.1" tslib "^2.0.0" -"@oclif/core@^1.16.4": - version "1.16.4" - resolved "https://registry.yarnpkg.com/@oclif/core/-/core-1.16.4.tgz#fafa338ada0624d7f1adac036302b05a37cd96d0" - integrity sha512-l+xHtVMteJWeTZZ+f2yLyNjf69X0mhAH8GILXnmoAGAemXbc1DVstvloxOouarvm9xyHHhquzO1Qg5l6xa1VIw== - dependencies: - "@oclif/linewrap" "^1.0.0" - "@oclif/screen" "^3.0.2" - ansi-escapes "^4.3.2" - ansi-styles "^4.3.0" - cardinal "^2.1.1" - chalk "^4.1.2" - clean-stack "^3.0.1" - cli-progress "^3.10.0" - debug "^4.3.4" - ejs "^3.1.6" - fs-extra "^9.1.0" - get-package-type "^0.1.0" - globby "^11.1.0" - hyperlinker "^1.0.0" - indent-string "^4.0.0" - is-wsl "^2.2.0" - js-yaml "^3.14.1" - natural-orderby "^2.0.3" - object-treeify "^1.1.33" - password-prompt "^1.1.2" - semver "^7.3.7" - string-width "^4.2.3" - strip-ansi "^6.0.1" - supports-color "^8.1.1" - supports-hyperlinks "^2.2.0" - tslib "^2.3.1" - widest-line "^3.1.0" - wrap-ansi "^7.0.0" - -"@oclif/core@^1.3.1": - version "1.15.0" - resolved "https://registry.yarnpkg.com/@oclif/core/-/core-1.15.0.tgz#af015520456568a362a3592656b845d973494ffe" - integrity sha512-H+l0SybcYJiVPRXTu88TsEXNQZV9ZZ6k/xtiHbgE6LItPk77/st9HH4uI/IKK1nMOJS8KkxNmkLKyrcuiL7Bjw== +"@oclif/core@^1.19.0", "@oclif/core@^1.3.1": + 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" @@ -5332,12 +5298,7 @@ tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.0.0, tslib@^2.0.3, tslib@^2.2.0, tslib@^2.3.1: - version "2.3.1" - resolved "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" - integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== - -tslib@^2.1.0: +tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.2.0, tslib@^2.3.1: version "2.4.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== From 9218dc9737a03adaa6ec34a80fc9f7e09588fcde Mon Sep 17 00:00:00 2001 From: Mike Donnalley Date: Wed, 19 Oct 2022 10:18:15 -0600 Subject: [PATCH 5/6] chore: code review --- src/ux/base.ts | 2 +- src/ux/ux.ts | 30 +++++++++++++++++++++++++----- 2 files changed, 26 insertions(+), 6 deletions(-) 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 dfe050bc..955f8b4e 100644 --- a/src/ux/ux.ts +++ b/src/ux/ux.ts @@ -21,7 +21,7 @@ import { Spinner } from './spinner'; * * class MyCommand extends SfCommand { * public async run(): Promise { - * const ux = new Ux(this.jsonEnabled()); + * const ux = new Ux(!this.jsonEnabled()); * } * } * @@ -31,12 +31,31 @@ export class Ux extends UxBase { public spinner: Spinner; public prompter: Prompter; - public constructor(outputEnabled: boolean) { + 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. * @@ -72,15 +91,16 @@ export class Ux extends UxBase { * 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): void { - this.maybeNoop(() => CliUx.ux.styledObject(obj)); + 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 obj header to display + * @param text header to display */ public styledHeader(text: string): void { this.maybeNoop(() => CliUx.ux.styledHeader(text)); From 7d89475f80dd92d426b195872e242386140f1cfb Mon Sep 17 00:00:00 2001 From: Mike Donnalley Date: Wed, 19 Oct 2022 11:57:34 -0600 Subject: [PATCH 6/6] chore: update jsdoc --- src/sfCommand.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sfCommand.ts b/src/sfCommand.ts index c18aef0f..7c6e0999 100644 --- a/src/sfCommand.ts +++ b/src/sfCommand.ts @@ -174,7 +174,7 @@ export abstract class SfCommand extends Command { * * 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 - * `sf` to force configAggregator to be an instance of ConfigAggregator. + * `false` to force configAggregator to be an instance of ConfigAggregator. * * @example * ```