Require confirmation when uninstalling mods that are depended on

This commit is contained in:
Kallum Jones 2022-08-14 14:33:03 +01:00
parent 76f6c2c475
commit f7be866698
No known key found for this signature in database
GPG Key ID: D7F4589C4D7F81A9
6 changed files with 86 additions and 22 deletions

13
package-lock.json generated
View File

@ -19,7 +19,8 @@
"ora": "^6.1.2", "ora": "^6.1.2",
"pino": "^8.3.1", "pino": "^8.3.1",
"string-format": "^2.0.0", "string-format": "^2.0.0",
"string-similarity-js": "^2.1.4" "string-similarity-js": "^2.1.4",
"typescript-string-operations": "^1.4.1"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^18.6.3", "@types/node": "^18.6.3",
@ -835,6 +836,11 @@
"node": ">=4.2.0" "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": { "node_modules/util-deprecate": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@ -1416,6 +1422,11 @@
"integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==",
"dev": true "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": { "util-deprecate": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",

View File

@ -21,7 +21,8 @@
"ora": "^6.1.2", "ora": "^6.1.2",
"pino": "^8.3.1", "pino": "^8.3.1",
"string-format": "^2.0.0", "string-format": "^2.0.0",
"string-similarity-js": "^2.1.4" "string-similarity-js": "^2.1.4",
"typescript-string-operations": "^1.4.1"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^18.6.3", "@types/node": "^18.6.3",

View File

@ -9,9 +9,9 @@ export default class UninstallCommand implements Subcommand {
.description("Uninstalls the provided mods") .description("Uninstalls the provided mods")
.argument("<mods...>", "The mods to uninstall (as names or ids)") .argument("<mods...>", "The mods to uninstall (as names or ids)")
.action((mods) => { .action((mods) => {
ModManager.execute(() => { ModManager.execute(async () => {
for (let mod of mods) { for (let mod of mods) {
Mods.uninstall(mod); await Mods.uninstall(mod);
} }
}) })
}) })

View File

@ -7,6 +7,9 @@ import Util from "../util/util.js";
import ModManager from "../mod-manager.js"; import ModManager from "../mod-manager.js";
import MinecraftUtils from "../util/minecraft_utils.js"; import MinecraftUtils from "../util/minecraft_utils.js";
import MigrateError from "../errors/migrate_error.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 { export default class Mods {
private static readonly MOD_SOURCES: Array<ModSource> = []; private static readonly MOD_SOURCES: Array<ModSource> = [];
@ -97,26 +100,60 @@ export default class Mods {
return !Util.isArrayEmpty(modsWithId) 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 // Find mod to uninstall
const modToUninstall = this.findMod(mod);
const spinner = new PrintUtils.Spinner(`Uninstalling ${mod}...`) const spinner = new PrintUtils.Spinner(`Uninstalling ${mod}...`)
spinner.start(); 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) { if (modToUninstall != undefined) {
this.silentUninstall(modToUninstall);
for (let dependency of modToUninstall.dependencies) { // Ensure the user wants to delete this mod
if (!this.isDependedOn(dependency)) { let del = true;
const dependencyMod = this.findMod(dependency); if (Mods.isDependedOn(modToUninstall.id)) {
if (dependencyMod != undefined) { spinner.pause();
this.silentUninstall(dependencyMod)
} 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<string | boolean> {
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 { } else {
spinner.error(`${mod} was not found.`) spinner.error(`${mod} was not found.`)
} }
@ -366,13 +403,14 @@ export default class Mods {
} }
private static isDependedOn(dependency: string) { private static isDependedOn(dependency: string) {
const trackedMods = this.getTrackedMods(); return Mods.getDependantMods(dependency).length != 0
}
for (let trackedMod of trackedMods) { private static modListToSting(dependantMods: TrackedMod[]) {
if (trackedMod.dependencies.includes(dependency)) { const builder = new StringBuilder();
return true; for (let dependantMod of dependantMods) {
} builder.Append(dependantMod.name)
} }
return false; return builder.ToString();
} }
} }

View File

@ -24,6 +24,10 @@ export default class PrintUtils {
this.spinner.stopAndPersist(); this.spinner.stopAndPersist();
} }
public pause() {
this.spinner.stop();
}
public error(print: string | Error) { public error(print: string | Error) {
if (print instanceof Error) { if (print instanceof Error) {
this.spinner.fail(print.message) this.spinner.fail(print.message)

View File

@ -21,4 +21,14 @@ export default class Util {
compare = compare.toLowerCase(); compare = compare.toLowerCase();
return stringSimilarity(master, compare) >= this.SIMILARITY_THRESHOLD; 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")
}
}
} }