Repository: oclif/core
Pull request #416 (2022-05-05 20:41:58)

feat: improve Command interface
[skip-validate-pr]


Patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
index f73e80a..89ae832 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -19,6 +19,11 @@ workflows:
                 - lts
                 - maintenance
             alias: unit-tests
+            exclude:
+              - os: windows
+                node_version: latest
+              - os: windows
+                node_version: maintenance
       - release-management/test-package:
           name: release-management/test-package-yarn test:e2e-<< matrix.node_version >>-<< matrix.os >>
           command: yarn test:e2e
@@ -32,6 +37,11 @@ workflows:
                 - lts
                 - maintenance
             alias: integration-tests
+            exclude:
+              - os: windows
+                node_version: latest
+              - os: windows
+                node_version: maintenance
       - release-management/release-package:
           context: SF-CLI-RELEASE-PROCESS
           filters:
diff --git a/src/config/config.ts b/src/config/config.ts
index 45f0fff..874aaf9 100644
--- a/src/config/config.ts
+++ b/src/config/config.ts
@@ -120,7 +120,7 @@ export class Config implements IConfig {
 
   private topicPermutations = new Permutations()
 
-  private _commands = new Map<string, Command.Plugin>()
+  private _commands = new Map<string, Command.Loadable>()
 
   private _topics = new Map<string, Topic>()
 
@@ -321,7 +321,7 @@ export class Config implements IConfig {
   }
 
   // eslint-disable-next-line default-param-last
-  async runCommand<T = unknown>(id: string, argv: string[] = [], cachedCommand?: Command.Plugin): Promise<T> {
+  async runCommand<T = unknown>(id: string, argv: string[] = [], cachedCommand?: Command.Loadable): Promise<T> {
     debug('runCommand %s %o', id, argv)
     const c = cachedCommand || this.findCommand(id)
     if (!c) {
@@ -361,11 +361,11 @@ export class Config implements IConfig {
     .toUpperCase()
   }
 
-  findCommand(id: string, opts: { must: true }): Command.Plugin
+  findCommand(id: string, opts: { must: true }): Command.Loadable
 
-  findCommand(id: string, opts?: { must: boolean }): Command.Plugin | undefined
+  findCommand(id: string, opts?: { must: boolean }): Command.Loadable | undefined
 
-  findCommand(id: string, opts: { must?: boolean } = {}): Command.Plugin | undefined {
+  findCommand(id: string, opts: { must?: boolean } = {}): Command.Loadable | undefined {
     const lookupId = this.getCmdLookupId(id)
     const command = this._commands.get(lookupId)
     if (opts.must && !command) error(`command ${lookupId} not found`)
@@ -396,7 +396,7 @@ export class Config implements IConfig {
    * @param argv string[] process.argv containing the flags and arguments provided by the user
    * @returns string[]
    */
-  findMatches(partialCmdId: string, argv: string[]): Command.Plugin[] {
+  findMatches(partialCmdId: string, argv: string[]): Command.Loadable[] {
     const flags = argv.filter(arg => !getHelpFlagAdditions(this).includes(arg) && arg.startsWith('-')).map(a => a.replace(/-/g, ''))
     const possibleMatches = [...this.commandPermutations.get(partialCmdId)].map(k => this._commands.get(k)!)
 
@@ -414,9 +414,9 @@ export class Config implements IConfig {
 
   /**
    * Returns an array of all commands. If flexible taxonomy is enabled then all permutations will be appended to the array.
-   * @returns Command.Plugin[]
+   * @returns Command.Loadable[]
    */
-  getAllCommands(): Command.Plugin[] {
+  getAllCommands(): Command.Loadable[] {
     const commands = [...this._commands.values()]
     const validPermutations = [...this.commandPermutations.getAllValid()]
     for (const permutation of validPermutations) {
@@ -437,7 +437,7 @@ export class Config implements IConfig {
     return this.getAllCommands().map(c => c.id)
   }
 
-  get commands(): Command.Plugin[] {
+  get commands(): Command.Loadable[] {
     return [...this._commands.values()]
   }
 
@@ -675,9 +675,9 @@ export class Config implements IConfig {
    * plugin as discovered (will not change the order)
    *
    * @param commands commands to determine the priority of
-   * @returns command instance {Command.Plugin} or undefined
+   * @returns command instance {Command.Loadable} or undefined
    */
-  private determinePriority(commands: Command.Plugin[]): Command.Plugin {
+  private determinePriority(commands: Command.Loadable[]): Command.Loadable {
     const oclifPlugins = this.pjson.oclif?.plugins ?? []
     const commandPlugins = commands.sort((a, b) => {
       const pluginAliasA = a.pluginAlias ?? 'A-Cannot-Find-This'
diff --git a/src/config/plugin.ts b/src/config/plugin.ts
index 1885f87..1d9aa2a 100644
--- a/src/config/plugin.ts
+++ b/src/config/plugin.ts
@@ -112,7 +112,7 @@ export class Plugin implements IPlugin {
 
   manifest!: Manifest
 
-  commands!: Command.Plugin[]
+  commands!: Command.Loadable[]
 
   hooks!: {[k: string]: string[]}
 
diff --git a/src/interfaces/command.ts b/src/interfaces/command.ts
index 6236e1c..cf9fe58 100644
--- a/src/interfaces/command.ts
+++ b/src/interfaces/command.ts
@@ -1,5 +1,6 @@
 import {Config, LoadOptions} from './config'
 import {ArgInput, BooleanFlagProps, FlagInput, OptionFlagProps} from './parser'
+import {Plugin as IPlugin} from './plugin'
 
 export type Example = string | {
   description: string;
@@ -92,7 +93,7 @@ export namespace Command {
   }
 
   export interface Class extends Base {
-    plugin?: Plugin;
+    plugin?: IPlugin;
     flags?: FlagInput<any>;
     args?: ArgInput;
     strict: boolean;
@@ -106,6 +107,13 @@ export namespace Command {
     _run(argv: string[]): Promise<any>;
   }
 
+  export interface Loadable extends Command {
+    load(): Promise<Class>;
+  }
+
+  /**
+   * @deprecated use Command.Loadable instead.
+   */
   export interface Plugin extends Command {
     load(): Promise<Class>;
   }
diff --git a/src/interfaces/config.ts b/src/interfaces/config.ts
index 754f9f1..1cd6f3a 100644
--- a/src/interfaces/config.ts
+++ b/src/interfaces/config.ts
@@ -90,20 +90,20 @@ export interface Config {
   valid: boolean;
   flexibleTaxonomy?: boolean;
   topicSeparator: ':' | ' ';
-  readonly commands: Command.Plugin[];
+  readonly commands: Command.Loadable[];
   readonly topics: Topic[];
   readonly commandIDs: string[];
 
   runCommand<T = unknown>(id: string, argv?: string[]): Promise<T>;
-  runCommand<T = unknown>(id: string, argv?: string[], cachedCommand?: Command.Plugin): Promise<T>;
+  runCommand<T = unknown>(id: string, argv?: string[], cachedCommand?: Command.Loadable): Promise<T>;
   runHook<T extends keyof Hooks>(event: T, opts: Hooks[T]['options'], timeout?: number): Promise<Hook.Result<Hooks[T]['return']>>;
   getAllCommandIDs(): string[]
-  getAllCommands(): Command.Plugin[]
-  findCommand(id: string, opts: { must: true }): Command.Plugin;
-  findCommand(id: string, opts?: { must: boolean }): Command.Plugin | undefined;
+  getAllCommands(): Command.Loadable[]
+  findCommand(id: string, opts: { must: true }): Command.Loadable;
+  findCommand(id: string, opts?: { must: boolean }): Command.Loadable | undefined;
   findTopic(id: string, opts: { must: true }): Topic;
   findTopic(id: string, opts?: { must: boolean }): Topic | undefined;
-  findMatches(id: string, argv: string[]): Command.Plugin[];
+  findMatches(id: string, argv: string[]): Command.Loadable[];
   scopedEnvVar(key: string): string | undefined;
   scopedEnvVarKey(key: string): string;
   scopedEnvVarTrue(key: string): boolean;
diff --git a/src/interfaces/hooks.ts b/src/interfaces/hooks.ts
index fbaa579..59e1ea6 100644
--- a/src/interfaces/hooks.ts
+++ b/src/interfaces/hooks.ts
@@ -38,7 +38,7 @@ export interface Hooks {
     return: unknown;
   };
   'command_incomplete': {
-    options: {id: string; argv: string[], matches: Command.Plugin[]};
+    options: {id: string; argv: string[], matches: Command.Loadable[]};
     return: unknown;
   };
   'plugins:preinstall': {
diff --git a/src/interfaces/plugin.ts b/src/interfaces/plugin.ts
index 28ef3b1..836eb63 100644
--- a/src/interfaces/plugin.ts
+++ b/src/interfaces/plugin.ts
@@ -19,6 +19,7 @@ export interface Options extends PluginOptions {
   channel?: string;
   version?: string;
 }
+
 export interface Plugin {
   /**
    * ../config version
@@ -63,7 +64,7 @@ export interface Plugin {
    */
   valid: boolean;
 
-  commands: Command.Plugin[];
+  commands: Command.Loadable[];
   hooks: { [k: string]: string[] };
   readonly commandIDs: string[];
   readonly topics: Topic[];


Test patch:
diff --git a/test/config/config.flexible.test.ts b/test/config/config.flexible.test.ts
index 47f79fc..75ca543 100644
--- a/test/config/config.flexible.test.ts
+++ b/test/config/config.flexible.test.ts
@@ -60,7 +60,7 @@ describe('Config with flexible taxonomy', () => {
       return new MyComandClass() as unknown as ICommand.Class
     }
 
-    const commandPluginA: ICommand.Plugin = {
+    const commandPluginA: ICommand.Loadable = {
       strict: false,
       aliases: [],
       args: [],
@@ -75,7 +75,7 @@ describe('Config with flexible taxonomy', () => {
       pluginType: types[0] ?? 'core',
       pluginAlias: '@My/plugina',
     }
-    const commandPluginB: ICommand.Plugin = {
+    const commandPluginB: ICommand.Loadable = {
       strict: false,
       aliases: [],
       args: [],
diff --git a/test/config/config.test.ts b/test/config/config.test.ts
index 2127811..dd4fbe9 100644
--- a/test/config/config.test.ts
+++ b/test/config/config.test.ts
@@ -197,7 +197,7 @@ describe('Config', () => {
         return new MyComandClass()
       }
 
-      const commandPluginA: ICommand.Plugin = {
+      const commandPluginA: ICommand.Loadable = {
         strict: false,
         aliases: [], args: [], flags: {}, hidden: false, id: commandIds[0], async load(): Promise<ICommand.Class> {
           return new MyComandClass() as unknown as ICommand.Class
@@ -205,7 +205,7 @@ describe('Config', () => {
         pluginType: types[0] ?? 'core',
         pluginAlias: '@My/plugina',
       }
-      const commandPluginB: ICommand.Plugin = {
+      const commandPluginB: ICommand.Loadable = {
         strict: false,
         aliases: [], args: [], flags: {}, hidden: false, id: commandIds[1], async load(): Promise<ICommand.Class> {
           return new MyComandClass() as unknown as ICommand.Class