Skip to content

Commit e68334b

Browse files
authoredJun 17, 2020
refactor(ts): move and tsify most of root yargs.js to lib/yargs (#1670)
1 parent cb7fbb8 commit e68334b

16 files changed

+2060
-1517
lines changed
 

‎lib/apply-extends.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ function mergeDeep (config1: Dictionary, config2: Dictionary) {
3232
return target
3333
}
3434

35-
export function applyExtends (config: Dictionary, cwd: string, mergeExtends: boolean) {
35+
export function applyExtends (config: Dictionary, cwd: string, mergeExtends = false): Dictionary {
3636
let defaultConfig = {}
3737

3838
if (Object.prototype.hasOwnProperty.call(config, 'extends')) {

‎lib/command.ts

+23-14
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Dictionary, assertNotUndefined, assertNotTrue } from './common-types'
1+
import { Dictionary, assertNotStrictEqual } from './common-types'
22
import { isPromise } from './is-promise'
33
import { applyMiddleware, commandMiddlewareFactory, Middleware } from './middleware'
44
import { parseCommand, Positional } from './parse-command'
@@ -7,9 +7,7 @@ import { RequireDirectoryOptions } from 'require-directory'
77
import { UsageInstance } from './usage'
88
import { inspect } from 'util'
99
import { ValidationInstance } from './validation'
10-
import { YargsInstance, isYargsInstance, Options, OptionDefinition } from './yargs-types'
11-
import { DetailedArguments, Arguments } from 'yargs-parser'
12-
import { Context } from 'vm'
10+
import { YargsInstance, isYargsInstance, Options, OptionDefinition, Context, Configuration, Arguments, DetailedArguments } from './yargs'
1311
import requireDirectory = require('require-directory')
1412
import whichModule = require('which-module')
1513
import Parser = require('yargs-parser')
@@ -148,7 +146,7 @@ export function command (
148146
function extractDesc ({ describe, description, desc }: CommandHandlerDefinition) {
149147
for (const test of [describe, description, desc]) {
150148
if (typeof test === 'string' || test === false) return test
151-
assertNotTrue(test)
149+
assertNotStrictEqual(test, true as true)
152150
}
153151
return false
154152
}
@@ -161,7 +159,7 @@ export function command (
161159

162160
self.runCommand = function runCommand (command, yargs, parsed, commandIndex) {
163161
let aliases = parsed.aliases
164-
const commandHandler = handlers[command] || handlers[aliasMap[command]] || defaultCommand
162+
const commandHandler = handlers[command!] || handlers[aliasMap[command!]] || defaultCommand
165163
const currentContext = yargs.getContext()
166164
let numFiles = currentContext.files.length
167165
const parentCommands = currentContext.commands.slice()
@@ -186,7 +184,7 @@ export function command (
186184
)
187185
}
188186
innerArgv = innerYargs._parseArgs(null, null, true, commandIndex)
189-
aliases = innerYargs.parsed.aliases
187+
aliases = (innerYargs.parsed as DetailedArguments).aliases
190188
} else if (isCommandBuilderOptionDefinitions(builder)) {
191189
// as a short hand, an object can instead be provided, specifying
192190
// the options that a command takes.
@@ -201,19 +199,26 @@ export function command (
201199
innerYargs.option(key, builder[key])
202200
})
203201
innerArgv = innerYargs._parseArgs(null, null, true, commandIndex)
204-
aliases = innerYargs.parsed.aliases
202+
aliases = (innerYargs.parsed as DetailedArguments).aliases
205203
}
206204

207205
if (!yargs._hasOutput()) {
208-
positionalMap = populatePositionals(commandHandler, innerArgv, currentContext)
206+
positionalMap = populatePositionals(commandHandler, innerArgv as Arguments, currentContext)
209207
}
210208

211209
const middlewares = globalMiddleware.slice(0).concat(commandHandler.middlewares)
212210
applyMiddleware(innerArgv, yargs, middlewares, true)
213211

214212
// we apply validation post-hoc, so that custom
215213
// checks get passed populated positional arguments.
216-
if (!yargs._hasOutput()) yargs._runValidation(innerArgv, aliases, positionalMap, yargs.parsed.error, !command)
214+
if (!yargs._hasOutput()) {
215+
yargs._runValidation(
216+
innerArgv as Arguments,
217+
aliases,
218+
positionalMap,
219+
(yargs.parsed as DetailedArguments).error,
220+
!command)
221+
}
217222

218223
if (commandHandler.handler && !yargs._hasOutput()) {
219224
yargs._setHasOutput()
@@ -279,7 +284,7 @@ export function command (
279284
}
280285

281286
self.runDefaultBuilderOn = function (yargs) {
282-
assertNotUndefined(defaultCommand)
287+
assertNotStrictEqual(defaultCommand, undefined)
283288
if (shouldUpdateUsage(yargs)) {
284289
// build the root-level command string from the default string.
285290
const commandString = DEFAULT_MARKER.test(defaultCommand.original)
@@ -360,7 +365,7 @@ export function command (
360365
// short-circuit parse.
361366
if (!unparsed.length) return
362367

363-
const config = Object.assign({}, options.configuration, {
368+
const config: Configuration = Object.assign({}, options.configuration, {
364369
'populate--': true
365370
})
366371
const parsed = Parser.detailed(unparsed, Object.assign({}, options, {
@@ -440,7 +445,7 @@ export function command (
440445
}
441446
self.unfreeze = () => {
442447
const frozen = frozens.pop()
443-
assertNotUndefined(frozen)
448+
assertNotStrictEqual(frozen, undefined)
444449
;({
445450
handlers,
446451
aliasMap,
@@ -475,7 +480,7 @@ export interface CommandInstance {
475480
getCommands (): string[]
476481
hasDefaultCommand (): boolean
477482
reset (): CommandInstance
478-
runCommand (command: string, yargs: YargsInstance, parsed: DetailedArguments, commandIndex: number): void
483+
runCommand (command: string | null, yargs: YargsInstance, parsed: DetailedArguments, commandIndex?: number): Arguments | Promise<Arguments>
479484
runDefaultBuilderOn (yargs: YargsInstance): void
480485
unfreeze(): void
481486
}
@@ -546,3 +551,7 @@ type FrozenCommandInstance = {
546551
aliasMap: Dictionary<string>
547552
defaultCommand: CommandHandler | undefined
548553
}
554+
555+
export interface FinishCommandHandler {
556+
(handlerResult: any): any
557+
}

‎lib/common-types.ts

+40-5
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,48 @@
1-
import { notStrictEqual } from 'assert'
1+
import { notStrictEqual, strictEqual } from 'assert'
22

3+
/**
4+
* An object whose all properties have the same type.
5+
*/
36
export type Dictionary<T = any> = { [key: string]: T }
47

8+
/**
9+
* Returns the keys of T that match Dictionary<U> and are not arrays.
10+
*/
11+
export type DictionaryKeyof<T, U = any> = Exclude<KeyOf<T, Dictionary<U>>, KeyOf<T, any[]>>
12+
13+
/**
14+
* Returns the keys of T that match U.
15+
*/
16+
export type KeyOf<T, U> = Exclude<{ [K in keyof T]: T[K] extends U ? K : never }[keyof T], undefined>
17+
18+
/**
19+
* An array whose first element is not undefined.
20+
*/
521
export type NotEmptyArray<T = any> = [T, ...T[]]
622

7-
export function assertNotTrue<T = any> (actual: T | true): asserts actual is T {
8-
notStrictEqual(actual, true)
23+
/**
24+
* Returns the type of a Dictionary or array values.
25+
*/
26+
export type ValueOf<T> = T extends (infer U)[] ? U : T[keyof T];
27+
28+
/**
29+
* Typing wrapper around assert.notStrictEqual()
30+
*/
31+
export function assertNotStrictEqual<N, T> (actual: T|N, expected: N, message ?: string | Error)
32+
: asserts actual is Exclude<T, N> {
33+
notStrictEqual(actual, expected, message)
34+
}
35+
36+
/**
37+
* Asserts actual is a single key, not a key array or a key map.
38+
*/
39+
export function assertSingleKey (actual: string | string[] | Dictionary): asserts actual is string {
40+
strictEqual(typeof actual, 'string')
941
}
1042

11-
export function assertNotUndefined<T = any> (actual: T | undefined): asserts actual is T {
12-
notStrictEqual(actual, undefined)
43+
/**
44+
* Typing wrapper around Object.keys()
45+
*/
46+
export function objectKeys<T> (object: T) {
47+
return Object.keys(object) as (keyof T)[]
1348
}

‎lib/completion.ts

+11-4
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ import { isPromise } from './is-promise'
44
import { parseCommand } from './parse-command'
55
import * as path from 'path'
66
import { UsageInstance } from './usage'
7-
import { YargsInstance } from './yargs-types'
7+
import { YargsInstance } from './yargs'
88
import { Arguments, DetailedArguments } from 'yargs-parser'
9+
import { assertNotStrictEqual } from './common-types'
910

1011
// add bash completions to your
1112
// yargs-powered applications.
@@ -31,7 +32,9 @@ export function completion (yargs: YargsInstance, usage: UsageInstance, command:
3132

3233
// a custom completion function can be provided
3334
// to completion().
34-
if (completionFunction) {
35+
function runCompletionFunction (argv: Arguments) {
36+
assertNotStrictEqual(completionFunction, null)
37+
3538
if (isSyncCompletionFunction(completionFunction)) {
3639
const result = completionFunction(current, argv)
3740

@@ -54,6 +57,10 @@ export function completion (yargs: YargsInstance, usage: UsageInstance, command:
5457
}
5558
}
5659

60+
if (completionFunction) {
61+
return isPromise(argv) ? argv.then(runCompletionFunction) : runCompletionFunction(argv)
62+
}
63+
5764
const handlers = command.getCommandHandlers()
5865
for (let i = 0, ii = args.length; i < ii; ++i) {
5966
if (handlers[args[i]] && handlers[args[i]].builder) {
@@ -137,15 +144,15 @@ export function completion (yargs: YargsInstance, usage: UsageInstance, command:
137144
}
138145

139146
/** Instance of the completion module. */
140-
interface CompletionInstance {
147+
export interface CompletionInstance {
141148
completionKey: string
142149
generateCompletionScript($0: string, cmd: string): string
143150
getCompletion(args: string[], done: (completions: string[]) => any): any
144151
registerFunction(fn: CompletionFunction): void
145152
setParsed(parsed: DetailedArguments): void
146153
}
147154

148-
type CompletionFunction = SyncCompletionFunction | AsyncCompletionFunction
155+
export type CompletionFunction = SyncCompletionFunction | AsyncCompletionFunction
149156

150157
interface SyncCompletionFunction {
151158
(current: string, argv: Arguments): string[] | Promise<string[]>

‎lib/middleware.ts

+10-11
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { argsert } from './argsert'
22
import { isPromise } from './is-promise'
3-
import { YargsInstance } from './yargs-types'
4-
import { Arguments } from 'yargs-parser'
3+
import { YargsInstance, Arguments } from './yargs'
54

65
export function globalMiddlewareFactory<T> (globalMiddleware: Middleware[], context: T) {
76
return function (callback: MiddlewareCallback | MiddlewareCallback[], applyBeforeValidation = false) {
@@ -31,38 +30,38 @@ export function commandMiddlewareFactory (commandMiddleware?: MiddlewareCallback
3130
}
3231

3332
export function applyMiddleware (
34-
argv: Arguments,
33+
argv: Arguments | Promise<Arguments>,
3534
yargs: YargsInstance,
3635
middlewares: Middleware[],
3736
beforeValidation: boolean
3837
) {
3938
const beforeValidationError = new Error('middleware cannot return a promise when applyBeforeValidation is true')
4039
return middlewares
41-
.reduce<Arguments | Promise<Arguments>>((accumulation, middleware) => {
40+
.reduce<Arguments | Promise<Arguments>>((acc, middleware) => {
4241
if (middleware.applyBeforeValidation !== beforeValidation) {
43-
return accumulation
42+
return acc
4443
}
4544

46-
if (isPromise(accumulation)) {
47-
return accumulation
45+
if (isPromise(acc)) {
46+
return acc
4847
.then(initialObj =>
4948
Promise.all<Arguments, Partial<Arguments>>([initialObj, middleware(initialObj, yargs)])
5049
)
5150
.then(([initialObj, middlewareObj]) =>
5251
Object.assign(initialObj, middlewareObj)
5352
)
5453
} else {
55-
const result = middleware(argv, yargs)
54+
const result = middleware(acc, yargs)
5655
if (beforeValidation && isPromise(result)) throw beforeValidationError
5756

5857
return isPromise(result)
59-
? result.then(middlewareObj => Object.assign(accumulation, middlewareObj))
60-
: Object.assign(accumulation, result)
58+
? result.then(middlewareObj => Object.assign(acc, middlewareObj))
59+
: Object.assign(acc, result)
6160
}
6261
}, argv)
6362
}
6463

65-
interface MiddlewareCallback {
64+
export interface MiddlewareCallback {
6665
(argv: Arguments, yargs: YargsInstance): Partial<Arguments> | Promise<Partial<Arguments>>
6766
}
6867

‎lib/obj-filter.ts

+7-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
import { Dictionary } from './common-types'
1+
import { objectKeys } from './common-types'
22

3-
export function objFilter<T = any> (original: Dictionary<T>, filter: (k: string, v: T) => boolean = () => true) {
4-
const obj: Dictionary<T> = {}
5-
Object.keys(original || {}).forEach((key) => {
3+
export function objFilter<T extends object> (
4+
original = {} as T,
5+
filter: (k: keyof T, v: T[keyof T]) => boolean = () => true
6+
) {
7+
const obj = {} as T
8+
objectKeys(original).forEach((key) => {
69
if (filter(key, original[key])) {
710
obj[key] = original[key]
811
}
+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// TODO: either create @types/require-main-filename or or convert require-main-filename to typescript
2+
3+
/**
4+
* Returns the entry point of the current application.
5+
*
6+
* `require.main.filename` is great for figuring out the entry point for the current application.
7+
* This can be combined with a module like pkg-conf to, as if by magic, load top-level configuration.
8+
*
9+
* Unfortunately, `require.main.filename` sometimes fails when an application is executed
10+
* with an alternative process manager, e.g., iisnode.
11+
*
12+
* `require-main-filename` is a shim that addresses this problem.
13+
*
14+
* @param _require require function
15+
* @returns hash of modules in specified directory
16+
*/
17+
declare function requireMainFilename(_require: NodeRequire): string;
18+
19+
declare module 'require-main-filename' {
20+
export = requireMainFilename;
21+
}

‎lib/typings/y18n.d.ts

+56-51
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,64 @@
11
// TODO: either update @types/y18n with this or convert y18n to typescript
22
// Forked from: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/a9cb5fa/types/y18n/index.d.ts
33

4-
interface Config {
5-
/**
6-
* The locale directory, default ./locales.
7-
*/
8-
directory: string;
9-
/**
10-
* Should newly observed strings be updated in file, default true.
11-
*/
12-
updateFiles: boolean;
13-
/**
14-
* What locale should be used.
15-
*/
16-
locale: string;
17-
/**
18-
* Should fallback to a language-only file (e.g. en.json) be allowed
19-
* if a file matching the locale does not exist (e.g. en_US.json), default true.
20-
*/
21-
fallbackToLanguage: boolean;
22-
}
4+
/* eslint no-redeclare: "off" */
5+
declare namespace y18n {
6+
interface Config {
7+
/**
8+
* The locale directory, default ./locales.
9+
*/
10+
directory?: string;
11+
/**
12+
* Should newly observed strings be updated in file, default true.
13+
*/
14+
updateFiles?: boolean;
15+
/**
16+
* What locale should be used.
17+
*/
18+
locale?: string;
19+
/**
20+
* Should fallback to a language-only file (e.g. en.json) be allowed
21+
* if a file matching the locale does not exist (e.g. en_US.json), default true.
22+
*/
23+
fallbackToLanguage?: boolean;
24+
}
25+
26+
export class Y18N {
27+
/**
28+
* Create an instance of y18n with the config provided
29+
*/
30+
constructor(config?: Config);
31+
32+
/**
33+
* Print a localized string, %s will be replaced with args.
34+
*/
35+
__(str: string, arg1?: string, arg2?: string, arg3?: string): string;
36+
37+
/**
38+
* Print a localized string with appropriate pluralization.
39+
* If %d is provided in the string, the quantity will replace this placeholder.
40+
*/
41+
__n(singular: string, plural: string, quantity: number, ...param: any[]): string;
2342

24-
declare class Y18N {
25-
/**
26-
* Create an instance of y18n with the config provided
27-
*/
28-
constructor(config?: Config);
29-
30-
/**
31-
* Print a localized string, %s will be replaced with args.
32-
*/
33-
__(str: string, arg1?: string, arg2?: string, arg3?: string): string;
34-
35-
/**
36-
* Print a localized string with appropriate pluralization.
37-
* If %d is provided in the string, the quantity will replace this placeholder.
38-
*/
39-
__n(singular: string, plural: string, quantity: number, ...param: any[]): string;
40-
41-
/**
42-
* Set the current locale being used.
43-
*/
44-
setLocale(str: string): void;
45-
46-
/**
47-
* What locale is currently being used?
48-
*/
49-
getLocale(): string;
50-
51-
/**
52-
* Update the current locale with the key value pairs in obj.
53-
*/
54-
updateLocale(obj: object): void;
43+
/**
44+
* Set the current locale being used.
45+
*/
46+
setLocale(str: string): void;
47+
48+
/**
49+
* What locale is currently being used?
50+
*/
51+
getLocale(): string;
52+
53+
/**
54+
* Update the current locale with the key value pairs in obj.
55+
*/
56+
updateLocale(obj: { [key: string]: string }): void;
57+
}
5558
}
5659

60+
declare function y18n (opts?: y18n.Config): y18n.Y18N;
61+
5762
declare module 'y18n' {
58-
export = Y18N;
63+
export = y18n;
5964
}

‎lib/typings/yargs-parser.d.ts

+124
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
// TODO: either update @types/yargs-parser with this or convert yargs-parser to typescript
2+
// Forked from: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/699b8159a6f571a14ef6d1d07b956cf78c8e729c/types/yargs-parser/index.d.ts
3+
4+
/* eslint no-redeclare: "off" */
5+
declare namespace yargsParser {
6+
interface Arguments {
7+
/** Non-option arguments */
8+
_: string[];
9+
/** All remaining options */
10+
[argName: string]: any;
11+
}
12+
13+
interface DetailedArguments {
14+
/** An object representing the parsed value of `args` */
15+
argv: Arguments;
16+
/** Populated with an error object if an exception occurred during parsing. */
17+
error: Error | null;
18+
/** The inferred list of aliases built by combining lists in opts.alias. */
19+
aliases: { [alias: string]: string[] };
20+
/** Any new aliases added via camel-case expansion. */
21+
newAliases: { [alias: string]: boolean };
22+
/** The configuration loaded from the yargs stanza in package.json. */
23+
configuration: Configuration;
24+
}
25+
26+
interface Configuration {
27+
/** Should variables prefixed with --no be treated as negations? Default is `true` */
28+
'boolean-negation': boolean;
29+
/** Should hyphenated arguments be expanded into camel-case aliases? Default is `true` */
30+
'camel-case-expansion': boolean;
31+
/** Should arrays be combined when provided by both command line arguments and a configuration file. Default is `false` */
32+
'combine-arrays': boolean;
33+
/** Should keys that contain . be treated as objects? Default is `true` */
34+
'dot-notation': boolean;
35+
/** Should arguments be coerced into an array when duplicated. Default is `true` */
36+
'duplicate-arguments-array': boolean;
37+
/** Should array arguments be coerced into a single array when duplicated. Default is `true` */
38+
'flatten-duplicate-arrays': boolean;
39+
/** Should arrays consume more than one positional argument following their flag? Default is `true` */
40+
'greedy-arrays': boolean;
41+
/** Should parsing stop at the first text argument? This is similar to how e.g. ssh parses its command line. Default is `false` */
42+
'halt-at-non-option': boolean;
43+
/** Should nargs consume dash options as well as positional arguments? Default is `false` */
44+
'nargs-eats-options': boolean;
45+
/** The prefix to use for negated boolean variables. Default is `'no-'` */
46+
'negation-prefix': string;
47+
/** Should keys that look like numbers be treated as such? Default is `true` */
48+
'parse-numbers': boolean;
49+
/** Should unparsed flags be stored in -- or _. Default is `false` */
50+
'populate--': boolean;
51+
/** Should a placeholder be added for keys not set via the corresponding CLI argument? Default is `false` */
52+
'set-placeholder-key': boolean;
53+
/** Should a group of short-options be treated as boolean flags? Default is `true` */
54+
'short-option-groups': boolean;
55+
/** Should aliases be removed before returning results? Default is `false` */
56+
'strip-aliased': boolean;
57+
/** Should dashed keys be removed before returning results? This option has no effect if camel-case-expansion is disabled. Default is `false` */
58+
'strip-dashed': boolean;
59+
/** Should unknown options be treated like regular arguments? An unknown option is one that is not configured in opts. Default is `false` */
60+
'unknown-options-as-args': boolean;
61+
}
62+
63+
type ArrayOption = string | { key: string; string?: boolean, boolean?: boolean, number?: boolean, integer?: boolean };
64+
65+
interface Options {
66+
/** An object representing the set of aliases for a key: `{ alias: { foo: ['f']} }`. */
67+
alias: { [key: string]: string | string[] };
68+
/**
69+
* Indicate that keys should be parsed as an array: `{ array: ['foo', 'bar'] }`.
70+
* Indicate that keys should be parsed as an array and coerced to booleans / numbers:
71+
* { array: [ { key: 'foo', boolean: true }, {key: 'bar', number: true} ] }`.
72+
*/
73+
array: ArrayOption | ArrayOption[];
74+
/** Arguments should be parsed as booleans: `{ boolean: ['x', 'y'] }`. */
75+
boolean: string | string[];
76+
/** Indicate a key that represents a path to a configuration file (this file will be loaded and parsed). */
77+
config: string | string[] | { [key: string]: boolean | ConfigCallback };
78+
/** configuration objects to parse, their properties will be set as arguments */
79+
configObjects: Array<{ [key: string]: any }>;
80+
/** Provide configuration options to the yargs-parser. */
81+
configuration: Partial<Configuration>;
82+
/**
83+
* Provide a custom synchronous function that returns a coerced value from the argument provided (or throws an error), e.g.
84+
* `{ coerce: { foo: function (arg) { return modifiedArg } } }`.
85+
*/
86+
coerce: { [key: string]: CoerceCallback };
87+
/** Indicate a key that should be used as a counter, e.g., `-vvv = {v: 3}`. */
88+
count: string | string[];
89+
/** Provide default values for keys: `{ default: { x: 33, y: 'hello world!' } }`. */
90+
default: { [key: string]: any };
91+
/** Environment variables (`process.env`) with the prefix provided should be parsed. */
92+
envPrefix: string;
93+
/** Specify that a key requires n arguments: `{ narg: {x: 2} }`. */
94+
narg: { [key: string]: number };
95+
/** `path.normalize()` will be applied to values set to this key. */
96+
normalize: string | string[];
97+
/** Keys should be treated as strings (even if they resemble a number `-x 33`). */
98+
string: string | string[];
99+
/** Keys should be treated as numbers. */
100+
number: string | string[];
101+
/** i18n handler, defaults to util.format */
102+
__: (format: any, ...param: any[]) => string;
103+
/** alias lookup table defaults */
104+
key: { [key: string]: any };
105+
}
106+
107+
interface CoerceCallback {
108+
(arg: any): any
109+
}
110+
111+
interface ConfigCallback {
112+
(configPath: string): { [key: string]: any }
113+
}
114+
115+
interface Parser {
116+
(args: string | any[], opts?: Partial<Options>): Arguments;
117+
detailed(args: string | any[], opts?: Partial<Options>): DetailedArguments;
118+
}
119+
}
120+
121+
declare var yargsParser: yargsParser.Parser
122+
declare module 'yargs-parser' {
123+
export = yargsParser;
124+
}

‎lib/usage.ts

+22-20
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
// this file handles outputting usage instructions,
22
// failures, etc. keeps logging in one place.
3-
import { Dictionary, assertNotUndefined } from './common-types'
3+
import { Dictionary, assertNotStrictEqual } from './common-types'
44
import { objFilter } from './obj-filter'
55
import * as path from 'path'
6-
import { YargsInstance } from './yargs-types'
6+
import { YargsInstance } from './yargs'
77
import { YError } from './yerror'
8+
import { Y18N } from 'y18n'
9+
import { DetailedArguments } from 'yargs-parser'
810
import decamelize = require('decamelize')
911
import setBlocking = require('set-blocking')
1012
import stringWidth = require('string-width')
11-
import Y18N = require('y18n')
1213

1314
export function usage (yargs: YargsInstance, y18n: Y18N) {
1415
const __ = y18n.__
@@ -111,8 +112,12 @@ export function usage (yargs: YargsInstance, y18n: Y18N) {
111112
self.getCommands = () => commands
112113

113114
let descriptions: Dictionary<string | undefined> = {}
114-
self.describe = function describe (keyOrKeys: string | Dictionary<string>, desc?: string) {
115-
if (typeof keyOrKeys === 'object') {
115+
self.describe = function describe (keyOrKeys: string | string[] | Dictionary<string>, desc?: string) {
116+
if (Array.isArray(keyOrKeys)) {
117+
keyOrKeys.forEach((k) => {
118+
self.describe(k, desc)
119+
})
120+
} else if (typeof keyOrKeys === 'object') {
116121
Object.keys(keyOrKeys).forEach((k) => {
117122
self.describe(k, keyOrKeys[k])
118123
})
@@ -128,7 +133,7 @@ export function usage (yargs: YargsInstance, y18n: Y18N) {
128133
}
129134

130135
let wrapSet = false
131-
let wrap: number | undefined
136+
let wrap: number | null | undefined
132137
self.wrap = (cols) => {
133138
wrapSet = true
134139
wrap = cols
@@ -245,9 +250,9 @@ export function usage (yargs: YargsInstance, y18n: Y18N) {
245250
// perform some cleanup on the keys array, making it
246251
// only include top-level keys not their aliases.
247252
const aliasKeys = (Object.keys(options.alias) || [])
248-
.concat(Object.keys(yargs.parsed.newAliases) || [])
253+
.concat(Object.keys((yargs.parsed as DetailedArguments).newAliases) || [])
249254

250-
keys = keys.filter(key => !yargs.parsed.newAliases[key] && aliasKeys.every(alias => (options.alias[alias] || []).indexOf(key) === -1))
255+
keys = keys.filter(key => !(yargs.parsed as DetailedArguments).newAliases[key] && aliasKeys.every(alias => (options.alias[alias] || []).indexOf(key) === -1))
251256

252257
// populate 'Options:' group with any keys that have not
253258
// explicitly had a group set.
@@ -309,7 +314,7 @@ export function usage (yargs: YargsInstance, y18n: Y18N) {
309314
if (~options.array.indexOf(key)) type = `[${__('array')}]`
310315
if (~options.number.indexOf(key)) type = `[${__('number')}]`
311316

312-
const deprecatedExtra = (deprecated: string | boolean) => typeof deprecated === 'string'
317+
const deprecatedExtra = (deprecated?: string | boolean) => typeof deprecated === 'string'
313318
? `[${__('deprecated: %s', deprecated)}]`
314319
: `[${__('deprecated')}]`
315320

@@ -378,7 +383,7 @@ export function usage (yargs: YargsInstance, y18n: Y18N) {
378383

379384
// return the maximum width of a string
380385
// in the left-hand column of a table.
381-
function maxWidth (table: [string, ...any[]][] | Dictionary<string>, theWrap?: number, modifier?: string) {
386+
function maxWidth (table: [string, ...any[]][] | Dictionary<string>, theWrap?: number | null, modifier?: string) {
382387
let width = 0
383388

384389
// table might be of the form [leftColumn],
@@ -457,7 +462,7 @@ export function usage (yargs: YargsInstance, y18n: Y18N) {
457462
}
458463

459464
function filterHiddenOptions (key: string) {
460-
return yargs.getOptions().hiddenOptions.indexOf(key) < 0 || yargs.parsed.argv[yargs.getOptions().showHiddenOpt]
465+
return yargs.getOptions().hiddenOptions.indexOf(key) < 0 || (yargs.parsed as DetailedArguments).argv[yargs.getOptions().showHiddenOpt]
461466
}
462467

463468
self.showHelp = (level: 'error' | 'log' | ((message: string) => void)) => {
@@ -564,7 +569,7 @@ export function usage (yargs: YargsInstance, y18n: Y18N) {
564569
}
565570
self.unfreeze = function unfreeze () {
566571
const frozen = frozens.pop()
567-
assertNotUndefined(frozen)
572+
assertNotStrictEqual(frozen, undefined)
568573
;({
569574
failMessage,
570575
failureOutput,
@@ -586,8 +591,7 @@ export interface UsageInstance {
586591
clearCachedHelpMessage(): void
587592
command(cmd: string, description: string | undefined, isDefault: boolean, aliases: string[], deprecated?: boolean): void
588593
deferY18nLookup(str: string): string
589-
describe(key: string, desc?: string): void
590-
describe(keys: Dictionary<string>): void
594+
describe(keys: string | string[] | Dictionary<string>, desc?: string): void
591595
epilog(msg: string): void
592596
example(cmd: string, description?: string): void
593597
fail(msg?: string | null, err?: YError | string): void
@@ -601,19 +605,17 @@ export interface UsageInstance {
601605
getUsageDisabled(): boolean
602606
help(): string
603607
reset(localLookup: Dictionary<boolean>): UsageInstance
604-
showHelp(level: 'error' | 'log'): void
605-
showHelp(level: (message: string) => void): void
606-
showHelpOnFail(message?: string): UsageInstance
607-
showHelpOnFail(enabled: boolean, message: string): UsageInstance
608+
showHelp(level: 'error' | 'log' | ((message: string) => void)): void
609+
showHelpOnFail (enabled?: boolean | string, message?: string): UsageInstance
608610
showVersion(): void
609611
stringifiedValues(values?: any[], separator?: string): string
610612
unfreeze(): void
611613
usage(msg: string | null, description?: string | false): UsageInstance
612614
version(ver: any): void
613-
wrap(cols: number): void
615+
wrap(cols: number | null | undefined): void
614616
}
615617

616-
interface FailureFunction {
618+
export interface FailureFunction {
617619
(msg: string | undefined | null, err: YError | string | undefined, usage: UsageInstance): void
618620
}
619621

‎lib/validation.ts

+13-14
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { argsert } from './argsert'
2-
import { Dictionary, assertNotUndefined } from './common-types'
2+
import { Dictionary, assertNotStrictEqual } from './common-types'
33
import { levenshtein as distance } from './levenshtein'
44
import { objFilter } from './obj-filter'
55
import { UsageInstance } from './usage'
6-
import { YargsInstance } from './yargs-types'
7-
import { Arguments, DetailedArguments } from 'yargs-parser'
8-
import Y18N = require('y18n')
6+
import { YargsInstance, Arguments } from './yargs'
7+
import { DetailedArguments } from 'yargs-parser'
8+
import { Y18N } from 'y18n'
99
const specialKeys = ['$0', '--', '_']
1010

1111
// validation-type-stuff, missing params,
@@ -84,7 +84,7 @@ export function validation (yargs: YargsInstance, usage: UsageInstance, y18n: Y1
8484
// make sure all the required arguments are present.
8585
self.requiredArguments = function requiredArguments (argv) {
8686
const demandedOptions = yargs.getDemandedOptions()
87-
let missing: Dictionary<string> | null = null
87+
let missing: Dictionary<string | undefined> | null = null
8888

8989
for (const key of Object.keys(demandedOptions)) {
9090
if (!Object.prototype.hasOwnProperty.call(argv, key) || typeof argv[key] === 'undefined') {
@@ -179,7 +179,7 @@ export function validation (yargs: YargsInstance, usage: UsageInstance, y18n: Y1
179179
if (!Object.prototype.hasOwnProperty.call(aliases, key)) {
180180
return false
181181
}
182-
const newAliases = yargs.parsed.newAliases
182+
const newAliases = (yargs.parsed as DetailedArguments).newAliases
183183
for (const a of [key, ...aliases[key]]) {
184184
if (!Object.prototype.hasOwnProperty.call(newAliases, a) || !newAliases[key]) {
185185
return true
@@ -254,7 +254,7 @@ export function validation (yargs: YargsInstance, usage: UsageInstance, y18n: Y1
254254

255255
// check implications, argument foo implies => argument bar.
256256
let implied: Dictionary<KeyOrPos[]> = {}
257-
self.implies = function implies (key: string | Dictionary<KeyOrPos | KeyOrPos[]>, value?: KeyOrPos | KeyOrPos[]) {
257+
self.implies = function implies (key, value) {
258258
argsert('<string|object> [array|number|string]', [key, value], arguments.length)
259259

260260
if (typeof key === 'object') {
@@ -269,6 +269,7 @@ export function validation (yargs: YargsInstance, usage: UsageInstance, y18n: Y1
269269
if (Array.isArray(value)) {
270270
value.forEach((i) => self.implies(key, i))
271271
} else {
272+
assertNotStrictEqual(value, undefined)
272273
implied[key].push(value)
273274
}
274275
}
@@ -325,7 +326,7 @@ export function validation (yargs: YargsInstance, usage: UsageInstance, y18n: Y1
325326
}
326327

327328
let conflicting: Dictionary<(string | undefined)[]> = {}
328-
self.conflicts = function conflicts (key: string | Dictionary<string | string []>, value?: string | string[]) {
329+
self.conflicts = function conflicts (key, value) {
329330
argsert('<string|object> [array|string]', [key, value], arguments.length)
330331

331332
if (typeof key === 'object') {
@@ -393,7 +394,7 @@ export function validation (yargs: YargsInstance, usage: UsageInstance, y18n: Y1
393394
}
394395
self.unfreeze = function unfreeze () {
395396
const frozen = frozens.pop()
396-
assertNotUndefined(frozen)
397+
assertNotStrictEqual(frozen, undefined)
397398
;({
398399
implied,
399400
checks,
@@ -408,15 +409,13 @@ export function validation (yargs: YargsInstance, usage: UsageInstance, y18n: Y1
408409
export interface ValidationInstance {
409410
check(f: CustomCheck['func'], global: boolean): void
410411
conflicting(argv: Arguments): void
411-
conflicts(key: string, value: string | string[]): void
412-
conflicts(key: Dictionary<string | string[]>): void
412+
conflicts(key: string | Dictionary<string | string[]>, value?: string | string[]): void
413413
customChecks(argv: Arguments, aliases: DetailedArguments['aliases']): void
414414
freeze(): void
415415
getConflicting(): Dictionary<(string | undefined)[]>
416416
getImplied(): Dictionary<KeyOrPos[]>
417417
implications(argv: Arguments): void
418-
implies(key: string, value: KeyOrPos | KeyOrPos[]): void
419-
implies(key: Dictionary<KeyOrPos | KeyOrPos[]>): void
418+
implies(key: string | Dictionary<KeyOrPos | KeyOrPos[]>, value?: KeyOrPos | KeyOrPos[]): void
420419
isValidAndSomeAliasIsNotNew(key: string, aliases: DetailedArguments['aliases']): boolean
421420
limitedChoices(argv: Arguments): void
422421
nonOptionCount(argv: Arguments): void
@@ -445,4 +444,4 @@ interface FrozenValidationInstance {
445444
conflicting: Dictionary<(string | undefined)[]>
446445
}
447446

448-
type KeyOrPos = string | number | undefined
447+
export type KeyOrPos = string | number

‎lib/yargs-types.ts

-100
This file was deleted.

‎lib/yargs.ts

+1,715
Large diffs are not rendered by default.

‎package.json

-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
"index.js",
1414
"yargs.js",
1515
"build",
16-
"lib/**/*.js",
1716
"locales",
1817
"LICENSE"
1918
],
@@ -34,7 +33,6 @@
3433
"@types/chai": "^4.2.11",
3534
"@types/mocha": "^7.0.2",
3635
"@types/node": "^10.0.3",
37-
"@types/yargs-parser": "^15.0.0",
3836
"@typescript-eslint/eslint-plugin": "^3.0.0",
3937
"@typescript-eslint/parser": "^3.0.0",
4038
"c8": "^7.0.0",

‎test/usage.js

+15
Original file line numberDiff line numberDiff line change
@@ -3631,4 +3631,19 @@ describe('usage tests', () => {
36313631
)
36323632
})
36333633
})
3634+
3635+
it('should allow setting the same description for several keys', () => {
3636+
const r = checkUsage(() => yargs('--help')
3637+
.describe(['big', 'small'], 'Packet size')
3638+
.parse()
3639+
)
3640+
3641+
r.logs[0].split('\n').should.deep.equal([
3642+
'Options:',
3643+
' --help Show help [boolean]',
3644+
' --version Show version number [boolean]',
3645+
' --big Packet size',
3646+
' --small Packet size'
3647+
])
3648+
})
36343649
})

‎yargs.js

+2-1,291
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)
Please sign in to comment.