diff --git a/package.json b/package.json index 6e863d56..9e2facda 100644 --- a/package.json +++ b/package.json @@ -114,4 +114,4 @@ "pretest": "yarn build --noEmit && tsc -p test --noEmit --skipLibCheck" }, "types": "lib/index.d.ts" -} \ No newline at end of file +} diff --git a/src/command.ts b/src/command.ts index 7efd940d..f054a507 100644 --- a/src/command.ts +++ b/src/command.ts @@ -9,7 +9,7 @@ import {PrettyPrintableError} from './errors' import * as Parser from './parser' import * as Flags from './flags' import {Deprecation} from './interfaces/parser' -import {formatCommandDeprecationWarning, formatFlagDeprecationWarning} from './help/util' +import {formatCommandDeprecationWarning, formatFlagDeprecationWarning, normalizeArgv} from './help/util' const pjson = require('../package.json') @@ -62,6 +62,11 @@ export default abstract class Command { static deprecationOptions?: Deprecation; + /** + * Emit deprecation warning when a command alias is used + */ + static deprecateAliases?: boolean + /** * An override string (or strings) for the default usage documentation. */ @@ -255,10 +260,27 @@ export default abstract class Command { if (deprecated) { this.warn(formatFlagDeprecationWarning(flag, deprecated)) } + + const deprecateAliases = this.ctor.flags[flag]?.deprecateAliases + const aliases = (this.ctor.flags[flag]?.aliases ?? []).map(a => a.length === 1 ? `-${a}` : `--${a}`) + if (deprecateAliases && aliases.length > 0) { + const foundAliases = this.argv.filter(a => aliases.includes(a)) + for (const alias of foundAliases) { + this.warn(formatFlagDeprecationWarning(alias, {to: this.ctor.flags[flag]?.name})) + } + } } } protected warnIfCommandDeprecated(): void { + const [id] = normalizeArgv(this.config) + + if (this.ctor.deprecateAliases && this.ctor.aliases.includes(id)) { + const cmdName = toConfiguredId(this.ctor.id, this.config) + const aliasName = toConfiguredId(id, this.config) + this.warn(formatCommandDeprecationWarning(aliasName, {to: cmdName})) + } + if (this.ctor.state === 'deprecated') { const cmdName = toConfiguredId(this.ctor.id, this.config) this.warn(formatCommandDeprecationWarning(cmdName, this.ctor.deprecationOptions)) diff --git a/src/config/config.ts b/src/config/config.ts index f24a335e..6ca1e573 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -754,6 +754,7 @@ export async function toCached(c: Command.Class, plugin?: IPlugin): Promise> = {}) => { description: 'Show CLI help.', ...opts, parse: async (_: any, cmd: Command) => { - new Help(cmd.config).showHelp(cmd.argv) + new Help(cmd.config).showHelp(cmd.id ? [cmd.id, ...cmd.argv] : cmd.argv) cmd.exit(0) }, }) diff --git a/src/help/index.ts b/src/help/index.ts index 93df1ccb..569113cc 100644 --- a/src/help/index.ts +++ b/src/help/index.ts @@ -9,7 +9,7 @@ import {formatCommandDeprecationWarning, getHelpFlagAdditions, standardizeIDFrom import {HelpFormatter} from './formatter' import {toCached} from '../config/config' export {CommandHelp} from './command' -export {standardizeIDFromArgv, loadHelpClass, getHelpFlagAdditions} from './util' +export {standardizeIDFromArgv, loadHelpClass, getHelpFlagAdditions, normalizeArgv} from './util' function getHelpSubject(args: string[], config: Interfaces.Config): string | undefined { // for each help flag that starts with '--' create a new flag with same name sans '--' diff --git a/src/help/util.ts b/src/help/util.ts index 95c59420..e84357d6 100644 --- a/src/help/util.ts +++ b/src/help/util.ts @@ -108,7 +108,7 @@ export function formatFlagDeprecationWarning(flag: string, opts: true | Deprecat message += ` and will be removed in version ${opts.version}` } - message += opts.to ? `. Use "${opts.to}" instead.` : '.' + message += opts.to ? `. Use "--${opts.to}" instead.` : '.' return message } @@ -127,3 +127,8 @@ export function formatCommandDeprecationWarning(command: string, opts?: Deprecat return message } + +export function normalizeArgv(config: IConfig, argv = process.argv.slice(2)): string[] { + if (config.topicSeparator !== ':' && !argv[0]?.includes(':')) argv = standardizeIDFromArgv(argv, config) + return argv +} diff --git a/src/interfaces/command.ts b/src/interfaces/command.ts index 438837be..52446b93 100644 --- a/src/interfaces/command.ts +++ b/src/interfaces/command.ts @@ -22,6 +22,11 @@ export interface CommandProps { */ deprecationOptions?: Deprecation; + /** + * Emit a deprecation warning when a command alias is used. + */ + deprecateAliases?: boolean + /** An array of aliases for this command. */ aliases: string[]; diff --git a/src/interfaces/parser.ts b/src/interfaces/parser.ts index 54090a90..6b479627 100644 --- a/src/interfaces/parser.ts +++ b/src/interfaces/parser.ts @@ -157,6 +157,10 @@ export type FlagProps = { * Alternate names that can be used for this flag. */ aliases?: string[]; + /** + * Emit deprecation warning when a flag alias is provided + */ + deprecateAliases?: boolean } export type BooleanFlagProps = FlagProps & { diff --git a/src/main.ts b/src/main.ts index 21b17700..c3e05850 100644 --- a/src/main.ts +++ b/src/main.ts @@ -5,7 +5,7 @@ import {format, inspect} from 'util' import * as Interfaces from './interfaces' import {URL} from 'url' import {Config} from './config' -import {getHelpFlagAdditions, loadHelpClass, standardizeIDFromArgv} from './help' +import {getHelpFlagAdditions, loadHelpClass, normalizeArgv} from './help' const log = (message = '', ...args: any[]) => { // tslint:disable-next-line strict-type-predicates @@ -41,8 +41,7 @@ export async function run(argv = process.argv.slice(2), options?: Interfaces.Loa // return Main.run(argv, options) const config = await Config.load(options || (module.parent && module.parent.parent && module.parent.parent.filename) || __dirname) as Config - if (config.topicSeparator !== ':' && !argv[0]?.includes(':')) argv = standardizeIDFromArgv(argv, config) - let [id, ...argvSlice] = argv + let [id, ...argvSlice] = normalizeArgv(config, argv) // run init hook await config.runHook('init', {id, argv: argvSlice}) diff --git a/src/parser/parse.ts b/src/parser/parse.ts index c58df745..ffd8c7dc 100644 --- a/src/parser/parse.ts +++ b/src/parser/parse.ts @@ -79,8 +79,12 @@ export class Parser { - return Object.keys(this.input.flags).find(k => this.input.flags[k].char === arg[1]) + const findShortFlag = ([_, char]: string) => { + if (this.flagAliases[char]) { + return this.flagAliases[char].name + } + + return Object.keys(this.input.flags).find(k => this.input.flags[k].char === char) } const parseFlag = (arg: string): boolean => { diff --git a/test/command/command.test.ts b/test/command/command.test.ts index e8b7629a..f6f21182 100644 --- a/test/command/command.test.ts +++ b/test/command/command.test.ts @@ -233,6 +233,65 @@ describe('command', () => { .it('uses util.format()') }) + describe('flags with deprecated aliases', () => { + class CMD extends Command { + static flags = { + name: Flags.string({ + aliases: ['username', 'target-user', 'u'], + deprecateAliases: true, + }), + other: Flags.string(), + } + + async run() { + await this.parse(CMD) + this.log('running command') + } + } + + fancy + .stdout() + .stderr() + .do(async () => CMD.run(['--username', 'astro'])) + .do(ctx => expect(ctx.stderr).to.include('Warning: The "--username" flag has been deprecated')) + .it('shows warning for deprecated flag alias') + + fancy + .stdout() + .stderr() + .do(async () => CMD.run(['--target-user', 'astro'])) + .do(ctx => expect(ctx.stderr).to.include('Warning: The "--target-user" flag has been deprecated')) + .it('shows warning for deprecated flag alias') + + fancy + .stdout() + .stderr() + .do(async () => CMD.run(['-u', 'astro'])) + .do(ctx => expect(ctx.stderr).to.include('Warning: The "-u" flag has been deprecated')) + .it('shows warning for deprecated short char flag alias') + + fancy + .stdout() + .stderr() + .do(async () => CMD.run(['--name', 'username'])) + .do(ctx => expect(ctx.stderr).to.be.empty) + .it('shows no warning when using proper flag name with a value that matches a flag alias') + + fancy + .stdout() + .stderr() + .do(async () => CMD.run(['--other', 'target-user'])) + .do(ctx => expect(ctx.stderr).to.be.empty) + .it('shows no warning when using another flag with a value that matches a deprecated flag alias') + + fancy + .stdout() + .stderr() + .do(async () => CMD.run(['--name', 'u'])) + .do(ctx => expect(ctx.stderr).to.be.empty) + .it('shows no warning when proper flag name with a value that matches a short char flag alias') + }) + describe('deprecated flags', () => { fancy .stdout()