Skip to content

Commit

Permalink
feat: expose ux methods in SfCommand
Browse files Browse the repository at this point in the history
  • Loading branch information
mdonnalley committed Jan 25, 2022
1 parent a6c39b9 commit 8e58a93
Show file tree
Hide file tree
Showing 18 changed files with 603 additions and 144 deletions.
2 changes: 1 addition & 1 deletion src/deauthorizer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, salesforce.com, inc.
* Copyright (c) 2022, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
Expand Down
8 changes: 4 additions & 4 deletions src/deployer.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
/*
* Copyright (c) 2021, salesforce.com, inc.
* Copyright (c) 2022, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/

import { EventEmitter } from 'events';
import { AnyJson, JsonMap } from '@salesforce/ts-types';
import cli from 'cli-ux';
import { ux } from 'cli-ux';
import { QuestionCollection } from 'inquirer';
import { Prompter } from './prompter';
import { Prompter } from './ux';

export abstract class Deployable {
abstract getName(): string;
Expand Down Expand Up @@ -40,7 +40,7 @@ export abstract class Deployer extends EventEmitter {
* Log messages to the console
*/
public log(msg?: string | undefined, ...args: string[]): void {
cli.log(msg, ...args);
ux.log(msg, ...args);
}

/**
Expand Down
6 changes: 3 additions & 3 deletions src/exported.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
/*
* Copyright (c) 2021, salesforce.com, inc.
* Copyright (c) 2022, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/

export { generateTableChoices, toHelpSection } from './util';
export { toHelpSection } from './util';
export { Deployable, Deployer } from './deployer';
export { Deauthorizer } from './deauthorizer';
export { Prompter } from './prompter';
export { Progress, Prompter, generateTableChoices } from './ux';
export { SfHook } from './hooks';
export * from './types';
export { SfCommand, SfCommandInterface } from './sfCommand';
Expand Down
2 changes: 1 addition & 1 deletion src/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2021, salesforce.com, inc.
* Copyright (c) 2022, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
Expand Down
22 changes: 0 additions & 22 deletions src/prompter.ts

This file was deleted.

61 changes: 58 additions & 3 deletions src/sfCommand.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
/*
* Copyright (c) 2021, salesforce.com, inc.
* Copyright (c) 2022, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
import { Command, Config, HelpSection, Interfaces } from '@oclif/core';
import { ux } from 'cli-ux';
import { Messages } from '@salesforce/core';
import { Spinner } from './ux';
import { AnyJson } from '@salesforce/ts-types';
import { Progress, Prompter, Spinner, Ux } from './ux';

Messages.importMessagesDirectory(__dirname);
const messages = Messages.loadMessages('@salesforce/sf-plugins-core', 'messages');
Expand All @@ -31,14 +33,22 @@ export abstract class SfCommand<T> extends Command {
public static configurationVariablesSection?: HelpSection;
public static envVariablesSection?: HelpSection;
public static errorCodes?: HelpSection;
public static tableFlags = ux.table.flags;

public spinner: Spinner;
public progress: Progress;

private warnings: SfCommand.Warning[] = [];
private ux: Ux;
private prompter: Prompter;

public constructor(argv: string[], config: Config) {
super(argv, config);
this.spinner = new Spinner(this.jsonEnabled());
const outputEnabled = !this.jsonEnabled();
this.spinner = new Spinner(outputEnabled);
this.progress = new Progress(outputEnabled);
this.ux = new Ux(outputEnabled);
this.prompter = new Prompter();
}

/**
Expand All @@ -60,6 +70,51 @@ export abstract class SfCommand<T> extends Command {
this.log(msg);
}

/**
* Log a table to the console. Will automatically be suppressed when --json flag is present.
*/
public table<R extends Ux.Table.Data>(data: R[], columns: Ux.Table.Columns<R>, options?: Ux.Table.Options): void {
this.ux.table(data, columns, options);
}

/**
* Log a stylized url to the console. Will automatically be suppressed when --json flag is present.
*/
public url(text: string, uri: string, params = {}): void {
this.ux.url(text, uri, params);
}

/**
* Log stylized JSON to the console. Will automatically be suppressed when --json flag is present.
*/
public styledJSON(obj: AnyJson): void {
this.ux.styledJSON(obj);
}

/**
* Log stylized object to the console. Will automatically be suppressed when --json flag is present.
*/
public styledObject(obj: AnyJson): void {
this.ux.styledObject(obj);
}

/**
* Prompt user for information. See https://www.npmjs.com/package/inquirer for more.
*
* This will NOT be automatically suppressed when the --json flag is present since we assume
* that any command that prompts the user for required information will not also support the --json flag.
*
* If you need to conditionally suppress prompts to support json output, then do the following:
*
* @example
* if (!this.jsonEnabled()) {
* await this.prompt();
* }
*/
public async prompt<R = Prompter.Answers>(questions: Prompter.Questions<R>, initialAnswers?: Partial<R>): Promise<R> {
return this.prompter.prompt(questions, initialAnswers);
}

/**
* Wrap the command result into the standardized JSON structure.
*/
Expand Down
2 changes: 1 addition & 1 deletion src/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, salesforce.com, inc.
* Copyright (c) 2022, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
Expand Down
62 changes: 9 additions & 53 deletions src/util.ts
Original file line number Diff line number Diff line change
@@ -1,63 +1,19 @@
/*
* Copyright (c) 2021, salesforce.com, inc.
* Copyright (c) 2022, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/

import { Separator, ChoiceOptions, ChoiceBase } from 'inquirer';
import { Dictionary, Nullable, ensureString } from '@salesforce/ts-types';
import { HelpSection } from '@oclif/core';
import { ORG_CONFIG_ALLOWED_PROPERTIES, OrgConfigProperties } from '@salesforce/core';
import { SFDX_ALLOWED_PROPERTIES, SfdxPropertyKeys } from '@salesforce/core';
import { EnvironmentVariable, SUPPORTED_ENV_VARS } from '@salesforce/core';

/**
* Generate a formatted table for list and checkbox prompts
*
* Each option should contain the same keys as specified in columns.
* For example,
* const columns = { name: 'Name', type: 'Type', path: 'Path' };
* const options = [{ name: 'foo', type: 'org', path: '/path/to/foo/' }];
* generateTableChoices(columns, options);
*/
export function generateTableChoices<T>(
columns: Dictionary<string>,
choices: Array<Dictionary<Nullable<string> | T>>,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
padForCheckbox = true
): ChoiceBase[] {
const columnEntries = Object.entries(columns);
const columnLengths = columnEntries.map(
([key, value]) =>
Math.max(
ensureString(value).length,
...choices.map(
(option) =>
ensureString(option[key], `Type ${typeof option[key]} for ${key} in ${Object.keys(option).join(', ')}`)
.length
)
) + 1
);

const choicesOptions: ChoiceBase[] = [
new Separator(
`${padForCheckbox ? ' '.repeat(2) : ''}${columnEntries
.map(([, value], index) => value?.padEnd(columnLengths[index], ' '))
.join('')}`
),
];

for (const meta of choices) {
const name = columnEntries
.map(([key], index) => ensureString(meta[key]).padEnd(columnLengths[index], ' '))
.join('');
const choice: ChoiceOptions = { name, value: meta.value, short: ensureString(meta.name) };
choicesOptions.push(choice);
}

return choicesOptions;
}
import {
ORG_CONFIG_ALLOWED_PROPERTIES,
OrgConfigProperties,
SFDX_ALLOWED_PROPERTIES,
SfdxPropertyKeys,
EnvironmentVariable,
SUPPORTED_ENV_VARS,
} from '@salesforce/core';

/**
* Function to build a help section for command help.
Expand Down
11 changes: 11 additions & 0 deletions src/ux/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* Copyright (c) 2022, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/

export { Ux } from './ux';
export { Progress } from './progress';
export { Prompter, generateTableChoices } from './prompter';
export { Spinner } from './spinner';
117 changes: 117 additions & 0 deletions src/ux/progress.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*
* Copyright (c) 2022, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/

import * as util from 'util';
import { ux } from 'cli-ux';
import { once } from '@salesforce/kit';
import { Ux } from '.';

/**
* Class for display a progress bar to the console. Will automatically be suppressed if the --json flag is present.
*/
export class Progress extends Ux {
private static DEFAULT_OPTIONS = {
title: 'PROGRESS',
format: '%s | {bar} | {value}/{total} Components',
barCompleteChar: '\u2588',
barIncompleteChar: '\u2591',
linewrap: true,
};

private bar!: Progress.Bar;
private total!: number;

public constructor(outputEnabled: boolean) {
super(outputEnabled);
}

/**
* Set the total number of expected components.
*/
public setTotal(total: number): void {
this.total = total;
this.bar.setTotal(total);
}

/**
* Start the progress bar.
*/
public start(
total: number,
payload: Progress.Payload = {},
options: Partial<Progress.Options> = Progress.DEFAULT_OPTIONS
): void {
const opts = Object.assign(Progress.DEFAULT_OPTIONS, options);
opts.format = util.format(opts.format, opts.title);
this.bar = ux.progress({
format: opts.format,
barCompleteChar: opts.barCompleteChar,
barIncompleteChar: opts.barIncompleteChar,
linewrap: opts.linewrap,
}) as Progress.Bar;

this.bar.setTotal(total);
// this.maybeNoop(() => startProgressBar(this.bar, total, payload));
this.maybeNoop(() => {
this._start(total, payload);
});
}

/**
* Update the progress bar.
*/
public update(num: number, payload = {}): void {
this.bar.update(num, payload);
}

/**
* Update the progress bar with the final number and stop it.
*/
public finish(payload = {}): void {
this.bar.update(this.total, payload);
this.bar.stop();
}

/**
* Stop the progress bar.
*/
public stop(): void {
this.bar.stop();
}

private _start(total: number, payload: Progress.Payload = {}): void {
const start = once((bar: Progress.Bar, t: number, p: Progress.Payload = {}) => {
bar.start(t);
if (Object.keys(p).length) {
bar.update(0, p);
}
});
start(this.bar, total, payload);
}
}

export namespace Progress {
export type Bar = {
start: (num: number, payload?: unknown) => void;
update: (num: number, payload?: unknown) => void;
updateTotal: (num: number) => void;
setTotal: (num: number) => void;
stop: () => void;
};

export type Options = {
title: string;
format: string;
barCompleteChar: string;
barIncompleteChar: string;
linewrap: boolean;
/** output stream to use (default: process.stderr) */
stream?: NodeJS.WritableStream | undefined;
};

export type Payload = Record<string, unknown>;
}
Loading

0 comments on commit 8e58a93

Please sign in to comment.