From a3fe8a7b44aafa834282db220e54c3be9e9f1c65 Mon Sep 17 00:00:00 2001 From: Jeff Smale <6363749+jeffsmale90@users.noreply.github.com> Date: Wed, 1 Feb 2023 15:50:04 +1300 Subject: [PATCH 1/7] When attempting to stop a detached instance, select the instance if there's a single prefix match, or suggest close matches --- src/packages/cli/src/cli.ts | 18 +++- src/packages/cli/src/detach.ts | 156 ++++++++++++++++++++++++++++++--- 2 files changed, 158 insertions(+), 16 deletions(-) diff --git a/src/packages/cli/src/cli.ts b/src/packages/cli/src/cli.ts index 1864b4d374..8fbe5fedcf 100644 --- a/src/packages/cli/src/cli.ts +++ b/src/packages/cli/src/cli.ts @@ -164,11 +164,21 @@ if (argv.action === "start") { } else if (argv.action === "stop") { const instanceName = argv.name; - stopDetachedInstance(instanceName).then(instanceFound => { - if (instanceFound) { - console.log("Instance stopped"); + stopDetachedInstance(instanceName).then(instanceOrSuggestions => { + if ("instance" in instanceOrSuggestions) { + const highlightedName = porscheColor(instanceOrSuggestions.instance.name); + console.log(`${highlightedName} stopped.`); } else { - console.error("Instance not found"); + console.log("Instance not found."); + if (instanceOrSuggestions.suggestions?.length > 0) { + console.log(); + console.log("Did you mean:"); + console.log( + instanceOrSuggestions.suggestions + .map(name => " - " + porscheColor(name)) + .join("\n") + ); + } } }); } else if (argv.action === "start-detached") { diff --git a/src/packages/cli/src/detach.ts b/src/packages/cli/src/detach.ts index c4860bc32c..32a9f99aa5 100644 --- a/src/packages/cli/src/detach.ts +++ b/src/packages/cli/src/detach.ts @@ -19,6 +19,8 @@ export type DetachedInstance = { version: string; }; +const MAX_SUGGESTIONS = 4; +const MAX_LEVENSHTEIN_DISTANCE = 10; const FILE_ENCODING = "utf8"; const START_ERROR = "An error occurred spawning a detached instance of Ganache:"; @@ -53,6 +55,12 @@ export async function removeDetachedInstanceFile( return false; } +// A fuzzy matched detached instance(s). Either a strong match as instance, +// or a list of suggestions. +type InstanceOrSuggestions = + | { instance: DetachedInstance } + | { suggestions: string[] }; + /** * Attempts to stop a detached instance with the specified instance name by * sending a SIGTERM signal. Returns a boolean indicating whether the process @@ -61,25 +69,118 @@ export async function removeDetachedInstanceFile( * * Note: This does not guarantee that the instance actually stops. * @param {string} instanceName - * @returns boolean indicating whether the instance was found. + * @returns {InstanceOrSuggestions} either the stopped instance, or suggestions for similar instance names */ export async function stopDetachedInstance( instanceName: string -): Promise { +): Promise { + let instance; + try { - // getDetachedInstanceByName() throws if the instance file is not found or - // cannot be parsed - const instance = await getDetachedInstanceByName(instanceName); + instance = await getDetachedInstanceByName(instanceName); + } catch { + const similarInstances = await getSimilarInstanceNames(instanceName); + + if (similarInstances.match) { + try { + instance = await getDetachedInstanceByName(similarInstances.match); + } catch (err) { + if ((err as NodeJS.ErrnoException).code !== "ENOENT") { + // The instance file was removed between the call to + // `getSimilarInstanceNames` and `getDetachedInstancesByName`, but we + // didn't get suggestions (although some may exist). We _could_ + // reiterate stopDetachedInstance but that seems messy. Let's just + // output "Instance not found", and be done with it. + return { + suggestions: [] + }; + } + throw err; + } + } else { + return { suggestions: similarInstances.suggestions }; + } + } + + if (instance) { + // process.kill() throws if the process was not found (or was a group process in Windows) + try { + process.kill(instance.pid, "SIGTERM"); + } catch (err) { + // process not found + // todo: log message saying that the process could not be found + } finally { + await removeDetachedInstanceFile(instance.name); + return { instance }; + } + } +} + +/* +Find instances with names similar to instanceName. - // process.kill() throws if the process was not found (or was a group - // process in Windows) - process.kill(instance.pid, "SIGTERM"); +If there is a single instance with an exact prefix match, it is returned as the +match property in the result. Otherwise, up to `MAX_SUGGESTIONS` names that are +similar to instanceName are returned, prioritizing names that start with +instanceName and then ordered by increasing Levenshtein distance, with a maximum +distance of `MAX_LEVENSHTEIN_DISTANCE`. +*/ +async function getSimilarInstanceNames( + instanceName: string +): Promise<{ match?: string; suggestions?: string[] }> { + let filenames: string[]; + try { + filenames = (await fsPromises.readdir(dataPath, { withFileTypes: true })) + .map(file => { + const { name, ext } = path.parse(file.name); + if (ext === ".json") return name; + }) + .filter(name => name !== undefined); } catch (err) { - return false; - } finally { - await removeDetachedInstanceFile(instanceName); + if ((err as NodeJS.ErrnoException).code === "ENOENT") { + // instances directory does not exist, so there can be no suggestions + return { suggestions: [] }; + } + } + + const matches = []; + for (const name of filenames) { + if (name.startsWith(instanceName)) { + matches.push(name); + } + } + + if (matches.length === 1) { + return { match: matches[0] }; + } + + const similar = []; + for (const name of filenames) { + const distance = levenshteinDistance(instanceName, name); + + similar.push({ + name, + distance + }); + } + + const suggestions = similar + .filter( + s => + s.distance <= MAX_LEVENSHTEIN_DISTANCE && + !matches.some(m => m === s.name) + ) + .sort((a, b) => a.distance - b.distance) + .map(s => s.name); + + // matches should be at the start of the suggestions array + suggestions.splice(0, 0, ...matches); + + if (similar.length > 0) { + return { + suggestions: suggestions.slice(0, MAX_SUGGESTIONS) + }; } - return true; } /** @@ -323,3 +424,34 @@ export function formatUptime(ms: number) { return isFuture ? `In ${duration}` : duration; } + +export function levenshteinDistance(a: string, b: string): number { + if (a.length === 0) return b.length; + if (b.length === 0) return a.length; + + let matrix = []; + + for (let i = 0; i <= b.length; i++) { + matrix[i] = [i]; + } + + for (let j = 0; j <= a.length; j++) { + matrix[0][j] = j; + } + + for (let i = 1; i <= b.length; i++) { + for (let j = 1; j <= a.length; j++) { + if (b.charAt(i - 1) == a.charAt(j - 1)) { + matrix[i][j] = matrix[i - 1][j - 1]; + } else { + matrix[i][j] = Math.min( + matrix[i - 1][j - 1] + 1, + matrix[i][j - 1] + 1, + matrix[i - 1][j] + 1 + ); + } + } + } + + return matrix[b.length][a.length]; +} From bb413f60aae19112ff29b60507bfbbe0441ad055 Mon Sep 17 00:00:00 2001 From: Jeff Smale <6363749+jeffsmale90@users.noreply.github.com> Date: Wed, 1 Feb 2023 15:59:34 +1300 Subject: [PATCH 2/7] Add some tests for levenshteinDistance function --- src/packages/cli/tests/detach.test.ts | 60 ++++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/src/packages/cli/tests/detach.test.ts b/src/packages/cli/tests/detach.test.ts index e7507387f3..c2a1b68fae 100644 --- a/src/packages/cli/tests/detach.test.ts +++ b/src/packages/cli/tests/detach.test.ts @@ -1,8 +1,66 @@ import assert from "assert"; -import { formatUptime } from "../src/detach"; +import { formatUptime, levenshteinDistance } from "../src/detach"; describe("@ganache/cli", () => { describe("detach", () => { + describe("levenshteinDistance", () => { + it("returns 0 for identical strings", () => { + const a = "hello"; + const b = "hello"; + const result = levenshteinDistance(a, b); + + assert.strictEqual(result, 0); + }); + + it("returns correct distance for different strings", () => { + const a = "hello"; + const b = "world"; + const result = levenshteinDistance(a, b); + + assert.strictEqual(result, 4); + }); + + it("returns correct distance for strings of different lengths", () => { + const a = "hello"; + const b = "hi"; + const result = levenshteinDistance(a, b); + + assert.strictEqual(result, 4); + }); + + it("returns correct distance for strings with additions", () => { + const a = "hello"; + const b = "heBlAlo"; + const result = levenshteinDistance(a, b); + + assert.strictEqual(result, 2); + }); + + it("returns correct distance for strings with subtractions", () => { + const a = "hello"; + const b = "hll"; + const result = levenshteinDistance(a, b); + + assert.strictEqual(result, 2); + }); + + it("returns correct distance for strings with substitutions", () => { + const a = "hello"; + const b = "hAlAo"; + const result = levenshteinDistance(a, b); + + assert.strictEqual(result, 2); + }); + + it("returns correct distance for strings with addition, subtraction and substitution", () => { + const a = "hello world"; + const b = "helloo wolB"; + const result = levenshteinDistance(a, b); + + assert.strictEqual(result, 3); + }); + }); + describe("formatUptime()", () => { const durations: [number, string][] = [ [0, "Just started"], From 1fe3dd79b7893b1521eb24374cd9795cc704c8d9 Mon Sep 17 00:00:00 2001 From: Jeff Smale <6363749+jeffsmale90@users.noreply.github.com> Date: Thu, 2 Feb 2023 10:01:56 +1300 Subject: [PATCH 3/7] Return error code 1 when we fail to find an instance. Don't bother with L-diggity if we already have enough prefix matches. Refine distinction between 'match' and 'instance'. --- src/packages/cli/src/cli.ts | 4 ++- src/packages/cli/src/detach.ts | 59 +++++++++++++++++++--------------- 2 files changed, 36 insertions(+), 27 deletions(-) diff --git a/src/packages/cli/src/cli.ts b/src/packages/cli/src/cli.ts index 8fbe5fedcf..58c249487d 100644 --- a/src/packages/cli/src/cli.ts +++ b/src/packages/cli/src/cli.ts @@ -169,6 +169,7 @@ if (argv.action === "start") { const highlightedName = porscheColor(instanceOrSuggestions.instance.name); console.log(`${highlightedName} stopped.`); } else { + process.exitCode = 1; console.log("Instance not found."); if (instanceOrSuggestions.suggestions?.length > 0) { console.log(); @@ -191,7 +192,8 @@ if (argv.action === "start") { }) .catch(err => { // the child process would have output its error to stdout, so no need to - // output anything more + // output anything more other than set the exitCode + process.exitCode = 1; }); } else if (argv.action === "list") { getDetachedInstances().then(instances => { diff --git a/src/packages/cli/src/detach.ts b/src/packages/cli/src/detach.ts index 32a9f99aa5..a9d9505d13 100644 --- a/src/packages/cli/src/detach.ts +++ b/src/packages/cli/src/detach.ts @@ -81,7 +81,7 @@ export async function stopDetachedInstance( } catch { const similarInstances = await getSimilarInstanceNames(instanceName); - if (similarInstances.match) { + if ("match" in similarInstances) { try { instance = await getDetachedInstanceByName(similarInstances.match); } catch (err) { @@ -117,17 +117,17 @@ export async function stopDetachedInstance( } /* -Find instances with names similar to instanceName. +Find instances with names similar to `instanceName`. If there is a single instance with an exact prefix match, it is returned as the -match property in the result. Otherwise, up to `MAX_SUGGESTIONS` names that are -similar to instanceName are returned, prioritizing names that start with -instanceName and then ordered by increasing Levenshtein distance, with a maximum -distance of `MAX_LEVENSHTEIN_DISTANCE`. +`match` property in the result. Otherwise, up to `MAX_SUGGESTIONS` names that +are similar to `instanceName` are returned as `suggestions`. Names with an exact +prefix match are prioritized, followed by increasing Levenshtein distance, up to +a maximum distance of `MAX_LEVENSHTEIN_DISTANCE`. */ async function getSimilarInstanceNames( instanceName: string -): Promise<{ match?: string; suggestions?: string[] }> { +): Promise<{ match: string } | { suggestions: string[] }> { let filenames: string[]; try { filenames = (await fsPromises.readdir(dataPath, { withFileTypes: true })) @@ -154,33 +154,40 @@ async function getSimilarInstanceNames( return { match: matches[0] }; } - const similar = []; - for (const name of filenames) { - const distance = levenshteinDistance(instanceName, name); + let suggestions: string[]; + if (matches.length >= MAX_SUGGESTIONS) { + suggestions = matches; + } else { + const similar = []; - similar.push({ - name, - distance - }); - } + for (const name of filenames) { + const distance = levenshteinDistance(instanceName, name); - const suggestions = similar - .filter( - s => - s.distance <= MAX_LEVENSHTEIN_DISTANCE && - !matches.some(m => m === s.name) - ) - .sort((a, b) => a.distance - b.distance) - .map(s => s.name); + similar.push({ + name, + distance + }); + } - // matches should be at the start of the suggestions array - suggestions.splice(0, 0, ...matches); + suggestions = similar + .filter( + s => + s.distance <= MAX_LEVENSHTEIN_DISTANCE && + !matches.some(m => m === s.name) + ) + .sort((a, b) => a.distance - b.distance) + .map(s => s.name); + // matches should be at the start of the suggestions array + suggestions.splice(0, 0, ...matches); + } - if (similar.length > 0) { + if (suggestions.length > 0) { return { suggestions: suggestions.slice(0, MAX_SUGGESTIONS) }; } + + return { suggestions: [] }; } /** From 65381c05c6b65cad8ef7655b58382448257825aa Mon Sep 17 00:00:00 2001 From: Jeff Smale <6363749+jeffsmale90@users.noreply.github.com> Date: Thu, 2 Feb 2023 12:21:28 +1300 Subject: [PATCH 4/7] Include input name in not-found message --- src/packages/cli/src/cli.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/packages/cli/src/cli.ts b/src/packages/cli/src/cli.ts index 58c249487d..9b9271c01f 100644 --- a/src/packages/cli/src/cli.ts +++ b/src/packages/cli/src/cli.ts @@ -170,7 +170,7 @@ if (argv.action === "start") { console.log(`${highlightedName} stopped.`); } else { process.exitCode = 1; - console.log("Instance not found."); + console.log(`${porscheColor(instanceName)} not found.`); if (instanceOrSuggestions.suggestions?.length > 0) { console.log(); console.log("Did you mean:"); From 18ab2c5118c0bf48def2c479acc59ab8d217b4f1 Mon Sep 17 00:00:00 2001 From: Jeff Smale <6363749+jeffsmale90@users.noreply.github.com> Date: Thu, 2 Feb 2023 15:15:03 +1300 Subject: [PATCH 5/7] Tidy up the place a little --- src/packages/cli/src/cli.ts | 2 +- src/packages/cli/src/detach.ts | 76 +++++++++++++++++----------------- 2 files changed, 38 insertions(+), 40 deletions(-) diff --git a/src/packages/cli/src/cli.ts b/src/packages/cli/src/cli.ts index 9b9271c01f..992adb200a 100644 --- a/src/packages/cli/src/cli.ts +++ b/src/packages/cli/src/cli.ts @@ -192,7 +192,7 @@ if (argv.action === "start") { }) .catch(err => { // the child process would have output its error to stdout, so no need to - // output anything more other than set the exitCode + // do anything more other than set the exitCode process.exitCode = 1; }); } else if (argv.action === "list") { diff --git a/src/packages/cli/src/detach.ts b/src/packages/cli/src/detach.ts index a9d9505d13..672bf908b8 100644 --- a/src/packages/cli/src/detach.ts +++ b/src/packages/cli/src/detach.ts @@ -69,7 +69,8 @@ type InstanceOrSuggestions = * * Note: This does not guarantee that the instance actually stops. * @param {string} instanceName - * @returns {InstanceOrSuggestions} either the stopped instance, or suggestions for similar instance names + * @returns {InstanceOrSuggestions} either the stopped `instance`, or + * `suggestions` for similar instance names */ export async function stopDetachedInstance( instanceName: string @@ -85,12 +86,12 @@ export async function stopDetachedInstance( try { instance = await getDetachedInstanceByName(similarInstances.match); } catch (err) { - if ((err as NodeJS.ErrnoException).code !== "ENOENT") { + if ((err as NodeJS.ErrnoException).code === "ENOENT") { // The instance file was removed between the call to // `getSimilarInstanceNames` and `getDetachedInstancesByName`, but we // didn't get suggestions (although some may exist). We _could_ // reiterate stopDetachedInstance but that seems messy. Let's just - // output "Instance not found", and be done with it. + // tell the user the instance wasn't found, and be done with it. return { suggestions: [] }; @@ -103,7 +104,8 @@ export async function stopDetachedInstance( } if (instance) { - // process.kill() throws if the process was not found (or was a group process in Windows) + // process.kill() throws if the process was not found (or was a group + // process in Windows) try { process.kill(instance.pid, "SIGTERM"); } catch (err) { @@ -128,14 +130,17 @@ a maximum distance of `MAX_LEVENSHTEIN_DISTANCE`. async function getSimilarInstanceNames( instanceName: string ): Promise<{ match: string } | { suggestions: string[] }> { - let filenames: string[]; + const filenames: string[] = []; try { - filenames = (await fsPromises.readdir(dataPath, { withFileTypes: true })) - .map(file => { - const { name, ext } = path.parse(file.name); - if (ext === ".json") return name; - }) - .filter(name => name !== undefined); + const parsedPaths = ( + await fsPromises.readdir(dataPath, { withFileTypes: true }) + ).map(file => path.parse(file.name)); + + for (const { ext, name } of parsedPaths) { + if (ext === ".json") { + filenames.push(name); + } + } } catch (err) { if ((err as NodeJS.ErrnoException).code === "ENOENT") { // instances directory does not exist, so there can be no suggestions @@ -143,51 +148,44 @@ async function getSimilarInstanceNames( } } - const matches = []; + const prefixMatches = []; for (const name of filenames) { if (name.startsWith(instanceName)) { - matches.push(name); + prefixMatches.push(name); } } - if (matches.length === 1) { - return { match: matches[0] }; + if (prefixMatches.length === 1) { + return { match: prefixMatches[0] }; } let suggestions: string[]; - if (matches.length >= MAX_SUGGESTIONS) { - suggestions = matches; + if (prefixMatches.length >= MAX_SUGGESTIONS) { + suggestions = prefixMatches; } else { const similar = []; for (const name of filenames) { - const distance = levenshteinDistance(instanceName, name); - - similar.push({ - name, - distance - }); + if (!prefixMatches.some(m => m === name)) { + const distance = levenshteinDistance(instanceName, name); + if (distance <= MAX_LEVENSHTEIN_DISTANCE) { + similar.push({ + name, + distance + }); + } + } } + similar.sort((a, b) => a.distance - b.distance); - suggestions = similar - .filter( - s => - s.distance <= MAX_LEVENSHTEIN_DISTANCE && - !matches.some(m => m === s.name) - ) - .sort((a, b) => a.distance - b.distance) - .map(s => s.name); + suggestions = similar.map(s => s.name); // matches should be at the start of the suggestions array - suggestions.splice(0, 0, ...matches); + suggestions.splice(0, 0, ...prefixMatches); } - if (suggestions.length > 0) { - return { - suggestions: suggestions.slice(0, MAX_SUGGESTIONS) - }; - } - - return { suggestions: [] }; + return { + suggestions: suggestions.slice(0, MAX_SUGGESTIONS) + }; } /** From e8051069a8267d296d05a9069e45ef0912fee274 Mon Sep 17 00:00:00 2001 From: jeffsmale90 <6363749+jeffsmale90@users.noreply.github.com> Date: Thu, 2 Feb 2023 15:19:59 +1300 Subject: [PATCH 6/7] Add comments --- src/packages/cli/src/detach.ts | 36 +++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/src/packages/cli/src/detach.ts b/src/packages/cli/src/detach.ts index 672bf908b8..45ab56aa7f 100644 --- a/src/packages/cli/src/detach.ts +++ b/src/packages/cli/src/detach.ts @@ -69,8 +69,8 @@ type InstanceOrSuggestions = * * Note: This does not guarantee that the instance actually stops. * @param {string} instanceName - * @returns {InstanceOrSuggestions} either the stopped `instance`, or - * `suggestions` for similar instance names + * @returns {InstanceOrSuggestions} an object containing either the stopped + * `instance`, or `suggestions` for similar instance names */ export async function stopDetachedInstance( instanceName: string @@ -118,15 +118,19 @@ export async function stopDetachedInstance( } } -/* -Find instances with names similar to `instanceName`. - -If there is a single instance with an exact prefix match, it is returned as the -`match` property in the result. Otherwise, up to `MAX_SUGGESTIONS` names that -are similar to `instanceName` are returned as `suggestions`. Names with an exact -prefix match are prioritized, followed by increasing Levenshtein distance, up to -a maximum distance of `MAX_LEVENSHTEIN_DISTANCE`. -*/ +/** + * Find instances with names similar to `instanceName`. + * + * If there is a single instance with an exact prefix match, it is returned as + * the `match` property in the result. Otherwise, up to `MAX_SUGGESTIONS` names + * that are similar to `instanceName` are returned as `suggestions`. Names with + * an exact prefix match are prioritized, followed by increasing Levenshtein + * distance, up to a maximum distance of `MAX_LEVENSHTEIN_DISTANCE`. + * @param {string} instanceName the name for which similarly named instance will + * be searched + * @returns {{ match: string } | { suggestions: string[] }} an object + * containiner either a single exact `match` or a number of `suggestions` + */ async function getSimilarInstanceNames( instanceName: string ): Promise<{ match: string } | { suggestions: string[] }> { @@ -430,6 +434,16 @@ export function formatUptime(ms: number) { return isFuture ? `In ${duration}` : duration; } +/** + * This function calculates the Levenshtein distance between two strings. + * Levenshtein distance is a measure of the difference between two strings, + * defined as the minimum number of edits (insertions, deletions or substitutions) + * required to transform one string into another. + * + * @param {string} a - The first string to compare. + * @param {string} b - The second string to compare. + * @return {number} The Levenshtein distance between the two strings. + */ export function levenshteinDistance(a: string, b: string): number { if (a.length === 0) return b.length; if (b.length === 0) return a.length; From 4ce7169b6aa9b36bf51365ebf297a18e5eb71361 Mon Sep 17 00:00:00 2001 From: Jeff Smale <6363749+jeffsmale90@users.noreply.github.com> Date: Wed, 19 Apr 2023 17:01:04 +1200 Subject: [PATCH 7/7] Remove types from doc-comments, improve wordology in instance-not-found message --- src/packages/cli/src/cli.ts | 11 +++++++++-- src/packages/cli/src/detach.ts | 23 ++++++++++++----------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/packages/cli/src/cli.ts b/src/packages/cli/src/cli.ts index 992adb200a..e40df52ca8 100644 --- a/src/packages/cli/src/cli.ts +++ b/src/packages/cli/src/cli.ts @@ -170,16 +170,23 @@ if (argv.action === "start") { console.log(`${highlightedName} stopped.`); } else { process.exitCode = 1; - console.log(`${porscheColor(instanceName)} not found.`); + console.log(`We couldn't find '${porscheColor(instanceName)}'.`); if (instanceOrSuggestions.suggestions?.length > 0) { console.log(); - console.log("Did you mean:"); + console.log("But here's some instances with similar names:"); console.log( instanceOrSuggestions.suggestions .map(name => " - " + porscheColor(name)) .join("\n") ); } + + console.log(); + console.log( + `Try ${porscheColor( + "ganache instances list" + )} to see all running instances.` + ); } }); } else if (argv.action === "start-detached") { diff --git a/src/packages/cli/src/detach.ts b/src/packages/cli/src/detach.ts index 45ab56aa7f..db2c2be203 100644 --- a/src/packages/cli/src/detach.ts +++ b/src/packages/cli/src/detach.ts @@ -41,8 +41,9 @@ export function notifyDetachedInstanceReady(port: number) { /** * Attempt to find and remove the instance file for a detached instance. - * @param {string} instanceName the name of the instance to be removed - * @returns boolean indicating whether the instance file was cleaned up successfully + * @param instanceName the name of the instance to be removed + * @returns resolves to a boolean indicating whether the instance file was + * cleaned up successfully */ export async function removeDetachedInstanceFile( instanceName: string @@ -68,8 +69,8 @@ type InstanceOrSuggestions = * corresponding instance file will be removed. * * Note: This does not guarantee that the instance actually stops. - * @param {string} instanceName - * @returns {InstanceOrSuggestions} an object containing either the stopped + * @param instanceName + * @returns an object containing either the stopped * `instance`, or `suggestions` for similar instance names */ export async function stopDetachedInstance( @@ -194,8 +195,8 @@ async function getSimilarInstanceNames( /** * Start an instance of Ganache in detached mode. - * @param {string[]} argv arguments to be passed to the new instance. - * @returns {Promise} resolves to the DetachedInstance once it + * @param argv arguments to be passed to the new instance. + * @returns resolves to the DetachedInstance once it * is started and ready to receive requests. */ export async function startDetachedInstance( @@ -310,7 +311,7 @@ export async function startDetachedInstance( /** * Fetch all instance of Ganache running in detached mode. Cleans up any * instance files for processes that are no longer running. - * @returns {Promise} resolves with an array of instances + * @returns resolves with an array of instances */ export async function getDetachedInstances(): Promise { let dirEntries: Dirent[]; @@ -402,7 +403,7 @@ export async function getDetachedInstances(): Promise { /** * Attempts to load data for the instance specified by instanceName. Throws if * the instance file is not found or cannot be parsed - * @param {string} instanceName + * @param instanceName */ async function getDetachedInstanceByName( instanceName: string @@ -440,9 +441,9 @@ export function formatUptime(ms: number) { * defined as the minimum number of edits (insertions, deletions or substitutions) * required to transform one string into another. * - * @param {string} a - The first string to compare. - * @param {string} b - The second string to compare. - * @return {number} The Levenshtein distance between the two strings. + * @param a - The first string to compare. + * @param b - The second string to compare. + * @return The Levenshtein distance between the two strings. */ export function levenshteinDistance(a: string, b: string): number { if (a.length === 0) return b.length;