Skip to content

Commit

Permalink
feat: adds deprecation option for commands
Browse files Browse the repository at this point in the history
  • Loading branch information
laggingreflex committed Apr 29, 2020
1 parent 1f26de8 commit 027a636
Show file tree
Hide file tree
Showing 7 changed files with 106 additions and 24 deletions.
1 change: 1 addition & 0 deletions docs/advanced.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ simply needs to export:
* `exports.describe`: string used as the description for the command in help text, use `false` for a hidden command
* `exports.builder`: object declaring the options the command accepts, or a function accepting and returning a yargs instance
* `exports.handler`: a function which will be passed the parsed argv.
* `exports.deprecated`: a boolean (or string) to show deprecation notice.

```js
// my-module.js
Expand Down
9 changes: 5 additions & 4 deletions lib/command.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ module.exports = function command (yargs, usage, validation, globalMiddleware) {
let defaultCommand
globalMiddleware = globalMiddleware || []

self.addHandler = function addHandler (cmd, description, builder, handler, commandMiddleware) {
self.addHandler = function addHandler (cmd, description, builder, handler, commandMiddleware, deprecated) {
let aliases = []
const middlewares = commandMiddlewareFactory(commandMiddleware)
handler = handler || (() => {})
Expand All @@ -30,13 +30,13 @@ module.exports = function command (yargs, usage, validation, globalMiddleware) {
} else if (typeof cmd === 'object') {
let command = (Array.isArray(cmd.command) || typeof cmd.command === 'string') ? cmd.command : moduleName(cmd)
if (cmd.aliases) command = [].concat(command).concat(cmd.aliases)
self.addHandler(command, extractDesc(cmd), cmd.builder, cmd.handler, cmd.middlewares)
self.addHandler(command, extractDesc(cmd), cmd.builder, cmd.handler, cmd.middlewares, cmd.deprecated)
return
}

// allow a module to be provided instead of separate builder and handler
if (typeof builder === 'object' && builder.builder && typeof builder.handler === 'function') {
self.addHandler([cmd].concat(aliases), description, builder.builder, builder.handler, builder.middlewares)
self.addHandler([cmd].concat(aliases), description, builder.builder, builder.handler, builder.middlewares, builder.deprecated)
return
}

Expand Down Expand Up @@ -72,7 +72,7 @@ module.exports = function command (yargs, usage, validation, globalMiddleware) {
})

if (description !== false) {
usage.command(cmd, description, isDefault, aliases)
usage.command(cmd, description, isDefault, aliases, deprecated)
}

handlers[parsedCommand.cmd] = {
Expand All @@ -81,6 +81,7 @@ module.exports = function command (yargs, usage, validation, globalMiddleware) {
handler,
builder: builder || {},
middlewares,
deprecated,
demanded: parsedCommand.demanded,
optional: parsedCommand.optional
}
Expand Down
2 changes: 1 addition & 1 deletion lib/types/frozen-usage-instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ export interface FrozenUsageInstance {
usageDisabled: boolean
epilogs: string[]
examples: [string, string][]
commands: [string, string, boolean, string[]][]
commands: [string, string, boolean, string[], boolean][]
descriptions: Dictionary<string | undefined>
}
4 changes: 2 additions & 2 deletions lib/types/usage-instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { YError } from '../yerror'
export interface UsageInstance {
cacheHelpMessage (): void
clearCachedHelpMessage (): void
command (cmd: string, description: string | undefined, isDefault: boolean, aliases: string[]): void
command (cmd: string, description: string | undefined, isDefault: boolean, aliases: string[], deprecated: boolean): void
deferY18nLookup (str: string): string
describe (key: string, desc?: string): void
describe (keys: Dictionary<string>): void
Expand All @@ -16,7 +16,7 @@ export interface UsageInstance {
failFn (f: FailureFunction): void
freeze (): void
functionDescription (fn: { name?: string }): string
getCommands (): [string, string, boolean, string[]][]
getCommands (): [string, string, boolean, string[], boolean][]
getDescriptions (): Dictionary<string | undefined>
getPositionalGroupName (): string
getUsage (): [string, string][]
Expand Down
13 changes: 10 additions & 3 deletions lib/usage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,16 +96,16 @@ export function usage (yargs: YargsInstance, y18n: Y18N) {
examples.push([cmd, description || ''])
}

let commands: [string, string, boolean, string[]][] = []
self.command = function command (cmd, description, isDefault, aliases) {
let commands: [string, string, boolean, string[], boolean][] = []
self.command = function command (cmd, description, isDefault, aliases, deprecated = false) {
// the last default wins, so cancel out any previously set default
if (isDefault) {
commands = commands.map((cmdArray) => {
cmdArray[2] = false
return cmdArray
})
}
commands.push([cmd, description || '', isDefault, aliases])
commands.push([cmd, description || '', isDefault, aliases, deprecated])
}
self.getCommands = () => commands

Expand Down Expand Up @@ -224,6 +224,13 @@ export function usage (yargs: YargsInstance, y18n: Y18N) {
if (command[3] && command[3].length) {
hints.push(`[${__('aliases:')} ${command[3].join(', ')}]`)
}
if (command[4]) {
if (typeof command[4] === 'string') {
hints.push(`[${__('deprecated: %s', command[4])}]`)
} else {
hints.push(`[${__('deprecated')}]`)
}
}
if (hints.length) {
ui.div({ text: hints.join(' '), padding: [0, 0, 0, 2], align: 'right' })
} else {
Expand Down
95 changes: 84 additions & 11 deletions test/command.js
Original file line number Diff line number Diff line change
Expand Up @@ -276,21 +276,23 @@ describe('Command', () => {
const desc = 'i\'m not feeling very creative at the moment'
const isDefault = false
const aliases = []
const deprecated = false

const y = yargs([]).command(cmd, desc)
const commands = y.getUsageInstance().getCommands()
commands[0].should.deep.equal([cmd, desc, isDefault, aliases])
commands[0].should.deep.equal([cmd, desc, isDefault, aliases, deprecated])
})

it('accepts array, string as first 2 arguments', () => {
const aliases = ['bar', 'baz']
const cmd = 'foo <qux>'
const desc = 'i\'m not feeling very creative at the moment'
const isDefault = false
const deprecated = false

const y = yargs([]).command([cmd].concat(aliases), desc)
const usageCommands = y.getUsageInstance().getCommands()
usageCommands[0].should.deep.equal([cmd, desc, isDefault, aliases])
usageCommands[0].should.deep.equal([cmd, desc, isDefault, aliases, deprecated])
const cmdCommands = y.getCommandInstance().getCommands()
cmdCommands.should.deep.equal(['foo', 'bar', 'baz'])
})
Expand Down Expand Up @@ -381,14 +383,15 @@ describe('Command', () => {
}
const isDefault = false
const aliases = []
const deprecated = false

const y = yargs([]).command(module)
const handlers = y.getCommandInstance().getCommandHandlers()
handlers.foo.original.should.equal(module.command)
handlers.foo.builder.should.equal(module.builder)
handlers.foo.handler.should.equal(module.handler)
const commands = y.getUsageInstance().getCommands()
commands[0].should.deep.equal([module.command, module.describe, isDefault, aliases])
commands[0].should.deep.equal([module.command, module.describe, isDefault, aliases, deprecated])
})

it('accepts module (description key, builder function) as 1st argument', () => {
Expand All @@ -400,14 +403,15 @@ describe('Command', () => {
}
const isDefault = false
const aliases = []
const deprecated = false

const y = yargs([]).command(module)
const handlers = y.getCommandInstance().getCommandHandlers()
handlers.foo.original.should.equal(module.command)
handlers.foo.builder.should.equal(module.builder)
handlers.foo.handler.should.equal(module.handler)
const commands = y.getUsageInstance().getCommands()
commands[0].should.deep.equal([module.command, module.description, isDefault, aliases])
commands[0].should.deep.equal([module.command, module.description, isDefault, aliases, deprecated])
})

it('accepts module (desc key, builder function) as 1st argument', () => {
Expand All @@ -419,14 +423,15 @@ describe('Command', () => {
}
const isDefault = false
const aliases = []
const deprecated = false

const y = yargs([]).command(module)
const handlers = y.getCommandInstance().getCommandHandlers()
handlers.foo.original.should.equal(module.command)
handlers.foo.builder.should.equal(module.builder)
handlers.foo.handler.should.equal(module.handler)
const commands = y.getUsageInstance().getCommands()
commands[0].should.deep.equal([module.command, module.desc, isDefault, aliases])
commands[0].should.deep.equal([module.command, module.desc, isDefault, aliases, deprecated])
})

it('accepts module (false describe, builder function) as 1st argument', () => {
Expand Down Expand Up @@ -475,14 +480,15 @@ describe('Command', () => {
}
const isDefault = false
const aliases = []
const deprecated = false

const y = yargs([]).command(module)
const handlers = y.getCommandInstance().getCommandHandlers()
handlers.foo.original.should.equal(module.command)
handlers.foo.builder.should.equal(module.builder)
handlers.foo.handler.should.equal(module.handler)
const commands = y.getUsageInstance().getCommands()
commands[0].should.deep.equal([module.command, module.describe, isDefault, aliases])
commands[0].should.deep.equal([module.command, module.describe, isDefault, aliases, deprecated])
})

it('accepts module (missing handler function) as 1st argument', () => {
Expand All @@ -497,14 +503,15 @@ describe('Command', () => {
}
const isDefault = false
const aliases = []
const deprecated = false

const y = yargs([]).command(module)
const handlers = y.getCommandInstance().getCommandHandlers()
handlers.foo.original.should.equal(module.command)
handlers.foo.builder.should.equal(module.builder)
expect(typeof handlers.foo.handler).to.equal('function')
const commands = y.getUsageInstance().getCommands()
commands[0].should.deep.equal([module.command, module.describe, isDefault, aliases])
commands[0].should.deep.equal([module.command, module.describe, isDefault, aliases, deprecated])
})

it('accepts module (with command array) as 1st argument', () => {
Expand All @@ -515,14 +522,15 @@ describe('Command', () => {
handler (argv) {}
}
const isDefault = false
const deprecated = false

const y = yargs([]).command(module)
const handlers = y.getCommandInstance().getCommandHandlers()
handlers.foo.original.should.equal(module.command[0])
handlers.foo.builder.should.equal(module.builder)
handlers.foo.handler.should.equal(module.handler)
const usageCommands = y.getUsageInstance().getCommands()
usageCommands[0].should.deep.equal([module.command[0], module.describe, isDefault, ['bar', 'baz']])
usageCommands[0].should.deep.equal([module.command[0], module.describe, isDefault, ['bar', 'baz'], deprecated])
const cmdCommands = y.getCommandInstance().getCommands()
cmdCommands.should.deep.equal(['foo', 'bar', 'baz'])
})
Expand All @@ -536,14 +544,15 @@ describe('Command', () => {
handler (argv) {}
}
const isDefault = false
const deprecated = false

const y = yargs([]).command(module)
const handlers = y.getCommandInstance().getCommandHandlers()
handlers.foo.original.should.equal(module.command)
handlers.foo.builder.should.equal(module.builder)
handlers.foo.handler.should.equal(module.handler)
const usageCommands = y.getUsageInstance().getCommands()
usageCommands[0].should.deep.equal([module.command, module.describe, isDefault, module.aliases])
usageCommands[0].should.deep.equal([module.command, module.describe, isDefault, module.aliases, deprecated])
const cmdCommands = y.getCommandInstance().getCommands()
cmdCommands.should.deep.equal(['foo', 'bar', 'baz'])
})
Expand All @@ -557,14 +566,15 @@ describe('Command', () => {
handler (argv) {}
}
const isDefault = false
const deprecated = false

const y = yargs([]).command(module)
const handlers = y.getCommandInstance().getCommandHandlers()
handlers.foo.original.should.equal(module.command[0])
handlers.foo.builder.should.equal(module.builder)
handlers.foo.handler.should.equal(module.handler)
const usageCommands = y.getUsageInstance().getCommands()
usageCommands[0].should.deep.equal([module.command[0], module.describe, isDefault, ['bar', 'baz', 'nat']])
usageCommands[0].should.deep.equal([module.command[0], module.describe, isDefault, ['bar', 'baz', 'nat'], deprecated])
const cmdCommands = y.getCommandInstance().getCommands()
cmdCommands.should.deep.equal(['foo', 'bar', 'baz', 'nat'])
})
Expand All @@ -578,17 +588,29 @@ describe('Command', () => {
handler (argv) {}
}
const isDefault = false
const deprecated = false

const y = yargs([]).command(module)
const handlers = y.getCommandInstance().getCommandHandlers()
handlers.foo.original.should.equal(module.command)
handlers.foo.builder.should.equal(module.builder)
handlers.foo.handler.should.equal(module.handler)
const usageCommands = y.getUsageInstance().getCommands()
usageCommands[0].should.deep.equal([module.command, module.describe, isDefault, ['bar']])
usageCommands[0].should.deep.equal([module.command, module.describe, isDefault, ['bar'], deprecated])
const cmdCommands = y.getCommandInstance().getCommands()
cmdCommands.should.deep.equal(['foo', 'bar'])
})

it('accepts deprecated as 5th argument', () => {
const command = 'command'
const description = 'description'
const isDefault = false
const aliases = []
const deprecated = false
const y = yargs([]).command(command, description, {}, () => {}, [], deprecated)
const usageCommands = y.getUsageInstance().getCommands()
usageCommands[0].should.deep.equal([command, description, isDefault, aliases, deprecated])
})
})

describe('commandDir', () => {
Expand Down Expand Up @@ -1396,6 +1418,57 @@ describe('Command', () => {
})
})

describe('deprecated command', () => {
describe('using arg', () => {
it('shows deprecated notice with boolean', () => {
const command = 'command'
const description = 'description'
const deprecated = true
const r = checkOutput(() => {
yargs('--help')
.command(command, description, {}, () => {}, [], deprecated)
.parse()
})
r.logs.should.match(/\[deprecated\]/)
})
it('shows deprecated notice with string', () => {
const command = 'command'
const description = 'description'
const deprecated = 'deprecated'
const r = checkOutput(() => {
yargs('--help')
.command(command, description, {}, () => {}, [], deprecated)
.parse()
})
r.logs.should.match(/\[deprecated: deprecated\]/)
})
})
describe('using module', () => {
it('shows deprecated notice with boolean', () => {
const command = 'command'
const description = 'description'
const deprecated = true
const r = checkOutput(() => {
yargs('--help')
.command({ command, description, deprecated })
.parse()
})
r.logs.should.match(/\[deprecated\]/)
})
it('shows deprecated notice with string', () => {
const command = 'command'
const description = 'description'
const deprecated = 'deprecated'
const r = checkOutput(() => {
yargs('--help')
.command({ command, description, deprecated })
.parse()
})
r.logs.should.match(/\[deprecated: deprecated\]/)
})
})
})

// addresses: https://github.com/yargs/yargs/issues/819
it('should kick along [demand] configuration to commands', () => {
let called = false
Expand Down
6 changes: 3 additions & 3 deletions yargs.js
Original file line number Diff line number Diff line change
Expand Up @@ -389,9 +389,9 @@ function Yargs (processArgs, cwd, parentRequire) {
return self
}

self.command = function (cmd, description, builder, handler, middlewares) {
argsert('<string|array|object> [string|boolean] [function|object] [function] [array]', [cmd, description, builder, handler, middlewares], arguments.length)
command.addHandler(cmd, description, builder, handler, middlewares)
self.command = function (cmd, description, builder, handler, middlewares, deprecated) {
argsert('<string|array|object> [string|boolean] [function|object] [function] [array] [boolean|string]', [cmd, description, builder, handler, middlewares, deprecated], arguments.length)
command.addHandler(cmd, description, builder, handler, middlewares, deprecated)
return self
}

Expand Down

0 comments on commit 027a636

Please sign in to comment.