From 51d1a6fbb2bc3fc6d9772fb3e2903664b1f8ffaf Mon Sep 17 00:00:00 2001 From: ayush-coder-hai Date: Wed, 18 Oct 2023 23:38:34 +0530 Subject: [PATCH] fixed the bug of help command. --- package-lock.json | 11 +++- package.json | 7 ++- src/hooks/command_not_found/myhook.ts | 74 +++++++++++++++++++++++++-- 3 files changed, 87 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2e51498524d..9c78ddc49b5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,6 +29,7 @@ "ajv": "^8.12.0", "chalk": "^4.1.0", "chokidar": "^3.5.2", + "fast-levenshtein": "^3.0.0", "fs-extra": "^11.1.0", "indent-string": "^4.0.0", "inquirer": "^8.2.0", @@ -54,6 +55,7 @@ "@oclif/test": "^2", "@swc/core": "^1.3.2", "@types/chai": "^4.3.6", + "@types/fast-levenshtein": "^0.0.2", "@types/fs-extra": "^11.0.1", "@types/inquirer": "^8.1.3", "@types/js-yaml": "^4.0.5", @@ -6279,6 +6281,12 @@ "version": "1.20.4", "license": "MIT" }, + "node_modules/@types/fast-levenshtein": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@types/fast-levenshtein/-/fast-levenshtein-0.0.2.tgz", + "integrity": "sha512-h9AGeNlFimLtFUlEZgk+hb3LUT4tNHu8y0jzCUeTdi1BM4e86sBQs/nQYgHk70ksNyNbuLwpymFAXkb0GAehmw==", + "dev": true + }, "node_modules/@types/fs-extra": { "version": "11.0.1", "dev": true, @@ -10873,7 +10881,8 @@ }, "node_modules/fast-levenshtein": { "version": "3.0.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-3.0.0.tgz", + "integrity": "sha512-hKKNajm46uNmTlhHSyZkmToAc56uZJwYq7yrciZjqOxnlfQwERDQJmHPUp7m1m9wx8vgOe8IaCKZ5Kv2k1DdCQ==", "dependencies": { "fastest-levenshtein": "^1.0.7" } diff --git a/package.json b/package.json index 2301604cbe7..4dc4d686fec 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "ajv": "^8.12.0", "chalk": "^4.1.0", "chokidar": "^3.5.2", + "fast-levenshtein": "^3.0.0", "fs-extra": "^11.1.0", "indent-string": "^4.0.0", "inquirer": "^8.2.0", @@ -50,6 +51,7 @@ "@oclif/test": "^2", "@swc/core": "^1.3.2", "@types/chai": "^4.3.6", + "@types/fast-levenshtein": "^0.0.2", "@types/fs-extra": "^11.0.1", "@types/inquirer": "^8.1.3", "@types/js-yaml": "^4.0.5", @@ -105,7 +107,10 @@ "bin": "asyncapi", "plugins": [], "hooks": { - "command_not_found": ["./lib/hooks/command_not_found/myhook"] }, + "command_not_found": [ + "./lib/hooks/command_not_found/myhook" + ] + }, "macos": { "identifier": "com.asyncapi.cli" }, diff --git a/src/hooks/command_not_found/myhook.ts b/src/hooks/command_not_found/myhook.ts index 3f4077a2324..19b9b350ff3 100644 --- a/src/hooks/command_not_found/myhook.ts +++ b/src/hooks/command_not_found/myhook.ts @@ -1,7 +1,75 @@ -import {Hook} from '@oclif/core'; +import {Hook, toConfiguredId, CliUx} from '@oclif/core'; +import chalk from 'chalk'; +import {default as levenshtein} from 'fast-levenshtein'; +import { Help } from '@oclif/core'; -const hook: Hook<'command_not_found'> = async function (opts) { - if (opts.id === 'help') { process.stdout.write(`${opts.id} command not found.\n`);} +export const closest = (target: string, possibilities: string[]): string => + possibilities + .map((id) => ({distance: levenshtein.get(target, id, {useCollator: true}), id})) + .sort((a, b) => a.distance - b.distance)[0]?.id ?? ''; + +const hook: Hook.CommandNotFound = async function (opts) { + if (opts.id === '--help') { + const help = new Help(this.config); + help.showHelp(['--help']); + return; + } + const hiddenCommandIds = new Set(opts.config.commands.filter((c) => {c.hidden;}).map((c) => c.id)); + const commandIDs = [...opts.config.commandIDs, ...opts.config.commands.flatMap((c) => c.aliases)].filter( + (c) => !hiddenCommandIds.has(c), + ); + //here we get the command ids and hidden command ids. + + if (commandIDs.length === 0) {return;} + // now we we return if the command id are not there. + + let binHelp = `${opts.config.bin} help`; + + const idSplit = opts.id.split(':'); + if (opts.config.findTopic(idSplit[0])) { + // if valid topic, update binHelp with topic + binHelp = `${binHelp} ${idSplit[0]}`; + } + + //if there is a topic in the opts we just upgrade the our commnad like + + // alter the suggestion in the help scenario so that help is the first command + // otherwise the user will be presented 'did you mean 'help'?' instead of 'did you mean "help "?' + let suggestion = (/:?help:?/).test(opts.id) + ? ['help', ...opts.id.split(':').filter((cmd) => cmd !== 'help')].join(':') + : closest(opts.id, commandIDs); + + let readableSuggestion = toConfiguredId(suggestion, this.config); + const originalCmd = toConfiguredId(opts.id, this.config); + this.warn(`${chalk.yellow(originalCmd)} is not a ${opts.config.bin} command.`); + + let response = ''; + try { + if (opts.id === 'help') {readableSuggestion = '--help';} + response = await CliUx.ux.prompt(`Did you mean ${chalk.blueBright(readableSuggestion)}? [y/n]`, {timeout: 10_000}); + } catch (error) { + this.log(''); + this.debug(error); + } + + if (response === 'y') { + // this will split the original command from the suggested replacement, and gather the remaining args as varargs to help with situations like: + // confit set foo-bar -> confit:set:foo-bar -> config:set:foo-bar -> config:set foo-bar + let argv = opts.argv?.length ? opts.argv : opts.id.split(':').slice(suggestion.split(':').length); + if (suggestion.startsWith('help:')) { + // the args are the command/partial command you need help for (package:version) + // we created the suggestion variable to start with "help" so slice the first entry + argv = suggestion.split(':').slice(1); + // the command is just the word "help" + suggestion = 'help'; + } + if (opts.id === 'help') { + return this.config.runCommand('--help'); + } + return this.config.runCommand(suggestion, argv); + } + + this.error(`Run ${chalk.bold.cyan(binHelp)} for a list of available commands.`, {exit: 127}); }; export default hook;