diff --git a/package-lock.json b/package-lock.json index af36ffb..14bd2cc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,8 @@ "ora": "^6.1.2", "pino": "^8.3.1", "string-format": "^2.0.0", - "string-similarity-js": "^2.1.4" + "string-similarity-js": "^2.1.4", + "typescript-string-operations": "^1.4.1" }, "devDependencies": { "@types/node": "^18.6.3", @@ -835,6 +836,11 @@ "node": ">=4.2.0" } }, + "node_modules/typescript-string-operations": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/typescript-string-operations/-/typescript-string-operations-1.4.1.tgz", + "integrity": "sha512-c+q+Tb0hxeebohdT9KpGUAm5zwxhU8pHeNOeuLCGFMXKN0OrldoAxtufrGLR3xSPCXDA4A3IBCEdRNNscVqLQg==" + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -1416,6 +1422,11 @@ "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", "dev": true }, + "typescript-string-operations": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/typescript-string-operations/-/typescript-string-operations-1.4.1.tgz", + "integrity": "sha512-c+q+Tb0hxeebohdT9KpGUAm5zwxhU8pHeNOeuLCGFMXKN0OrldoAxtufrGLR3xSPCXDA4A3IBCEdRNNscVqLQg==" + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index 5a0aff1..b0ad659 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,8 @@ "ora": "^6.1.2", "pino": "^8.3.1", "string-format": "^2.0.0", - "string-similarity-js": "^2.1.4" + "string-similarity-js": "^2.1.4", + "typescript-string-operations": "^1.4.1" }, "devDependencies": { "@types/node": "^18.6.3", diff --git a/src/commands/uninstall_command.ts b/src/commands/uninstall_command.ts index 1f5b0df..9416b7d 100644 --- a/src/commands/uninstall_command.ts +++ b/src/commands/uninstall_command.ts @@ -9,9 +9,9 @@ export default class UninstallCommand implements Subcommand { .description("Uninstalls the provided mods") .argument("", "The mods to uninstall (as names or ids)") .action((mods) => { - ModManager.execute(() => { + ModManager.execute(async () => { for (let mod of mods) { - Mods.uninstall(mod); + await Mods.uninstall(mod); } }) }) diff --git a/src/mods/mods.ts b/src/mods/mods.ts index 245bb1d..c70a2f8 100644 --- a/src/mods/mods.ts +++ b/src/mods/mods.ts @@ -7,6 +7,9 @@ import Util from "../util/util.js"; import ModManager from "../mod-manager.js"; import MinecraftUtils from "../util/minecraft_utils.js"; import MigrateError from "../errors/migrate_error.js"; +import inquirer from "inquirer"; +import { StringBuilder } from 'typescript-string-operations'; +import chalk from "chalk"; export default class Mods { private static readonly MOD_SOURCES: Array = []; @@ -97,26 +100,60 @@ export default class Mods { return !Util.isArrayEmpty(modsWithId) } - static uninstall(mod: string) { + private static getDependantMods(dependency: string) { + return this.getTrackedMods().filter(mod => mod.dependencies.includes(dependency)) + } + + static async uninstall(mod: string) { // Find mod to uninstall + const modToUninstall = this.findMod(mod); + const spinner = new PrintUtils.Spinner(`Uninstalling ${mod}...`) spinner.start(); - const modToUninstall = this.findMod(mod); - // IF a matching mod is found, remove it + // If a matching mod is found, remove it if (modToUninstall != undefined) { - this.silentUninstall(modToUninstall); - for (let dependency of modToUninstall.dependencies) { - if (!this.isDependedOn(dependency)) { - const dependencyMod = this.findMod(dependency); - if (dependencyMod != undefined) { - this.silentUninstall(dependencyMod) - } - } + // Ensure the user wants to delete this mod + let del = true; + if (Mods.isDependedOn(modToUninstall.id)) { + spinner.pause(); + + const dependantMods = Mods.getDependantMods(modToUninstall.id); + const answer = await inquirer.prompt([{ + type: "input", + name: "delete", + message: chalk.yellowBright(`${modToUninstall.name} is depended on by ${Mods.modListToSting(dependantMods)}. Are you sure you would like to uninstall? (y/n)`), + async validate(input: any): Promise { + const lowerInput = input.toLowerCase(); + const valid = lowerInput === "y" || lowerInput === "n" ; + if (!valid) { + return "Please answer either y or n" + } + return valid + }, + }]) + del = Util.getBoolFromYesNo(answer.delete); + } + + // If we are deleting this mod, uninstall it + if (del) { + spinner.start() + this.silentUninstall(modToUninstall); + + // Remove any left over dependencies that are not depended on by any other mod + for (let dependency of modToUninstall.dependencies) { + if (!this.isDependedOn(dependency)) { + const dependencyMod = this.findMod(dependency); + if (dependencyMod != undefined) { + this.silentUninstall(dependencyMod) + } + } + } + + spinner.succeed(`${modToUninstall.name} successfully uninstalled!`) } - spinner.succeed(`${modToUninstall.name} successfully uninstalled!`) } else { spinner.error(`${mod} was not found.`) } @@ -366,13 +403,14 @@ export default class Mods { } private static isDependedOn(dependency: string) { - const trackedMods = this.getTrackedMods(); + return Mods.getDependantMods(dependency).length != 0 + } - for (let trackedMod of trackedMods) { - if (trackedMod.dependencies.includes(dependency)) { - return true; - } + private static modListToSting(dependantMods: TrackedMod[]) { + const builder = new StringBuilder(); + for (let dependantMod of dependantMods) { + builder.Append(dependantMod.name) } - return false; + return builder.ToString(); } } \ No newline at end of file diff --git a/src/util/print_utils.ts b/src/util/print_utils.ts index 577ec06..75142f6 100644 --- a/src/util/print_utils.ts +++ b/src/util/print_utils.ts @@ -24,6 +24,10 @@ export default class PrintUtils { this.spinner.stopAndPersist(); } + public pause() { + this.spinner.stop(); + } + public error(print: string | Error) { if (print instanceof Error) { this.spinner.fail(print.message) diff --git a/src/util/util.ts b/src/util/util.ts index 6cb8cd6..7e553e6 100644 --- a/src/util/util.ts +++ b/src/util/util.ts @@ -21,4 +21,14 @@ export default class Util { compare = compare.toLowerCase(); return stringSimilarity(master, compare) >= this.SIMILARITY_THRESHOLD; } + + static getBoolFromYesNo(answer: string) { + if (answer.toLowerCase() === "y") { + return true; + } else if (answer.toLowerCase() === "n") { + return false + } else { + throw new Error("Invalid answer to get a boolean value from") + } + } }