mirror of
https://git.bits.team/Bits/mod-manager.git
synced 2024-11-21 21:48:21 -05:00
Added an upgrade command
This commit is contained in:
parent
b801d59352
commit
39c1d72219
17
src/commands/upgrade_command.ts
Normal file
17
src/commands/upgrade_command.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import Subcommand from "./subcommand.js";
|
||||||
|
import {Command} from "commander";
|
||||||
|
import ModManager from "../mod-manager.js";
|
||||||
|
import Mods from "../mods/mods.js";
|
||||||
|
|
||||||
|
export default class UpgradeCommand implements Subcommand {
|
||||||
|
registerCommand(program: Command): void {
|
||||||
|
program.command("update")
|
||||||
|
.description("Checks for and updates mods that have a newer available version")
|
||||||
|
.action(() => {
|
||||||
|
ModManager.execute(async() => {
|
||||||
|
await Mods.update();
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
8
src/io/download_task.d.ts
vendored
8
src/io/download_task.d.ts
vendored
@ -1,8 +0,0 @@
|
|||||||
declare global {
|
|
||||||
type DownloadTask = {
|
|
||||||
fileName: string,
|
|
||||||
url: string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export {}
|
|
@ -6,14 +6,14 @@ import ModManager from "../mod-manager.js";
|
|||||||
|
|
||||||
|
|
||||||
export default class FileDownloader {
|
export default class FileDownloader {
|
||||||
static downloadMod(task: DownloadTask): void {
|
static downloadMod(version: Version): void {
|
||||||
https.get(task.url, res => {
|
https.get(version.url, res => {
|
||||||
const filePath = path.join(ModManager.FilePaths.MODS_FOLDER_PATH, task.fileName);
|
const filePath = path.join(ModManager.FilePaths.MODS_FOLDER_PATH, version.fileName);
|
||||||
const writeStream = createWriteStream(filePath);
|
const writeStream = createWriteStream(filePath);
|
||||||
res.pipe(writeStream);
|
res.pipe(writeStream);
|
||||||
writeStream.on("finish", () => writeStream.close());
|
writeStream.on("finish", () => writeStream.close());
|
||||||
writeStream.on('error', () => {
|
writeStream.on('error', () => {
|
||||||
throw new DownloadError(`Failed to download ${task.fileName} from ${task.url}`)
|
throw new DownloadError(`Failed to download ${version.fileName} from ${version.url}`)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ import {ListCommand} from "./commands/list_command.js";
|
|||||||
import UninstallCommand from "./commands/uninstall_command.js";
|
import UninstallCommand from "./commands/uninstall_command.js";
|
||||||
import EssentialCommand from "./commands/essential_command.js";
|
import EssentialCommand from "./commands/essential_command.js";
|
||||||
import {readFileSync, unlinkSync} from "fs";
|
import {readFileSync, unlinkSync} from "fs";
|
||||||
|
import UpgradeCommand from "./commands/upgrade_command.js";
|
||||||
|
|
||||||
|
|
||||||
export default class ModManager {
|
export default class ModManager {
|
||||||
@ -23,7 +24,8 @@ export default class ModManager {
|
|||||||
new InstallCommand(),
|
new InstallCommand(),
|
||||||
new ListCommand(),
|
new ListCommand(),
|
||||||
new UninstallCommand(),
|
new UninstallCommand(),
|
||||||
new EssentialCommand()
|
new EssentialCommand(),
|
||||||
|
new UpgradeCommand()
|
||||||
];
|
];
|
||||||
|
|
||||||
static FilePaths = class {
|
static FilePaths = class {
|
||||||
@ -48,6 +50,8 @@ export default class ModManager {
|
|||||||
command.registerCommand(this.program);
|
command.registerCommand(this.program);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.program.showSuggestionAfterError();
|
||||||
|
this.program.showHelpAfterError();
|
||||||
this.program.parse();
|
this.program.parse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
7
src/mods/mod.d.ts
vendored
7
src/mods/mod.d.ts
vendored
@ -8,6 +8,13 @@ declare global {
|
|||||||
source: string,
|
source: string,
|
||||||
essential: boolean
|
essential: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Version = {
|
||||||
|
id: string
|
||||||
|
fileName: string,
|
||||||
|
url: string
|
||||||
|
version_number: string
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {}
|
export {}
|
@ -6,7 +6,7 @@ import ModNotFoundError from "../errors/mod_not_found_error.js";
|
|||||||
import {readFileSync, unlinkSync, writeFileSync} from "fs";
|
import {readFileSync, unlinkSync, writeFileSync} from "fs";
|
||||||
import Util from "../util/util.js";
|
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";
|
||||||
|
|
||||||
export default class Mods {
|
export default class Mods {
|
||||||
private static readonly MOD_SOURCES: Array<ModSource> = [
|
private static readonly MOD_SOURCES: Array<ModSource> = [
|
||||||
@ -45,7 +45,9 @@ export default class Mods {
|
|||||||
if (!this.isModInstalled(id)) {
|
if (!this.isModInstalled(id)) {
|
||||||
spinner.updateText(`Installing ${projectName}...`)
|
spinner.updateText(`Installing ${projectName}...`)
|
||||||
try {
|
try {
|
||||||
const modObj: Mod = await source.install(id, essential);
|
const mcVersion = await MinecraftUtils.getCurrentMinecraftVersion();
|
||||||
|
const latestVersion = await source.getLatestVersion(id, mcVersion)
|
||||||
|
const modObj: Mod = await source.install(latestVersion, essential);
|
||||||
this.trackMod(modObj);
|
this.trackMod(modObj);
|
||||||
|
|
||||||
spinner.succeed(`Successfully installed ${projectName}`);
|
spinner.succeed(`Successfully installed ${projectName}`);
|
||||||
@ -90,25 +92,27 @@ export default class Mods {
|
|||||||
static uninstall(mod: string) {
|
static uninstall(mod: string) {
|
||||||
// Find mod to uninstall
|
// Find mod to uninstall
|
||||||
const spinner = new PrintUtils.Spinner(`Uninstalling ${mod}...`)
|
const spinner = new PrintUtils.Spinner(`Uninstalling ${mod}...`)
|
||||||
|
spinner.start();
|
||||||
|
|
||||||
const modToUninstall = this.findMod(mod);
|
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) {
|
||||||
let mods: Array<Mod> = this.getTrackedMods();
|
this.silentUninstall(modToUninstall);
|
||||||
|
|
||||||
// Remove mod from list and uninstall it
|
|
||||||
unlinkSync(path.join(ModManager.FilePaths.MOD_FILE_PATH, modToUninstall.fileName));
|
|
||||||
mods = mods.filter(item => !Mods.areModsEqual(item, modToUninstall));
|
|
||||||
this.writeFile(mods);
|
|
||||||
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.`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static silentUninstall(mod: Mod) {
|
||||||
|
let mods: Array<Mod> = this.getTrackedMods();
|
||||||
|
|
||||||
|
// Remove mod from list and uninstall it
|
||||||
|
unlinkSync(path.join(ModManager.FilePaths.MODS_FOLDER_PATH, mod.fileName));
|
||||||
|
mods = mods.filter(item => !Mods.areModsEqual(item, mod));
|
||||||
|
this.writeFile(mods);
|
||||||
|
}
|
||||||
|
|
||||||
static areModsEqual(mod1: Mod, mod2: Mod): boolean {
|
static areModsEqual(mod1: Mod, mod2: Mod): boolean {
|
||||||
return mod1.id === mod2.id;
|
return mod1.id === mod2.id;
|
||||||
}
|
}
|
||||||
@ -153,4 +157,54 @@ export default class Mods {
|
|||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async update() {
|
||||||
|
const trackedMods = this.getTrackedMods();
|
||||||
|
|
||||||
|
if (Util.isArrayEmpty(trackedMods)) {
|
||||||
|
PrintUtils.error("There are no mods currently installed. Try `mod-manager install -h` to learn more!")
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mcVersion = await MinecraftUtils.getCurrentMinecraftVersion();
|
||||||
|
|
||||||
|
// For every tracked mod
|
||||||
|
for (let mod of trackedMods) {
|
||||||
|
const spinner = new PrintUtils.Spinner(`Checking for newer version of ${mod.name}`)
|
||||||
|
spinner.start();
|
||||||
|
|
||||||
|
// Get the latest version
|
||||||
|
const source = this.getSourceFromName(mod.source);
|
||||||
|
let latestVersion: Version | undefined = undefined;
|
||||||
|
try {
|
||||||
|
latestVersion = await source.getLatestVersion(mod.id, mcVersion);
|
||||||
|
|
||||||
|
// If the latest version has a different version number, it must be newer, install it.
|
||||||
|
if (latestVersion.version_number != mod.version) {
|
||||||
|
spinner.updateText(`Newer version for ${mod.name} found. Installing...`)
|
||||||
|
this.silentUninstall(mod);
|
||||||
|
|
||||||
|
const newMod = await source.install(latestVersion, mod.essential);
|
||||||
|
this.trackMod(newMod);
|
||||||
|
|
||||||
|
spinner.succeed(`Successfully updated ${newMod.name}`)
|
||||||
|
|
||||||
|
// Else, the latest version is already installed, do nothing.
|
||||||
|
} else {
|
||||||
|
throw new ModNotFoundError("There is no newer version available.");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
spinner.error(`${mod.name} already has the latest version installed!`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static getSourceFromName(name: string): ModSource {
|
||||||
|
const source = this.MOD_SOURCES.filter(src => src.getSourceName() === name)[0];
|
||||||
|
if (source == undefined) {
|
||||||
|
throw new Error(`There is no source registered with the name ${name}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return source
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,9 +1,11 @@
|
|||||||
export default interface ModSource {
|
export default interface ModSource {
|
||||||
search(query: string): Promise<string>;
|
search(query: string): Promise<string>;
|
||||||
|
|
||||||
install(id: string, essential: boolean): Promise<Mod>;
|
install(version: Version, essential: boolean): Promise<Mod>;
|
||||||
|
|
||||||
getSourceName(): string;
|
getSourceName(): string;
|
||||||
|
|
||||||
getProjectName(id: string): Promise<string>;
|
getProjectName(id: string): Promise<string>;
|
||||||
|
|
||||||
|
getLatestVersion(id: string, mcVersion: string): Promise<Version>;
|
||||||
}
|
}
|
@ -11,7 +11,7 @@ import DownloadError from "../../errors/download_error.js";
|
|||||||
export default class ModrinthSource implements ModSource {
|
export default class ModrinthSource implements ModSource {
|
||||||
private static readonly BASE_URL: string = "https://api.modrinth.com/v2";
|
private static readonly BASE_URL: string = "https://api.modrinth.com/v2";
|
||||||
private static readonly SEARCH_URL: string = ModrinthSource.BASE_URL + "/search";
|
private static readonly SEARCH_URL: string = ModrinthSource.BASE_URL + "/search";
|
||||||
private static readonly INSTALL_URL: string = ModrinthSource.BASE_URL + "/project/%s/version";
|
private static readonly LIST_VERSIONS_URL: string = ModrinthSource.BASE_URL + "/project/%s/version";
|
||||||
private static readonly PROJECT_URL: string = ModrinthSource.BASE_URL + "/project/%s";
|
private static readonly PROJECT_URL: string = ModrinthSource.BASE_URL + "/project/%s";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -87,87 +87,26 @@ export default class ModrinthSource implements ModSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Installs the mod with the provided mod id
|
* Installs the provided Version
|
||||||
* Example shape of data returned by query:
|
* @param version the Version to install
|
||||||
* [
|
|
||||||
* {
|
|
||||||
* "id": "ZRR9yqHD",
|
|
||||||
* "project_id": "gvQqBUqZ",
|
|
||||||
* "author_id": "uhPSqlnd",
|
|
||||||
* "featured": false,
|
|
||||||
* "name": "Lithium 0.8.3",
|
|
||||||
* "version_number": "mc1.19.1-0.8.3",
|
|
||||||
* "changelog": "Lithium 0.8.3 is the second release for 1.19.1! It includes a bugfix too!\n\n## Fixes\n- fix: update chunk serialization patch to new mappings\n\nYou can donate on patreon: https://www.patreon.com/2No2Name\n",
|
|
||||||
* "changelog_url": null,
|
|
||||||
* "date_published": "2022-07-29T22:18:09.072973Z",
|
|
||||||
* "downloads": 3592,
|
|
||||||
* "version_type": "release",
|
|
||||||
* "files": [
|
|
||||||
* {
|
|
||||||
* "hashes": {
|
|
||||||
* "sha1": "9ef9f10f62d4c19b736fe493f2a11d737fbe3d7c",
|
|
||||||
* "sha512": "a3b623b4c14f6ba46d1486ffb3d1ba3174e3317b419b2ddfdf7bb572244e706d2e0a37bdce169c94455bec00fd107530ba78d7e611162a632cc6950e6a625433"
|
|
||||||
* },
|
|
||||||
* "url": "https://cdn.modrinth.com/data/gvQqBUqZ/versions/mc1.19.1-0.8.3/lithium-fabric-mc1.19.1-0.8.3.jar",
|
|
||||||
* "filename": "lithium-fabric-mc1.19.1-0.8.3.jar",
|
|
||||||
* "primary": true,
|
|
||||||
* "size": 476619
|
|
||||||
* }
|
|
||||||
* ],
|
|
||||||
* "dependencies": [],
|
|
||||||
* "game_versions": [
|
|
||||||
* "1.19.1"
|
|
||||||
* ],
|
|
||||||
* "loaders": [
|
|
||||||
* "fabric"
|
|
||||||
* ]
|
|
||||||
* }
|
|
||||||
* ]
|
|
||||||
* @param id the id of the mod
|
|
||||||
* @param essential whether this mod is essential or not
|
* @param essential whether this mod is essential or not
|
||||||
* @throws DownloadError if an error occurs when downloading
|
* @throws DownloadError if an error occurs when downloading
|
||||||
* @throws ModNotFoundError if there are no versions available for the current Minecraft Version
|
|
||||||
*/
|
*/
|
||||||
async install(id: string, essential: boolean): Promise<Mod> {
|
async install(version: Version, essential: boolean): Promise<Mod> {
|
||||||
const mcVersion = await MinecraftUtils.getCurrentMinecraftVersion();
|
|
||||||
|
|
||||||
const params = {
|
|
||||||
loaders: '["fabric"]',
|
|
||||||
game_versions: format('["%s"]', mcVersion)
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await axios.get(format(ModrinthSource.INSTALL_URL, id), {params});
|
|
||||||
const results = await response.data;
|
|
||||||
|
|
||||||
if (Util.isArrayEmpty(results)) {
|
|
||||||
throw new ModNotFoundError(`Mod with id ${id} has no available versions on ${this.getSourceName()} for Minecraft version ${mcVersion}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const latestFile = results[0].files[0];
|
|
||||||
|
|
||||||
const fileName = latestFile.filename;
|
|
||||||
const url = latestFile.url;
|
|
||||||
const modVersion = results[0].version_number;
|
|
||||||
|
|
||||||
const task: DownloadTask = {
|
|
||||||
fileName: fileName,
|
|
||||||
url: url
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
FileDownloader.downloadMod(task)
|
FileDownloader.downloadMod(version)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: await this.getProjectName(id),
|
name: await this.getProjectName(version.id),
|
||||||
id: id,
|
id: version.id,
|
||||||
fileName: fileName,
|
fileName: version.fileName,
|
||||||
version: modVersion,
|
version: version.version_number,
|
||||||
source: this.getSourceName(),
|
source: this.getSourceName(),
|
||||||
essential: essential
|
essential: essential
|
||||||
};
|
};
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new DownloadError(`An error occurred downloading mod with id ${id} from ${this.getSourceName()}`)
|
throw new DownloadError(`An error occurred downloading mod with id ${version.id} from ${this.getSourceName()}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -244,4 +183,67 @@ export default class ModrinthSource implements ModSource {
|
|||||||
return await response.data.title;
|
return await response.data.title;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the latest version of the mod
|
||||||
|
* Example shape of data returned by query:
|
||||||
|
* [
|
||||||
|
* {
|
||||||
|
* "id": "ZRR9yqHD",
|
||||||
|
* "project_id": "gvQqBUqZ",
|
||||||
|
* "author_id": "uhPSqlnd",
|
||||||
|
* "featured": false,
|
||||||
|
* "name": "Lithium 0.8.3",
|
||||||
|
* "version_number": "mc1.19.1-0.8.3",
|
||||||
|
* "changelog": "Lithium 0.8.3 is the second release for 1.19.1! It includes a bugfix too!\n\n## Fixes\n- fix: update chunk serialization patch to new mappings\n\nYou can donate on patreon: https://www.patreon.com/2No2Name\n",
|
||||||
|
* "changelog_url": null,
|
||||||
|
* "date_published": "2022-07-29T22:18:09.072973Z",
|
||||||
|
* "downloads": 3592,
|
||||||
|
* "version_type": "release",
|
||||||
|
* "files": [
|
||||||
|
* {
|
||||||
|
* "hashes": {
|
||||||
|
* "sha1": "9ef9f10f62d4c19b736fe493f2a11d737fbe3d7c",
|
||||||
|
* "sha512": "a3b623b4c14f6ba46d1486ffb3d1ba3174e3317b419b2ddfdf7bb572244e706d2e0a37bdce169c94455bec00fd107530ba78d7e611162a632cc6950e6a625433"
|
||||||
|
* },
|
||||||
|
* "url": "https://cdn.modrinth.com/data/gvQqBUqZ/versions/mc1.19.1-0.8.3/lithium-fabric-mc1.19.1-0.8.3.jar",
|
||||||
|
* "filename": "lithium-fabric-mc1.19.1-0.8.3.jar",
|
||||||
|
* "primary": true,
|
||||||
|
* "size": 476619
|
||||||
|
* }
|
||||||
|
* ],
|
||||||
|
* "dependencies": [],
|
||||||
|
* "game_versions": [
|
||||||
|
* "1.19.1"
|
||||||
|
* ],
|
||||||
|
* "loaders": [
|
||||||
|
* "fabric"
|
||||||
|
* ]
|
||||||
|
* }
|
||||||
|
* ]
|
||||||
|
* @param id
|
||||||
|
* @param mcVersion
|
||||||
|
* @throws ModNotFoundError if there are no versions available for the current Minecraft Version
|
||||||
|
*/
|
||||||
|
async getLatestVersion(id: string, mcVersion: string): Promise<Version> {
|
||||||
|
const params = {
|
||||||
|
loaders: '["fabric"]',
|
||||||
|
game_versions: format('["%s"]', mcVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await axios.get(format(ModrinthSource.LIST_VERSIONS_URL, id), {params});
|
||||||
|
const results = await response.data;
|
||||||
|
|
||||||
|
if (Util.isArrayEmpty(results)) {
|
||||||
|
throw new ModNotFoundError(`Mod with id ${id} has no available versions on ${this.getSourceName()} for Minecraft version ${mcVersion}`);
|
||||||
|
}
|
||||||
|
const latestVersion = results[0];
|
||||||
|
const latestFile = results[0].files[0];
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: latestVersion.project_id,
|
||||||
|
version_number: latestVersion.version_number,
|
||||||
|
fileName: latestFile.filename,
|
||||||
|
url: latestFile.url
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user