From 81d22040f3506da115d0bfc68c51a80b8a7f6f91 Mon Sep 17 00:00:00 2001 From: Mike Donnalley Date: Fri, 23 Jul 2021 08:13:39 -0700 Subject: [PATCH] feat: support typings for sf (#157) --- src/execCmd.ts | 105 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 75 insertions(+), 30 deletions(-) diff --git a/src/execCmd.ts b/src/execCmd.ts index c59d6bea..f80df416 100644 --- a/src/execCmd.ts +++ b/src/execCmd.ts @@ -9,26 +9,29 @@ import { join as pathJoin, resolve as pathResolve } from 'path'; import { inspect } from 'util'; import { fs } from '@salesforce/core'; import { Duration, env, parseJson } from '@salesforce/kit'; -import { AnyJson, Dictionary, isNumber } from '@salesforce/ts-types'; +import { AnyJson, isNumber } from '@salesforce/ts-types'; import Debug from 'debug'; import * as shelljs from 'shelljs'; import { ExecCallback, ExecOptions, ShellString } from 'shelljs'; import stripAnsi = require('strip-ansi'); +type Collection = Record | Array>; + export interface ExecCmdOptions extends ExecOptions { /** * Throws if this exit code is not returned by the child process. */ ensureExitCode?: number; -} -type JsonOutput = Dictionary & { - status: number; - result: T; -}; + /** + * The base CLI that the plugin is used in. This is used primarily for changing the behavior + * of JSON parsing and types. + */ + cli?: 'sfdx' | 'sf'; +} -export interface ExecCmdResult { +export interface ExecCmdResult { /** * Command output from the shell. * @@ -36,10 +39,7 @@ export interface ExecCmdResult { */ shellOutput: ShellString; - /** - * Command output parsed as JSON, if `--json` param present. - */ - jsonOutput?: JsonOutput; + jsonOutput?: unknown; /** * The JsonParseError if parsing failed. @@ -52,6 +52,24 @@ export interface ExecCmdResult { execCmdDuration: Duration; } +export interface SfdxExecCmdResult extends ExecCmdResult { + /** + * Command output parsed as JSON, if `--json` param present. + */ + jsonOutput?: { status: number; result: T }; +} + +export interface SfExecCmdResult extends ExecCmdResult { + /** + * Command output parsed as JSON, if `--json` param present. + */ + jsonOutput?: T; +} + +const DEFAULT_EXEC_OPTIONS: ExecCmdOptions = { + cli: 'sfdx', +}; + const buildCmdOptions = (options?: ExecCmdOptions): ExecCmdOptions => { const defaults: shelljs.ExecOptions = { env: Object.assign({}, process.env), @@ -71,10 +89,10 @@ const hrtimeToMillisDuration = (hrTime: [number, number]) => Duration.milliseconds(hrTime[0] * Duration.MILLIS_IN_SECONDS + hrTime[1] / 1e6); // Add JSON output if json flag is set -const addJsonOutput = (cmd: string, result: ExecCmdResult): ExecCmdResult => { +const addJsonOutput = (cmd: string, result: T): T => { if (cmd.includes('--json')) { try { - result.jsonOutput = parseJson(stripAnsi(result.shellOutput.stdout)) as JsonOutput; + result.jsonOutput = parseJson(stripAnsi(result.shellOutput.stdout)) as unknown as U; } catch (parseErr: unknown) { result.jsonError = parseErr as Error; } @@ -117,7 +135,7 @@ const buildCmd = (cmdArgs: string): string => { return `${bin} ${cmdArgs}`; }; -const execCmdSync = (cmd: string, options?: ExecCmdOptions): ExecCmdResult => { +const execCmdSync = (cmd: string, options?: ExecCmdOptions): T => { const debug = Debug('testkit:execCmd'); // Add on the bin path @@ -127,10 +145,10 @@ const execCmdSync = (cmd: string, options?: ExecCmdOptions): ExecCmdResult debug(`Running cmd: ${cmd}`); debug(`Cmd options: ${inspect(cmdOptions)}`); - const result: ExecCmdResult = { + const result = { shellOutput: '' as ShellString, execCmdDuration: Duration.seconds(0), - }; + } as T; // Execute the command in a synchronous child process const startTime = process.hrtime(); @@ -142,16 +160,19 @@ const execCmdSync = (cmd: string, options?: ExecCmdOptions): ExecCmdResult throw getExitCodeError(cmd, cmdOptions.ensureExitCode, result.shellOutput); } - return addJsonOutput(cmd, result); + return addJsonOutput(cmd, result); }; -const execCmdAsync = async (cmd: string, options: ExecCmdOptions): Promise> => { +const execCmdAsync = async ( + cmd: string, + options: ExecCmdOptions +): Promise => { const debug = Debug('testkit:execCmdAsync'); // Add on the bin path cmd = buildCmd(cmd); - const resultPromise = new Promise>((resolve, reject) => { + const resultPromise = new Promise((resolve, reject) => { const cmdOptions = buildCmdOptions(options); debug(`Running cmd: ${cmd}`); @@ -168,15 +189,15 @@ const execCmdAsync = async (cmd: string, options: ExecCmdOptions): Promise = { + const result = { shellOutput: new ShellString(stdout), execCmdDuration, - }; + } as T; result.shellOutput.code = code; result.shellOutput.stdout = stripAnsi(stdout); result.shellOutput.stderr = stripAnsi(stderr); - resolve(addJsonOutput(cmd, result)); + resolve(addJsonOutput(cmd, result)); }; // Execute the command async in a child process @@ -205,7 +226,15 @@ const execCmdAsync = async (cmd: string, options: ExecCmdOptions): Promise(cmd: string, options?: ExecCmdOptions & { async?: false }): ExecCmdResult; +export function execCmd( + cmd: string, + options?: ExecCmdOptions & { async?: false; cli?: 'sfdx' } +): SfdxExecCmdResult; + +export function execCmd( + cmd: string, + options?: ExecCmdOptions & { async?: false; cli?: 'sf' } +): SfExecCmdResult; /** * Asynchronously execute a command with the provided options in a child process. @@ -225,15 +254,31 @@ export function execCmd(cmd: string, options?: ExecCmdOptions & { a * @param options The options used to run the command. * @returns The child process exit code, stdout, stderr, cmd run time, and the parsed JSON if `--json` param present. */ -export function execCmd(cmd: string, options: ExecCmdOptions & { async: true }): Promise>; +export function execCmd( + cmd: string, + options: ExecCmdOptions & { async: true; cli?: 'sfdx' } +): Promise>; -export function execCmd( +export function execCmd( cmd: string, - options?: ExecCmdOptions -): ExecCmdResult | Promise> { - if (options?.async) { - return execCmdAsync(cmd, options); + options: ExecCmdOptions & { async: true; cli?: 'sf' } +): Promise>; + +export function execCmd( + cmd: string, + options: ExecCmdOptions = DEFAULT_EXEC_OPTIONS +): SfdxExecCmdResult | Promise> | SfExecCmdResult | Promise> { + if (options.cli === 'sf') { + if (options.async) { + return execCmdAsync, T>(cmd, options); + } else { + return execCmdSync, T>(cmd, options); + } } else { - return execCmdSync(cmd, options); + if (options.async) { + return execCmdAsync, T>(cmd, options); + } else { + return execCmdSync, T>(cmd, options); + } } }