Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Show message when showHelpOnFail is chained globally #2154

Merged
merged 4 commits into from Mar 31, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions lib/typings/common-types.ts
@@ -1,5 +1,10 @@
import {Parser} from './yargs-parser-types.js';

/**
* A type that represents undefined or null
*/
export type nil = undefined | null;

/**
* An object whose all properties have the same type.
*/
Expand Down
4 changes: 2 additions & 2 deletions lib/typings/yargs-parser-types.ts
@@ -1,6 +1,6 @@
// Taken from yargs-parser@19.0.1
// TODO: update this file periodically.
import type {Dictionary, ValueOf} from './common-types.js';
import type {Dictionary, ValueOf, nil} from './common-types.js';

type KeyOf<T> = {
[K in keyof T]: string extends K ? never : number extends K ? never : K;
Expand Down Expand Up @@ -142,7 +142,7 @@ export interface Parser {
detailed(args: ArgsInput, opts?: Partial<Options>): DetailedArguments;
camelCase(str: string): string;
decamelize(str: string, joinString?: string): string;
looksLikeNumber(x: null | undefined | number | string): boolean;
looksLikeNumber(x: nil | number | string): boolean;
}
export declare type StringFlag = Dictionary<string[]>;
export declare type BooleanFlag = Dictionary<boolean>;
Expand Down
29 changes: 18 additions & 11 deletions lib/usage.ts
@@ -1,6 +1,6 @@
// this file handles outputting usage instructions,
// failures, etc. keeps logging in one place.
import {Dictionary, PlatformShim} from './typings/common-types.js';
import {Dictionary, PlatformShim, nil} from './typings/common-types.js';
import {objFilter} from './utils/obj-filter.js';
import {YargsInstance} from './yargs-factory.js';
import {YError} from './yerror.js';
Expand All @@ -20,16 +20,22 @@ export function usage(yargs: YargsInstance, shim: PlatformShim) {
self.failFn = function failFn(f) {
fails.push(f);
};
let failMessage: string | undefined | null = null;
let failMessage: string | nil = null;
let globalFailMessage: string | nil = null;
let showHelpOnFail = true;
self.showHelpOnFail = function showHelpOnFailFn(
arg1: boolean | string = true,
arg2?: string
) {
function parseFunctionArgs(): [boolean, string?] {
return typeof arg1 === 'string' ? [true, arg1] : [arg1, arg2];
const [enabled, message] =
typeof arg1 === 'string' ? [true, arg1] : [arg1, arg2];

// If global context, set globalFailMessage
// Addresses: https://github.com/yargs/yargs/issues/2085
if (yargs.getInternalMethods().getContext().commands.length === 0) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there an easier way to figure out that this is in the global context? (I can move this logic to a helper function and call it here)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the idea of pulling this into a private helper method, then if we can think of a better way to detect we're in the global context we can swap out the logic.

globalFailMessage = message;
}
const [enabled, message] = parseFunctionArgs();

failMessage = message;
showHelpOnFail = enabled;
return self;
Expand Down Expand Up @@ -60,9 +66,10 @@ export function usage(yargs: YargsInstance, shim: PlatformShim) {
logger.error();
}
if (msg || err) logger.error(msg || err);
if (failMessage) {
const globalOrCommandFailMessage = failMessage || globalFailMessage;
if (globalOrCommandFailMessage) {
if (msg || err) logger.error('');
logger.error(failMessage);
logger.error(globalOrCommandFailMessage);
}
}

Expand Down Expand Up @@ -150,7 +157,7 @@ export function usage(yargs: YargsInstance, shim: PlatformShim) {
};

let wrapSet = false;
let wrap: number | null | undefined;
let wrap: number | nil;
self.wrap = cols => {
wrapSet = true;
wrap = cols;
Expand Down Expand Up @@ -771,19 +778,19 @@ export interface UsageInstance {
unfreeze(defaultCommand?: boolean): void;
usage(msg: string | null, description?: string | false): UsageInstance;
version(ver: any): void;
wrap(cols: number | null | undefined): void;
wrap(cols: number | nil): void;
}

export interface FailureFunction {
(
msg: string | undefined | null,
msg: string | nil,
err: YError | string | undefined,
usage: UsageInstance
): void;
}

export interface FrozenUsageInstance {
failMessage: string | undefined | null;
failMessage: string | nil;
failureOutput: boolean;
usages: [string, string][];
usageDisabled: boolean;
Expand Down
13 changes: 5 additions & 8 deletions lib/yargs-factory.ts
Expand Up @@ -22,6 +22,7 @@ import type {
RequireDirectoryOptions,
PlatformShim,
RequireType,
nil,
} from './typings/common-types.js';
import {
assertNotStrictEqual,
Expand Down Expand Up @@ -172,7 +173,7 @@ export class YargsInstance {
#completion: CompletionInstance | null = null;
#completionCommand: string | null = null;
#defaultShowHiddenOpt = 'show-hidden';
#exitError: YError | string | undefined | null = null;
#exitError: YError | string | nil = null;
#detectLocale = true;
#emittedWarnings: Dictionary<boolean> = {};
#exitProcess = true;
Expand Down Expand Up @@ -1423,7 +1424,7 @@ export class YargsInstance {
this.describe(this.#versionOpt, msg);
return this;
}
wrap(cols: number | null | undefined): YargsInstance {
wrap(cols: number | nil): YargsInstance {
argsert('<number|null|undefined>', [cols], arguments.length);
this.#usage.wrap(cols);
return this;
Expand Down Expand Up @@ -2344,19 +2345,15 @@ interface FrozenYargsInstance {
strictOptions: boolean;
completionCommand: string | null;
output: string;
exitError: YError | string | undefined | null;
exitError: YError | string | nil;
hasOutput: boolean;
parsed: DetailedArguments | false;
parseFn: ParseCallback | null;
parseContext: object | null;
}

interface ParseCallback {
(
err: YError | string | undefined | null,
argv: Arguments,
output: string
): void;
(err: YError | string | nil, argv: Arguments, output: string): void;
}

interface Aliases {
Expand Down
83 changes: 83 additions & 0 deletions test/usage.cjs
Expand Up @@ -1711,6 +1711,89 @@ describe('usage tests', () => {
'Specify --help for available options',
]);
});

describe('should handle being chained both globally and on a command ', () => {
const options = {
opt1: {
alias: 'o',
demandOption: true,
},
};
const cmdMessage = 'What have you done (command)????';
const globalMessage = 'What have you done (global)????';
const errorMessage = 'Unknown argument: extraOpt';

it('chained on to command', () => {
const r = checkUsage(() =>
yargs('cmd1 --opt1 hello --extraOpt oops')
.command(
'cmd1',
'cmd1 desc',
yargs => yargs.options(options).showHelpOnFail(false, cmdMessage),
argv => console.log(argv)
)
.strict()
.parse()
);
r.should.have.property('result');
r.result.should.have.property('_').with.length(1);
r.should.have.property('errors');
r.should.have.property('logs').with.length(0);
r.should.have.property('exit').and.equal(true);
r.errors
.join('\n')
.split(/\n/)
.should.deep.equal([errorMessage, '', cmdMessage]);
});

it('chained on globally', () => {
const r = checkUsage(() =>
yargs('cmd1 --opt1 hello --extraOpt oops')
.command(
'cmd1',
'cmd1 desc',
yargs => yargs.option(options),
argv => console.log(argv)
)
.showHelpOnFail(false, globalMessage)
.strict()
.parse()
);
r.should.have.property('result');
r.result.should.have.property('_').with.length(1);
r.should.have.property('errors');
r.should.have.property('logs').with.length(0);
r.should.have.property('exit').and.equal(true);
r.errors
.join('\n')
.split(/\n/)
.should.deep.equal([errorMessage, '', globalMessage]);
});

it('chained on command and globally (priority given to command message)', () => {
const r = checkUsage(() =>
yargs('cmd1 --opt1 hello --extraOpt oops')
.command(
'cmd1',
'cmd1 desc',
yargs => yargs.option(options).showHelpOnFail(false, cmdMessage),
argv => console.log(argv)
)
.showHelpOnFail(false, globalMessage)
.strict()
.parse()
);
r.should.have.property('result');
r.result.should.have.property('_').with.length(1);
r.should.have.property('errors');
r.should.have.property('logs').with.length(0);
r.should.have.property('exit').and.equal(true);
r.errors
.join('\n')
.split(/\n/)
.should.deep.equal([errorMessage, '', cmdMessage]);
});
});
});

describe('exitProcess', () => {
Expand Down