diff --git a/src/command.ts b/src/command.ts index 4b4c0e9ab..bef7ea9f0 100644 --- a/src/command.ts +++ b/src/command.ts @@ -98,6 +98,9 @@ export abstract class Command { /** Hide the command from help */ public static hidden: boolean + /** An array of aliases for this command that are hidden from help. */ + public static hiddenAliases: string[] = [] + /** A command ID, used mostly in error or verbose reporting. */ public static id: string @@ -414,6 +417,7 @@ export namespace Command { flags: {[name: string]: Flag.Cached} hasDynamicHelp?: boolean hidden: boolean + hiddenAliases: string[] id: string isESM?: boolean permutations?: string[] diff --git a/src/config/config.ts b/src/config/config.ts index c1409e273..85abff098 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -784,17 +784,15 @@ export class Config implements IConfig { this.commandPermutations.add(permutation, command.id) } - // set command aliases - for (const alias of command.aliases ?? []) { + const handleAlias = (alias: string, hidden = false) => { if (this._commands.has(alias)) { const prioritizedCommand = this.determinePriority([this._commands.get(alias)!, command]) this._commands.set(alias, {...prioritizedCommand, id: alias}) } else { - this._commands.set(alias, {...command, id: alias}) + this._commands.set(alias, {...command, hidden, id: alias}) } // set every permutation of the aliases - // v3 moved command alias permutations to the manifest, but some plugins may not have // the new manifest yet. For those, we need to calculate the permutations here. const aliasPermutations = @@ -806,6 +804,16 @@ export class Config implements IConfig { this.commandPermutations.add(permutation, command.id) } } + + // set command aliases + for (const alias of command.aliases ?? []) { + handleAlias(alias) + } + + // set hidden command aliases + for (const alias of command.hiddenAliases ?? []) { + handleAlias(alias, true) + } } marker?.addDetails({commandCount: plugin.commands.length}) diff --git a/src/flags.ts b/src/flags.ts index 15183df86..55c15b991 100644 --- a/src/flags.ts +++ b/src/flags.ts @@ -1,4 +1,3 @@ -/* eslint-disable valid-jsdoc */ import {URL} from 'node:url' import {CLIError} from './errors' @@ -140,7 +139,9 @@ export const help = (opts: Partial> = {}): BooleanFlag): string { if (commands.length === 0) return '' - const body = this.renderList( - commands.map((c) => { - if (this.config.topicSeparator !== ':') c.id = c.id.replaceAll(':', this.config.topicSeparator) - return [c.id, this.summary(c)] - }), + commands + .filter((c) => (this.opts.hideAliasesFromRoot ? !c.aliases?.includes(c.id) : true)) + .map((c) => { + if (this.config.topicSeparator !== ':') c.id = c.id.replaceAll(':', this.config.topicSeparator) + return [c.id, this.summary(c)] + }), { indentation: 2, spacer: '\n', diff --git a/src/interfaces/help.ts b/src/interfaces/help.ts index 23023a65b..f0ca5c0e7 100644 --- a/src/interfaces/help.ts +++ b/src/interfaces/help.ts @@ -4,6 +4,10 @@ export interface HelpOptions { * Use docopts as the usage. Defaults to true. */ docopts?: boolean + /** + * If true, hide command aliases from the root help output. Defaults to false. + */ + hideAliasesFromRoot?: boolean /** * By default, the command summary is show at the top of the help and as the first line in * the command description. Repeating the summary in the command description improves readability diff --git a/src/main.ts b/src/main.ts index 4d9e155f9..169d6e3f9 100644 --- a/src/main.ts +++ b/src/main.ts @@ -70,7 +70,7 @@ export async function run(argv?: string[], options?: Interfaces.LoadOptions): Pr // display help version if applicable if (helpAddition(argv, config)) { const Help = await loadHelpClass(config) - const help = new Help(config, config.pjson.helpOptions) + const help = new Help(config, config.pjson.oclif.helpOptions ?? config.pjson.helpOptions) await help.showHelp(argv) await collectPerf() return diff --git a/src/util/cache-command.ts b/src/util/cache-command.ts index a49f92d5c..e48fe6184 100644 --- a/src/util/cache-command.ts +++ b/src/util/cache-command.ts @@ -95,15 +95,17 @@ export async function cacheCommand( const args = await cacheArgs(ensureArgObject(cmd.args), respectNoCacheDefault) const stdProperties = { - aliases: cmd.aliases || [], + aliases: cmd.aliases ?? [], args, deprecateAliases: cmd.deprecateAliases, deprecationOptions: cmd.deprecationOptions, description: cmd.description, - examples: cmd.examples || (cmd as any).example, + // Support both `examples` and `example` for backwards compatibility. + examples: cmd.examples ?? (cmd as unknown as {example: string}).example, flags, hasDynamicHelp: Object.values(flags).some((f) => f.hasDynamicHelp), hidden: cmd.hidden, + hiddenAliases: cmd.hiddenAliases ?? [], id: cmd.id, pluginAlias: plugin && plugin.alias, pluginName: plugin && plugin.name, diff --git a/test/config/config.flexible.test.ts b/test/config/config.flexible.test.ts index 1f51573be..2d95ea68a 100644 --- a/test/config/config.flexible.test.ts +++ b/test/config/config.flexible.test.ts @@ -58,6 +58,7 @@ describe('Config with flexible taxonomy', () => { flagA: Flags.boolean({char: 'a'}), }, hidden: false, + hiddenAliases: [], id: commandIds[0], async load(): Promise { return MyCommandClass @@ -74,6 +75,7 @@ describe('Config with flexible taxonomy', () => { flagB: Flags.boolean({}), }, hidden: false, + hiddenAliases: [], id: commandIds[1], async load(): Promise { return MyCommandClass diff --git a/test/config/config.test.ts b/test/config/config.test.ts index cc4b4b27b..82f014a5e 100644 --- a/test/config/config.test.ts +++ b/test/config/config.test.ts @@ -251,6 +251,7 @@ describe('Config', () => { args: {}, flags: {}, hidden: false, + hiddenAliases: [], id: commandIds[0], async load(): Promise { return MyCommandClass @@ -264,6 +265,7 @@ describe('Config', () => { args: {}, flags: {}, hidden: false, + hiddenAliases: [], id: commandIds[1], async load(): Promise { return MyCommandClass diff --git a/test/help/fixtures/fixtures.ts b/test/help/fixtures/fixtures.ts index 9ff601f9e..fe05e21ef 100644 --- a/test/help/fixtures/fixtures.ts +++ b/test/help/fixtures/fixtures.ts @@ -154,3 +154,12 @@ export class LongDescription extends Command { 'run' } } + +export class CommandWithAliases extends Command { + static aliases = ['bar', 'baz', 'qux'] + static description = 'This is a command with aliases' + static id = 'foo' + async run(): Promise { + 'run' + } +} diff --git a/test/help/show-help.test.ts b/test/help/show-help.test.ts index 9e44775de..01db70f07 100644 --- a/test/help/show-help.test.ts +++ b/test/help/show-help.test.ts @@ -13,6 +13,7 @@ import { AppsDestroy, AppsIndex, AppsTopic, + CommandWithAliases, DbCreate, DbTopic, DeprecateAliases, @@ -135,6 +136,69 @@ USAGE COMMANDS apps List all apps (app index command)`) }) + + test + .loadConfig() + .stdout() + .do(async (ctx) => { + const {config} = ctx + + monkeyPatchCommands(config, [ + { + name: 'plugin-1', + commands: [CommandWithAliases], + topics: [], + }, + ]) + + const help = new TestHelp(config as any, {hideAliasesFromRoot: true}) + await help.showHelp([]) + }) + .it('shows root help without aliases if hideAliasesFromRoot=true', ({stdout, config}) => { + expect(stdout.trim()).to.equal(`base library for oclif CLIs + +VERSION + ${config.userAgent} + +USAGE + $ oclif [COMMAND] + +COMMANDS + foo This is a command with aliases`) + }) + + test + .loadConfig() + .stdout() + .do(async (ctx) => { + const {config} = ctx + + monkeyPatchCommands(config, [ + { + name: 'plugin-1', + commands: [CommandWithAliases], + topics: [], + }, + ]) + + const help = new TestHelp(config as any) + await help.showHelp([]) + }) + .it('shows root help with aliases commands by default', ({stdout, config}) => { + expect(stdout.trim()).to.equal(`base library for oclif CLIs + +VERSION + ${config.userAgent} + +USAGE + $ oclif [COMMAND] + +COMMANDS + bar This is a command with aliases + baz This is a command with aliases + foo This is a command with aliases + qux This is a command with aliases`) + }) }) describe('showHelp for a topic', () => { diff --git a/test/integration/util.ts b/test/integration/util.ts index 005f92cee..fc2c3066e 100644 --- a/test/integration/util.ts +++ b/test/integration/util.ts @@ -113,7 +113,6 @@ export class Executor { } } -// eslint-disable-next-line valid-jsdoc /** * Setup for integration tests. * diff --git a/test/util/cache-command.test.ts b/test/util/cache-command.test.ts index 685bd8647..7b7e1246a 100644 --- a/test/util/cache-command.test.ts +++ b/test/util/cache-command.test.ts @@ -59,6 +59,7 @@ describe('cacheCommand', () => { state: undefined, description: 'test command', aliases: ['alias1', 'alias2'], + hiddenAliases: [], title: 'cmd title', usage: ['$ usage'], examples: undefined, @@ -193,6 +194,7 @@ describe('cacheCommand', () => { hidden: undefined, state: 'beta', aliases: ['base'], + hiddenAliases: [], examples: undefined, deprecationOptions: undefined, deprecateAliases: undefined,