From 5a1c6f47e4e1f6fa564619abef9d561bf01af394 Mon Sep 17 00:00:00 2001 From: Moritz Ruth Date: Thu, 18 Aug 2022 13:28:32 +0200 Subject: [PATCH] Implement modrinth open and activate --- README.md | 77 +----- package.json | 4 +- pnpm-lock.yaml | 29 +++ src/commands/modrinth/activate.ts | 23 +- src/commands/modrinth/index.ts | 8 +- src/commands/modrinth/open.ts | 24 ++ src/modrinth/index.ts | 24 +- src/utils/output.ts | 3 +- src_old/commands/modrinth/index.ts | 396 ----------------------------- src_old/modrinth/utils.ts | 47 ---- 10 files changed, 106 insertions(+), 529 deletions(-) create mode 100644 src/commands/modrinth/open.ts delete mode 100644 src_old/commands/modrinth/index.ts delete mode 100644 src_old/modrinth/utils.ts diff --git a/README.md b/README.md index 0a4e4a9..5916abe 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,7 @@ > A CLI tool for creating and maintaining Minecraft modpacks using the Fabric loader. 🎉 Features: -- Access [Modrinth](https://modrinth.com/) - - Search - - Add mods by ID or slug - - View available versions - - View dependencies +- Add mods from [Modrinth](https://modrinth.com/) - Check for updates and view changelogs before applying them - Export the pack to the [Modrinth format (`.mrpack`)](https://docs.modrinth.com/docs/modpacks/format_definition/) - Export the pack to the [`packwiz`](https://packwiz.infra.link/) format @@ -31,76 +27,9 @@ Run any command with the `-h` flag to see the available options. A new pack can be initialized using `horizr init `. -## Examples - -- Activate the latest (compatible) version of [Charm](https://modrinth.com/mod/charm) -```sh -$ horizr modrinth mod activate charm - -# or short: -$ horizr mr mod a charm -``` - -- Activate `v4.1.1` of [Charm](https://modrinth.com/mod/charm) -```sh -$ horizr modrinth mod versions charm - -# `BT9G1Jjs` is the version code you are looking for. -# This output will be colored in your console. -BT9G1Jjs 4.2.0+1.18.2 (↓ 137) -featured - -Name: [1.18.2] 4.2.0 -Channel: release -Minecraft versions: 1.18.2 - -Publication: last week - -https://modrinth.com/mod/pOQTcQmj/version/BT9G1Jjs - -# … more versions omitted for brevity - -$ horizr modrinth version activate BT9G1Jjs - -Charm (4.2.0+1.18.2) was successfully activated. - - -Dependencies -◉ Fabric API (P7dR8mSH): any version - -``` - -- Check for updates -```sh -$ horizr update -# Because Sodium's version string is not a valid SemVer, -# the publication date will instead be used for comparison. -❯ Sodium has no valid semantic version: mc1.18.2-0.4.1. The -publication date will instead be used. - -Available updates -- charm Charm: 4.1.0+1.18.2 → 4.2.0+1.18.2 -``` - -- Apply an update -```sh -$ horizr update charm - -Changelog for 4.2.0+1.18.2 - -* Added ebony wood. -* Fixed issue with Totems not always spawning or being -carried away by mobs. -# … omitted for brevity - -Apply the update? [Y/n] y - -Successfully updated Charm to 4.2.0+1.18.2. -``` - ## Contributing -I developed this tool primarily for my own packs, soooo… the code quality is not bad, but it’s not good either. +I developed this tool primarily for my own packs, that’s why its missing some features I didn’t absolutely need. -If you want a feature added, feel free to [create an issue](https://github.com/horizr/cli/issues/new). +Nevertheless, if you want a feature added, feel free to [create an issue](https://github.com/horizr/cli/issues/new). A pull request would be even better. diff --git a/package.json b/package.json index a488553..33a8538 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "repository": "https://github.com/horizr/cli", "scripts": { "start": "tsx src/main.ts", - "build": "del dist && tsc" + "build": "del dist && tsc", + "check": "tsc --noEmit" }, "bin": { "horizr": "bin/horizr.js" @@ -35,6 +36,7 @@ "lodash-es": "^4.17.21", "loud-rejection": "^2.2.0", "nanoid": "^4.0.0", + "open": "^8.4.0", "ora": "^6.1.2", "p-event": "^5.0.1", "p-limit": "^4.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 496b376..789ba4a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -27,6 +27,7 @@ specifiers: lodash-es: ^4.17.21 loud-rejection: ^2.2.0 nanoid: ^4.0.0 + open: ^8.4.0 ora: ^6.1.2 p-event: ^5.0.1 p-limit: ^4.0.0 @@ -59,6 +60,7 @@ dependencies: lodash-es: 4.17.21 loud-rejection: 2.2.0 nanoid: 4.0.0 + open: 8.4.0 ora: 6.1.2 p-event: 5.0.1 p-limit: 4.0.0 @@ -510,6 +512,11 @@ packages: engines: {node: '>=10'} dev: false + /define-lazy-prop/2.0.0: + resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} + engines: {node: '>=8'} + dev: false + /del-cli/5.0.0: resolution: {integrity: sha512-rENFhUaYcjoMODwFhhlON+ogN7DoG+4+GFN+bsA1XeDt4w2OKQnQadFP1thHSAlK9FAtl88qgP66wOV+eFZZiQ==} engines: {node: '>=14.16'} @@ -1023,6 +1030,12 @@ packages: has: 1.0.3 dev: true + /is-docker/2.2.1: + resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} + engines: {node: '>=8'} + hasBin: true + dev: false + /is-extglob/2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -1067,6 +1080,13 @@ packages: engines: {node: '>=12'} dev: false + /is-wsl/2.2.0: + resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} + engines: {node: '>=8'} + dependencies: + is-docker: 2.2.1 + dev: false + /js-tokens/4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} dev: true @@ -1303,6 +1323,15 @@ packages: mimic-fn: 2.1.0 dev: false + /open/8.4.0: + resolution: {integrity: sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==} + engines: {node: '>=12'} + dependencies: + define-lazy-prop: 2.0.0 + is-docker: 2.2.1 + is-wsl: 2.2.0 + dev: false + /ora/6.1.2: resolution: {integrity: sha512-EJQ3NiP5Xo94wJXIzAyOtSb0QEIAUu7m8t6UZ9krbz0vAJqr92JpcK/lEXg91q6B9pEGqrykkd2EQplnifDSBw==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} diff --git a/src/commands/modrinth/activate.ts b/src/commands/modrinth/activate.ts index 6b7fe59..cf65c64 100644 --- a/src/commands/modrinth/activate.ts +++ b/src/commands/modrinth/activate.ts @@ -4,7 +4,7 @@ import { findMetaFileForModrinthMod, getMetaFileContentVersionForModrinth, getSideOfModrinthMod, - isModrinthVersionCompatible, + isModrinthVersionCompatible, resolveFullRelation, resolveModrinthCode, sortModrinthVersionsByPreference } from "../../modrinth/index.js" @@ -15,6 +15,7 @@ import kleur from "kleur" import { META_FILE_EXTENSION, metaFileContentSchema, writeJsonFile } from "../../files.js" import fs from "fs-extra" import enquirer from "enquirer" +import { orEmptyString } from "../../utils/strings.js" export const activateCommand = new Command("activate") .argument("") @@ -27,7 +28,7 @@ export const activateCommand = new Command("activate") const modrinthMod = resolvedCode.modrinthMod let modrinthVersion = resolvedCode.modrinthVersion - const existingMetaFile = findMetaFileForModrinthMod(pack.metaFiles, modrinthMod) + const existingMetaFile = findMetaFileForModrinthMod(pack.metaFiles, modrinthMod.id) if (existingMetaFile !== null) { output.println(`The mod is already active: ${kleur.yellow(existingMetaFile.relativePath.toString())} ${kleur.blue(existingMetaFile.content.version.name)}`) @@ -77,6 +78,22 @@ export const activateCommand = new Command("activate") }) await pack.registerCreatedSourceFile(relativePath) - output.println(kleur.green(`Successfully wrote ${kleur.yellow(relativePath.toString())}`)) + + const loader = output.startLoading("Checking dependencies") + + for (const relation of modrinthVersion.relations) { + if (relation.type === "hard_dependency") { + const { modrinthMod, modrinthVersion } = await resolveFullRelation(relation) + + const metaFile = await findMetaFileForModrinthMod(pack.metaFiles, modrinthMod.id) + if (metaFile === null) { + const versionString = orEmptyString(modrinthVersion, v => ` ${kleur.blue(v.versionString)}`) + const idString = kleur.gray(modrinthMod.slug + orEmptyString(modrinthVersion, v => `@${v.versionString}`)) + output.warn(`Unmet dependency: ${kleur.yellow(modrinthMod.title)}${versionString} ${idString}`) + } + } + } + + loader.stop() }) diff --git a/src/commands/modrinth/index.ts b/src/commands/modrinth/index.ts index 723c1dc..2a31284 100644 --- a/src/commands/modrinth/index.ts +++ b/src/commands/modrinth/index.ts @@ -2,15 +2,17 @@ import { Command } from "commander" import { activateCommand } from "./activate.js" import dedent from "dedent" import kleur from "kleur" +import { openCommand } from "./open.js" export const modrinthCommand = new Command("modrinth") .alias("mr") .addCommand(activateCommand) - .addHelpText("after", dedent` - ${kleur.yellow("")} may be one of the following: + .addCommand(openCommand) + .addHelpText("afterAll", dedent` + \n${kleur.yellow("")} may be one of the following: - URL or slug of a Modrinth mod (${kleur.yellow("https://modrinth.com/mod/sodium")} or ${kleur.yellow("sodium")}) - URL of a Modrinth mod version (${kleur.yellow("https://modrinth.com/mod/sodium/version/mc1.19-0.4.2")}) - - slug of a Modrinth mod and a version with a ${kleur.yellow("@")} in between (${kleur.yellow("sodium:mc1.19-0.4.2")}) + - slug of a Modrinth mod and a version with a ${kleur.yellow("@")} in between (${kleur.yellow("sodium@mc1.19-0.4.2")}) - Modrinth project ID (${kleur.yellow("AANobbMI")} for Sodium) - Modrinth version ID, prefixed with ${kleur.yellow("@")} (${kleur.yellow("@Yp8wLY1P")} for Sodium mc1.19-0.4.2) `) diff --git a/src/commands/modrinth/open.ts b/src/commands/modrinth/open.ts new file mode 100644 index 0000000..136b372 --- /dev/null +++ b/src/commands/modrinth/open.ts @@ -0,0 +1,24 @@ +import { Command } from "commander" +import { usePack } from "../../pack.js" +import { output } from "../../utils/output.js" +import kleur from "kleur" +import open from "open" + +export const openCommand = new Command("open") + .argument("") + .action(async pathString => { + const pack = await usePack() + const metaFile = pack.getMetaFileFromInput(pathString) + + if (metaFile.content.source?.type === "modrinth") { + const { modId } = metaFile.content.source + const url = `https://modrinth.com/mod/${encodeURIComponent(modId)}` + + try { + await open(url, { wait: false }) + output.printlnWrapping(kleur.green(`Opened ${kleur.yellow(url)} in your default browser.`)) + } catch (e: unknown) { + output.fail(`Could not open ${kleur.yellow(url)} in a browser.`) + } + } else output.failAndExit(`${kleur.yellow(metaFile.relativePath.toString())} is not a Modrinth mod.`) + }) diff --git a/src/modrinth/index.ts b/src/modrinth/index.ts index dfccbfa..d92941b 100644 --- a/src/modrinth/index.ts +++ b/src/modrinth/index.ts @@ -1,5 +1,5 @@ import { IterableElement } from "type-fest" -import { modrinthApi, ModrinthMod, ModrinthVersion, ModrinthVersionFile } from "./api.js" +import { modrinthApi, ModrinthMod, ModrinthVersion, ModrinthVersionFile, ModrinthVersionRelation } from "./api.js" import { sortBy } from "lodash-es" import { MetaFile, Pack, releaseChannelOrder } from "../pack.js" import { MetaFileContentVersion } from "../files.js" @@ -106,5 +106,23 @@ export async function resolveModrinthCode(code: string): Promise<{ modrinthMod: return output.failAndExit(`Invalid ${kleur.yellow("")}: ${kleur.yellow(code)}`) } -export const findMetaFileForModrinthMod = (metaFiles: MetaFile[], modrinthMod: ModrinthMod) => - metaFiles.find(metaFile => metaFile.content.source?.type === "modrinth" && metaFile.content.source.modId === modrinthMod.id) ?? null +export const findMetaFileForModrinthMod = (metaFiles: MetaFile[], modrinthModId: string) => + metaFiles.find(metaFile => metaFile.content.source?.type === "modrinth" && metaFile.content.source.modId === modrinthModId) ?? null + +export async function resolveFullRelation(relation: ModrinthVersionRelation) { + if (relation.projectId === null) { + const modrinthVersion = (await modrinthApi.getVersion(relation.versionId!))! + + return { + modrinthVersion, + modrinthMod: (await modrinthApi.getMod(modrinthVersion.projectId))! + } + } else { + const modrinthMod = (await modrinthApi.getMod(relation.projectId))! + + return { + modrinthMod, + modrinthVersion: null + } + } +} diff --git a/src/utils/output.ts b/src/utils/output.ts index 371cac5..c4c7c73 100644 --- a/src/utils/output.ts +++ b/src/utils/output.ts @@ -140,14 +140,13 @@ export const output = { } }, println(text: string) { - this.print(text + "\n") + this.print(text + "\n\n") }, printlnWrapping(text: string) { this.println(wrapAnsi(text, process.stdout.columns)) }, warn(text: string) { this.printlnWrapping(`${kleur.yellow(figures.pointer)} ${text}`) - this.println("") }, fail(text: string) { last(loadersStack)?.fail() diff --git a/src_old/commands/modrinth/index.ts b/src_old/commands/modrinth/index.ts deleted file mode 100644 index ba244be..0000000 --- a/src_old/commands/modrinth/index.ts +++ /dev/null @@ -1,396 +0,0 @@ -import { Command } from "commander" -import { take } from "lodash-es" -import { usePack } from "../../pack.js" -import kleur from "kleur" -import { optionParsePositiveInteger, truncateWithEllipsis, zipDirectory } from "../../utils.js" -import { default as wrapAnsi } from "wrap-ansi" -import figures from "figures" -import { - modrinthApi, - ModrinthMod, - ModrinthVersion, - ModrinthVersionRelation, -} from "../../modrinth/api.js" -import dedent from "dedent" -import ago from "s-ago" -import semver from "semver" -import { output } from "../../../src/utils/output.js" -import fs from "fs-extra" -import { addModrinthMod, findModForModrinthMod, getModFileDataForModrinthVersion, isModrinthVersionCompatible, sortModrinthVersionsByPreference } from "../../modrinth/utils.js" -import { walk } from "@root/walk" - -const modrinthCommand = new Command("modrinth") - .alias("mr") - -modrinthCommand.command("search ") - .description("Search for mods.") - .option("-l, --limit ", "Limit the number of results", optionParsePositiveInteger, 8) - .option("-s, --skip ", "Skip results", optionParsePositiveInteger, 0) - .action(async (query, options) => { - const pack = await usePack() - const loader = output.startLoading(`Searching for ${kleur.yellow(query)}`) - const { results } = await modrinthApi.searchMods(pack.horizrFile.versions.minecraft, query, options) - loader.stop() - - output.println( - results.map(result => - `${kleur.blue(result.id)} ${kleur.bold(truncateWithEllipsis(result.title, 30))} ${kleur.gray(`(↓ ${result.downloadsCount})`)}\n` + - wrapAnsi(result.description, process.stdout.columns) - ) - .join("\n\n") - ) - }) - -const colorBySideCompatibility: Record = { - optional: kleur.blue, - required: kleur.green, - unsupported: kleur.red -} - -const modrinthModCommand = modrinthCommand.command("mod") - -modrinthModCommand.command("info ") - .description("Show information about the mod.") - .action(async id => { - const loader = output.startLoading("Fetching mod information") - const modrinthMod = await modrinthApi.getMod(id) - if (modrinthMod === null) return loader.failAndExit("not found") - - loader.stop() - const existingMod = await findModForModrinthMod(modrinthMod) - - output.println(dedent` - ${kleur.bold(modrinthMod.title)} ${kleur.gray(`(↓ ${modrinthMod.downloadsCount})`)} - ${wrapAnsi(modrinthMod.description, process.stdout.columns)} - - Client Server - ${colorBySideCompatibility[modrinthMod.clientSide](modrinthMod.clientSide.padEnd(12, " "))} ${colorBySideCompatibility[modrinthMod.serverSide](modrinthMod.serverSide)} - - License: ${kleur.yellow(modrinthMod.licenseCode.toUpperCase())} - Last update: ${kleur.yellow(ago(modrinthMod.updateDate))}\ - ${existingMod === null ? "" : kleur.green("\n\nThis mod is in the pack.")} - - https://modrinth.com/mod/${modrinthMod.slug} - `) - }) - -modrinthModCommand.command("versions ") - .description("Show a list of compatible versions of the mod.") - .option("-l, --limit ", "Limit the number of versions displayed.", optionParsePositiveInteger, 3) - .action(async (id, options) => { - const pack = await usePack() - - const loader = output.startLoading("Fetching mod information") - const modrinthMod = await modrinthApi.getMod(id) - if (modrinthMod === null) return loader.failAndExit("not found") - else loader.stop() - - const existingMod = await findModForModrinthMod(modrinthMod) - const modrinthVersions = await output.withLoading(modrinthApi.listVersions(id, pack.horizrFile.versions.minecraft), "Fetching versions") - - if (modrinthVersions.length === 0) { - const message = - `There are no versions compatible with the pack (Fabric ${kleur.yellow(pack.horizrFile.versions.fabric)}, Minecraft ${kleur.yellow(pack.horizrFile.versions.minecraft)}).` - - output.println(kleur.red(message)) - } else { - const versions = take(sortModrinthVersionsByPreference(modrinthVersions), options.limit) - .map(modrinthVersion => { - const state = existingMod !== null && existingMod.modFile.source.versionId === modrinthVersion.id - ? kleur.bgGreen().black(" active ") + "\n\n" - : modrinthVersion.isFeatured - ? kleur.green("featured") + "\n\n" - : "" - - return dedent` - ${kleur.blue(modrinthVersion.id)} ${kleur.bold(modrinthVersion.versionString)} ${kleur.gray(`(↓ ${modrinthVersion.downloadsCount})`)} - ${state}\ - ${modrinthVersion.name !== modrinthVersion.versionString ? `Name: ${kleur.yellow(modrinthVersion.name)}\n` : ""}\ - Channel: ${kleur.yellow(modrinthVersion.releaseChannel)} - Minecraft versions: ${kleur.yellow(modrinthVersion.supportedMinecraftVersions.join(", "))} - - Publication: ${kleur.yellow(ago(modrinthVersion.publicationDate))} - - https://modrinth.com/mod/${modrinthVersion.projectId}/version/${modrinthVersion.id} - ` - }) - .join("\n\n") - - output.println(versions) - } - }) - -modrinthModCommand.command("activate ") - .description("Activate the recommended version of the mod.") - .alias("a") - .option("-f, --force", "Replace a different version already active.") - .action(async (id, options) => { - const pack = await usePack() - - const loader = output.startLoading("Fetching mod information") - const modrinthMod = await modrinthApi.getMod(id) - if (modrinthMod === null) return loader.failAndExit("not found") - else loader.stop() - - const modrinthVersions = await output.withLoading(modrinthApi.listVersions(id, pack.horizrFile.versions.minecraft), "Fetching versions") - if (modrinthVersions.length === 0) return output.failAndExit("There is no compatible version of this mod.") - - const sortedModrinthVersions = sortModrinthVersionsByPreference(modrinthVersions) - const modrinthVersion = sortedModrinthVersions[0] - - await handleActivate(modrinthMod, modrinthVersion, options.force) - }) - -const colorByRelationType: Record = { - "embedded_dependency": kleur.green, - "soft_dependency": kleur.magenta, - "hard_dependency": kleur.yellow, - "incompatible": kleur.red -} - -const nullVersionStringByRelationType: Record = { - "embedded_dependency": "unknown version", - "soft_dependency": "any version", - "hard_dependency": "any version", - "incompatible": "all versions" -} - -const versionStateStrings = { - "active": kleur.bgGreen().black(" active "), - "compatible": kleur.blue("compatible"), - "incompatible": kleur.red("incompatible"), - "newer_version": `${kleur.bgYellow().black(" older version active ")} ${figures.arrowRight} EXISTING_VERSION`, - "older_version": `${kleur.bgYellow().black(" newer version active ")} ${figures.arrowRight} EXISTING_VERSION`, - "different_version": `${kleur.bgYellow().black(" different version active ")} ${figures.arrowRight} EXISTING_VERSION` -} - -async function getRelationsListLines(relations: ModrinthVersionRelation[]) { - return await Promise.all(relations.map(async relation => { - const color = colorByRelationType[relation.type] - - const relatedVersion = relation.versionId === null ? null : (await modrinthApi.getVersion(relation.versionId)) - const versionString = relatedVersion === null ? nullVersionStringByRelationType[relation.type] : relatedVersion.versionString - const relatedMod = (await modrinthApi.getMod(relation.projectId === null ? relatedVersion!.projectId : relation.projectId))! - - return `${color(figures.circleFilled)} ${relatedMod.title}${relation.projectId ? ` (${kleur.blue(relation.projectId)})` : ""}: ` + - `${versionString}${relation.versionId ? ` (${kleur.blue(relation.versionId)})` + " " : ""}` - })) -} - -const modrinthVersionCommand = modrinthCommand.command("version") - -modrinthVersionCommand.command("info ") - .description("Show information about the version.") - .option("-c, --changelog", "Show the changelog.") - .action(async (id, options) => { - const pack = await usePack() - const loader = output.startLoading("Fetching version information") - - const modrinthVersion = await modrinthApi.getVersion(id) - if (modrinthVersion === null) return loader.failAndExit("not found") - - loader.setText("Fetching mod information") - const modrinthMod = (await modrinthApi.getMod(modrinthVersion.projectId))! - - const existingMod = await findModForModrinthMod(modrinthMod) - - let state: keyof typeof versionStateStrings - if (existingMod === null) state = isModrinthVersionCompatible(modrinthVersion, pack) ? "compatible" : "incompatible" - else { - if (existingMod.modFile.source.versionId === modrinthVersion.id) state = "active" - else { - const existingSemver = semver.parse(existingMod.modFile.file.version) - const newSemver = semver.parse(modrinthVersion.versionString) - - if (existingSemver === null || newSemver === null) state = "different_version" - else { - const comparison = newSemver.compare(existingSemver) - - if (comparison === 1) state = "newer_version" - else if (comparison === -1) state = "older_version" - else state = "active" // this should not happen: the versionString is the same but the versionId is different - } - } - } - - loader.setText("Resolving relations") - - const relationsList = modrinthVersion.relations.length !== 0 ? (await getRelationsListLines(modrinthVersion.relations)).join("\n") : kleur.gray("none") - - const relationsColorKey = `${colorByRelationType.hard_dependency("hard dependency")}, ${colorByRelationType.soft_dependency("soft dependency")}, ` + - `${colorByRelationType.embedded_dependency("embedded")}, ${colorByRelationType.incompatible("incompatible")}` - - loader.stop() - - output.println(dedent` - ${kleur.underline(modrinthMod.title)} ${kleur.yellow(`${modrinthVersion.versionString} (${modrinthVersion.releaseChannel})`)} - ${versionStateStrings[state].replace("EXISTING_VERSION", existingMod?.modFile?.file.version ?? "ERROR")} - - Version name: ${kleur.yellow(modrinthVersion.name)} ${kleur.gray(ago(modrinthVersion.publicationDate))} - Minecraft versions: ${modrinthVersion.supportedMinecraftVersions.map(version => version === pack.horizrFile.versions.minecraft ? kleur.green(version) : kleur.red(version)).join(", ")} - Loaders: ${modrinthVersion.supportedLoaders.map(loader => loader === "fabric" ? kleur.green(loader) : kleur.red(loader)).join(", ")} - - Related mods: ${relationsColorKey} - ${relationsList} - - https://modrinth.com/mod/${modrinthMod.slug}/version/${modrinthVersion.versionString} - `) - - if (options.changelog) { - output.println("") - output.println(kleur.underline("Changelog")) - if (modrinthVersion.changelog === null) output.println(kleur.gray("not available")) - else output.printlnWrapping(modrinthVersion.changelog) - } - }) - -modrinthVersionCommand.command("activate ") - .description("Activate the mod version.") - .alias("a") - .option("-f, --force", "Replace a different version already active.") - .action(async (id, options) => { - const pack = await usePack() - const loader = output.startLoading("Fetching version information") - - const modrinthVersion = await modrinthApi.getVersion(id) - if (modrinthVersion === null) return loader.failAndExit("not found") - - loader.setText("Fetching mod information") - const modrinthMod = (await modrinthApi.getMod(modrinthVersion.projectId))! - loader.stop() - - if (!isModrinthVersionCompatible(modrinthVersion, pack)) return output.failAndExit("This version is not compatible with the pack.") - - await handleActivate(modrinthMod, modrinthVersion, options.force) - }) - -modrinthCommand.command("export") - .description("Export a Modrinth pack.") - .option("-s, --no-generate", "Skip regenerating the output directory.") - .option("-z, --no-zip", "Skip creating a zipped .mrpack file.") - .option("-c, --clear", "Remove the output directory afterwards.") - .action(async options => { - const pack = await usePack() - - const outputDirectory = pack.paths.generated.resolve("modrinth-pack") - - if (options.generate) { - const loader = output.startLoading("Generating") - await pack.validateOverridesDirectories() - await fs.remove(outputDirectory.toString()) - await fs.mkdirp(outputDirectory.toString()) - - await fs.writeJson(outputDirectory.resolve("modrinth.index.json").toString(), { - formatVersion: 1, - game: "minecraft", - versionId: pack.horizrFile.meta.version, - name: pack.horizrFile.meta.name, - summary: pack.horizrFile.meta.description, - dependencies: { - minecraft: pack.horizrFile.versions.minecraft, - "fabric-loader": pack.horizrFile.versions.fabric - }, - files: pack.mods.map(mod => ({ - path: `mods/${mod.modFile.file.name}`, - hashes: { - sha1: mod.modFile.file.hashes.sha1, - sha512: mod.modFile.file.hashes.sha512 - }, - env: { - client: mod.modFile.side === "client" || mod.modFile.side === "client-server" ? "required" : "unsupported", - server: mod.modFile.side === "server" || mod.modFile.side === "client-server" ? "required" : "unsupported" - }, - downloads: [ - mod.modFile.file.downloadUrl - ], - fileSize: mod.modFile.file.size - })) - }, { spaces: 2 }) - - if (await fs.pathExists(pack.paths.overrides["client-server"].toString())) await output.withLoading( - fs.copy(pack.paths.overrides["client-server"].toString(), outputDirectory.resolve("overrides").toString(), { recursive: true }), - "Copying client-server overrides" - ) - - if (await fs.pathExists(pack.paths.overrides["client"].toString())) { - await output.withLoading( - fs.copy(pack.paths.overrides["client"].toString(), outputDirectory.resolve("client-overrides").toString(), { recursive: true }), - "Copying client overrides" - ) - - // Workaround for https://github.com/PolyMC/PolyMC/issues/1060 - await walk(pack.paths.overrides["client"].toString(), async (error, path, dirent) => { - if (error) return - if (dirent.isDirectory()) { - const relativePath = pack.paths.overrides["client"].relative(path) - await fs.mkdirp(outputDirectory.resolve("overrides", relativePath).toString()) - } - - return true - }) - } - - if (await fs.pathExists(pack.paths.overrides["server"].toString())) await output.withLoading( - fs.copy(pack.paths.overrides["server"].toString(), outputDirectory.resolve("server-overrides").toString(), { recursive: true }), - "Copying server overrides" - ) - - output.println(kleur.green(`Generated Modrinth pack`)) - loader.stop() - } - - if (options.zip) { - if (!(await fs.pathExists(outputDirectory.toString()))) - output.failAndExit(`The ${kleur.yellow("modrinth-pack")} directory does not exist.\nRun the command without ${kleur.yellow("--no-generate")} to create it.`) - - await output.withLoading(zipDirectory(outputDirectory, pack.paths.generated.resolve("pack.mrpack")), `Creating ${kleur.yellow(".mrpack")} file`) - output.println(kleur.green(`Created ${kleur.yellow("pack.mrpack")}`)) - } - - if (options.clear) { - await fs.remove(outputDirectory.toString()) - output.println(kleur.green(`Removed the ${kleur.yellow("modrinth-pack")} directory`)) - } - }) - -async function handleActivate(modrinthMod: ModrinthMod, modrinthVersion: ModrinthVersion, force: boolean) { - const existingMod = await findModForModrinthMod(modrinthMod) - - if (existingMod === null) { - await addModrinthMod(modrinthMod, modrinthVersion) - output.println(`${modrinthMod.title} (${modrinthVersion.versionString}) ${kleur.green("was successfully activated.")}\n`) - - await handleDependencies(modrinthVersion.relations) - } else { - const oldVersion = existingMod.modFile.file.version - if (existingMod.modFile.source.versionId === modrinthVersion.id) { - output.println(kleur.green("This version is already installed.")) - } else if (force) { - existingMod.modFile.file = getModFileDataForModrinthVersion(modrinthMod, modrinthVersion) - existingMod.modFile.source.versionId = modrinthVersion.id - await existingMod.saveModFile() - output.println(`${kleur.green("Successfully replaced version")} ${oldVersion} ${kleur.green("of")} ${modrinthMod.title} ${kleur.green("with")} ${modrinthVersion.versionString}${kleur.green(".")}`) - - await handleDependencies(modrinthVersion.relations) - } else { - output.failAndExit(`There is already a different version of this mod installed.\nRun this command again with ${kleur.yellow("-f")} to change the version.`) - } - } -} - -async function handleDependencies(relations: ModrinthVersionRelation[]) { - const loader = output.startLoading("Fetching dependency information") - const lines = await getRelationsListLines(relations.filter(relation => relation.type === "hard_dependency" || relation.type === "soft_dependency")) - - if (lines.length !== 0) { - output.println(dedent` - \n${kleur.underline("Dependencies")} ${colorByRelationType.hard_dependency("hard")}, ${colorByRelationType.soft_dependency("soft")} - - ${lines.join("\n")} - `) - } - - loader.stop() -} - -export { modrinthCommand } diff --git a/src_old/modrinth/utils.ts b/src_old/modrinth/utils.ts deleted file mode 100644 index 21f6da2..0000000 --- a/src_old/modrinth/utils.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { IterableElement } from "type-fest" -import { sortBy } from "lodash-es" -import { Mod, Pack, usePack } from "../pack.js" -import { ModFile, ModFileData, MetaFileModrinthSource } from "../files.js" -import { pathExists } from "fs-extra" -import { nanoid } from "nanoid/non-secure" -import { output } from "../../src/utils/output.js" -import kleur from "kleur" -import { ModrinthMod, ModrinthVersion, ModrinthVersionFile } from "./api.js" -import { releaseChannelOrder, Side } from "../shared.js" - -export async function addModrinthMod(modrinthMod: ModrinthMod, modrinthVersion: ModrinthVersion, side?: Side) { - const pack = await usePack() - let id = modrinthMod.slug - - if (await pathExists(pack.paths.mods.resolve(`${id}.json`).toString())) { - const oldId = id - id = `${id}-${nanoid(5)}` - - output.warn( - `There is already a mod file named ${kleur.yellow(`${oldId}.json`)} specifying a non-Modrinth mod.\n` + - `The file for this mod will therefore be named ${kleur.yellow(`${id}.json`)}` - ) - } - - if (side === undefined) { - const isClientSupported = modrinthMod.clientSide !== "unsupported" - const isServerSupported = modrinthMod.serverSide !== "unsupported" - - side = isClientSupported && isServerSupported ? "client-server" : isClientSupported ? "client" : "server" - } - - await pack.addMod(id, { - name: modrinthMod.title, - enabled: true, - ignoreUpdates: false, - side, - file: getModFileDataForModrinthVersion(modrinthMod, modrinthVersion), - source: { - type: "modrinth", - modId: modrinthMod.id, - versionId: modrinthVersion.id - } - }) -} - -