Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: expose ux methods in SfCommand #25

Merged
merged 3 commits into from
Jan 27, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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();
RodEsp marked this conversation as resolved.
Show resolved Hide resolved
}

/**
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