Improve command suggestions, add topic help

Change-type: minor
Resolves: #2021
Signed-off-by: Scott Lowe <scott@balena.io>
This commit is contained in:
Scott Lowe 2020-09-10 14:44:32 +02:00
parent b3510f205f
commit 00c956394d
3 changed files with 64 additions and 6 deletions

View File

@ -40,6 +40,27 @@ export default class BalenaHelp extends Help {
return; return;
} }
// If they've typed a topic (e.g. `balena os`) that isn't also a command (e.g. `balena device`)
// then list the associated commands.
const topicCommands = this.config.commands.filter((c) => {
return c.id.startsWith(`${subject}:`);
});
if (topicCommands.length > 0) {
console.log(`${chalk.yellow(subject)} commands include:`);
console.log(this.formatCommands(topicCommands));
console.log(
`\nRun ${chalk.cyan.bold(
'balena help -v',
)} for a list of all available commands,`,
);
console.log(
` or ${chalk.cyan.bold(
'balena help <command>',
)} for detailed help on a specific command.`,
);
return;
}
throw new ExpectedError(`command ${chalk.cyan.bold(subject)} not found`); throw new ExpectedError(`command ${chalk.cyan.bold(subject)} not found`);
} }

View File

@ -22,10 +22,12 @@ import type { IConfig } from '@oclif/config';
A modified version of the command-not-found plugin logic, A modified version of the command-not-found plugin logic,
which deals with spaces separators stead of colons, and which deals with spaces separators stead of colons, and
prints suggested commands instead of prompting interactively. prints suggested commands instead of prompting interactively.
Also see help.ts showHelp() for handling of topics.
*/ */
const hook: Hook<'command-not-found'> = async function ( const hook: Hook<'command-not-found'> = async function (
opts: object & { config: IConfig; id?: string }, opts: object & { config: IConfig; id?: string; argv?: string[] },
) { ) {
const Levenshtein = await import('fast-levenshtein'); const Levenshtein = await import('fast-levenshtein');
const _ = await import('lodash'); const _ = await import('lodash');
@ -44,16 +46,36 @@ const hook: Hook<'command-not-found'> = async function (
return _.minBy(commandIDs, (c) => Levenshtein.get(cmd, c))!; return _.minBy(commandIDs, (c) => Levenshtein.get(cmd, c))!;
} }
const suggestions: string[] = [];
suggestions.push(closest(commandId).replace(':', ' ') || '');
// opts.argv contains everything after the first command word
// if there's something there, also test if it might be a double
// word command spelt wrongly, rather than command args.
if (opts.argv?.[0]) {
suggestions.unshift(
closest(`${commandId}: + ${opts.argv[0]}`).replace(':', ' ') || '',
);
}
// Output suggestions
console.error( console.error(
`${color.yellow(command)} is not a recognized balena command.\n`, `${color.yellow(command)} is not a recognized balena command.\n`,
); );
console.error(`Did you mean: ? `);
const suggestion = closest(commandId).replace(':', ' ') || ''; suggestions.forEach((s) => {
console.log(`Did you mean: ${color.cmd(suggestion)} ? `); console.error(` ${color.cmd(s)}`);
console.log( });
`Run ${color.cmd('balena help -v')} for a list of available commands.`, console.error(
`\nRun ${color.cmd('balena help -v')} for a list of available commands,`,
);
console.error(
` or ${color.cmd(
'balena help <command>',
)} for detailed help on a specific command.`,
); );
// Exit
const COMMAND_NOT_FOUND = 127; const COMMAND_NOT_FOUND = 127;
process.exit(COMMAND_NOT_FOUND); process.exit(COMMAND_NOT_FOUND);
}; };

View File

@ -0,0 +1,15 @@
diff --git a/node_modules/@oclif/config/lib/config.js b/node_modules/@oclif/config/lib/config.js
index aba1da9..830f800 100644
--- a/node_modules/@oclif/config/lib/config.js
+++ b/node_modules/@oclif/config/lib/config.js
@@ -165,7 +165,9 @@ class Config {
debug('runCommand %s %o', id, argv);
const c = this.findCommand(id);
if (!c) {
- await this.runHook('command_not_found', { id });
+ // argv added to command_not_found hook
+ // We should try to upstream this change
+ await this.runHook('command_not_found', { id, argv });
throw new errors_1.CLIError(`command ${id} not found`);
}
const command = c.load();