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

.showHelp() and .getHelp() now return same output for commands as --help #1826

Merged
merged 21 commits into from Mar 6, 2021
Merged
Show file tree
Hide file tree
Changes from 19 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
8 changes: 8 additions & 0 deletions lib/command.ts
Expand Up @@ -238,6 +238,10 @@ export function command(
// up a yargs chain and possibly returns it.
const builderOutput = builder(yargs.reset(parsed.aliases));
const innerYargs = isYargsInstance(builderOutput) ? builderOutput : yargs;
// A null command indicates we are running the default command,
// if this is the case, we should show the root usage instructions
// rather than the usage instructions for the nested default command:
if (!command) innerYargs.getUsageInstance().unfreeze();
if (shouldUpdateUsage(innerYargs)) {
innerYargs
.getUsageInstance()
Expand All @@ -261,6 +265,10 @@ export function command(
// as a short hand, an object can instead be provided, specifying
// the options that a command takes.
const innerYargs = yargs.reset(parsed.aliases);
// A null command indicates we are running the default command,
OsmanAltun marked this conversation as resolved.
Show resolved Hide resolved
// if this is the case, we should show the root usage instructions
// rather than the usage instructions for the nested default command:
if (!command) innerYargs.getUsageInstance().unfreeze();
if (shouldUpdateUsage(innerYargs)) {
innerYargs
.getUsageInstance()
Expand Down
20 changes: 16 additions & 4 deletions lib/usage.ts
Expand Up @@ -231,7 +231,12 @@ export function usage(yargs: YargsInstance, y18n: Y18N, shim: PlatformShim) {

// your application's commands, i.e., non-option
// arguments populated in '_'.
if (commands.length) {
//
// If there's only a single command, and it's the default command
// (represented by commands[0][2]) don't show command stanza:
//
// TODO(@bcoe): why isnt commands[0][2] an object with a named property?
if (commands.length > 1 || (commands.length === 1 && !commands[0][2])) {
ui.div(__('Commands:'));

const context = yargs.getContext();
Expand Down Expand Up @@ -535,10 +540,14 @@ export function usage(yargs: YargsInstance, y18n: Y18N, shim: PlatformShim) {
(Object.keys(options.alias) || []).forEach(key => {
options.alias[key].forEach(alias => {
// copy descriptions.
if (descriptions[alias]) self.describe(key, descriptions[alias]);
if (descriptions[alias])
OsmanAltun marked this conversation as resolved.
Show resolved Hide resolved
self.describe(key, descriptions[key] || descriptions[alias]);
// copy demanded.
if (alias in demandedOptions)
yargs.demandOption(key, demandedOptions[alias]);
yargs.demandOption(
key,
demandedOptions[key] || demandedOptions[alias]
);
// type messages.
if (~options.boolean.indexOf(alias)) yargs.boolean(key);
if (~options.count.indexOf(alias)) yargs.count(key);
Expand Down Expand Up @@ -703,7 +712,10 @@ export function usage(yargs: YargsInstance, y18n: Y18N, shim: PlatformShim) {
};
self.unfreeze = function unfreeze() {
const frozen = frozens.pop();
assertNotStrictEqual(frozen, undefined, shim);
// In the case of running a defaultCommand, we reset
// usage early to ensure we receive the top level instructions.
// unfreezing again should just be a noop:
if (!frozen) return;
({
failMessage,
failureOutput,
Expand Down
12 changes: 5 additions & 7 deletions lib/yargs-factory.ts
Expand Up @@ -1483,7 +1483,7 @@ function Yargs(
commandIndex = 0,
helpOnly = false
) {
let skipValidation = !!calledFromCommand;
let skipValidation = !!calledFromCommand || helpOnly;
args = args || processArgs;

options.__ = y18n.__;
Expand Down Expand Up @@ -1550,10 +1550,8 @@ function Yargs(

const handlerKeys = command.getCommands();
const requestCompletions = completion!.completionKey in argv;
const skipRecommendation = argv[helpOpt!] || requestCompletions;
const skipDefaultCommand =
skipRecommendation &&
(handlerKeys.length > 1 || handlerKeys[0] !== '$0');
const skipRecommendation =
argv[helpOpt!] || requestCompletions || helpOnly;

if (argv._.length) {
if (handlerKeys.length) {
Expand Down Expand Up @@ -1584,7 +1582,7 @@ function Yargs(
}

// run the default command, if defined
if (command.hasDefaultCommand() && !skipDefaultCommand) {
if (command.hasDefaultCommand() && !skipRecommendation) {
const innerArgv = command.runCommand(
null,
self,
Expand Down Expand Up @@ -1617,7 +1615,7 @@ function Yargs(
self.showCompletionScript();
self.exit(0);
}
} else if (command.hasDefaultCommand() && !skipDefaultCommand) {
} else if (command.hasDefaultCommand() && !skipRecommendation) {
const innerArgv = command.runCommand(null, self, parsed, 0, helpOnly);
return self._postProcess(
innerArgv,
Expand Down
177 changes: 177 additions & 0 deletions test/usage.cjs
Expand Up @@ -4261,4 +4261,181 @@ describe('usage tests', () => {
' --small Packet size',
]);
});

// Refs: https://github.com/yargs/yargs/pull/1826
describe('usage for default command', () => {
describe('default only', () => {
const expected = [
'usage',
'',
'Default command description',
'',
'Options:',
' --help Show help [boolean]',
' --version Show version number [boolean]',
];

it('should contain the expected output for --help', () => {
const r = checkUsage(() =>
yargs('--help')
.scriptName('usage')
.command('*', 'Default command description')
.parse()
);

r.logs[0].split('\n').should.deep.equal(expected);
});

it('should contain the expected output for showhelp', () => {
const r = checkUsage(() => {
const y = yargs()
.scriptName('usage')
.command('*', 'Default command description');
y.showHelp('log');
});
r.logs[0].split('\n').should.deep.equal(expected);
});

it('should contain the expected output for getHelp', async () => {
const y = yargs()
.scriptName('usage')
.command('*', 'Default command description');
const help = await y.getHelp();
help.split('\n').should.deep.equal(expected);
});

it('should contain the expected output for getHelp when called from within handler', async () => {
let help = '';
const y = yargs()
.scriptName('usage')
.command('*', 'Default command description', {}, async () => {
help = await y.getHelp();
});
await y.parse();
help.split('\n').should.deep.equal(expected);
});

it('should contain the expected output for showHelp when called from within handler', () => {
const r = checkUsage(() =>
yargs()
.scriptName('usage')
.command('*', 'Default command description', {}, () =>
yargs.showHelp('log')
)
.parse('')
);
r.logs[0].split('\n').should.deep.equal(expected);
});

it('should contain the expected output for showHelp, when exception occurs', () => {
const r = checkUsage(() =>
yargs()
.scriptName('usage')
.command('*', 'Default command description', {}, () =>
yargs.showHelp('log')
)
.check(() => {
return false;
})
.parse('')
);
r.errors[0].split('\n').should.deep.equal(expected);
});
});

describe('multiple', () => {
const expected = [
'Hello, world!',
'',
'Commands:',
' usage Default command description [default]',
' usage foo Foo command description',
'',
'Options:',
' --help Show help [boolean]',
' --version Show version number [boolean]',
];
it('should contain the expected output for --help', () => {
const r = checkUsage(() =>
yargs('--help')
.scriptName('usage')
.usage('Hello, world!')
.commands([
{command: '*', desc: 'Default command description'},
{command: 'foo', desc: 'Foo command description'},
])
.parse()
);
r.logs[0].split('\n').should.deep.equal(expected);
});

it('should contain the expected output for showHelp', () => {
const r = checkUsage(() => {
yargs()
.scriptName('usage')
.usage('Hello, world!')
.commands([
{command: '*', desc: 'Default command description'},
{command: 'foo', desc: 'Foo command description'},
])
.parse();
yargs.showHelp('log');
});
r.logs[0].split('\n').should.deep.equal(expected);
});

it('should contain the expected output for showHelp when called from within handler', () => {
const r = checkUsage(() =>
yargs()
.scriptName('usage')
.usage('Hello, world!')
.commands([
{
command: '*',
desc: 'Default command description',
handler: _ => yargs.showHelp('log'),
},
{command: 'foo', desc: 'Foo command description'},
])
.parse('')
);
r.logs[0].split('\n').should.deep.equal(expected);
});

it('should contain the expected output for getHelp', async () => {
const y = yargs()
.scriptName('usage')
.usage('Hello, world!')
.commands([
{
command: '*',
desc: 'Default command description',
handler: () => {},
},
{command: 'foo', desc: 'Foo command description'},
]);
const help = await y.getHelp();
help.split('\n').should.deep.equal(expected);
});

it('should contain the expected output for getHelp when called from within handler', async () => {
let help = '';
const y = yargs()
.scriptName('usage')
.usage('Hello, world!')
.commands([
{
command: '*',
desc: 'Default command description',
handler: async () => {
help = await y.getHelp();
},
},
{command: 'foo', desc: 'Foo command description'},
]);
await y.parse();
help.split('\n').should.deep.equal(expected);
});
});
});
});