Implement modrinth open and activate
This commit is contained in:
parent
585fd43708
commit
5a1c6f47e4
10 changed files with 106 additions and 529 deletions
77
README.md
77
README.md
|
@ -6,11 +6,7 @@
|
||||||
> A CLI tool for creating and maintaining Minecraft modpacks using the Fabric loader.
|
> A CLI tool for creating and maintaining Minecraft modpacks using the Fabric loader.
|
||||||
|
|
||||||
🎉 Features:
|
🎉 Features:
|
||||||
- Access [Modrinth](https://modrinth.com/)
|
- Add mods from [Modrinth](https://modrinth.com/)
|
||||||
- Search
|
|
||||||
- Add mods by ID or slug
|
|
||||||
- View available versions
|
|
||||||
- View dependencies
|
|
||||||
- Check for updates and view changelogs before applying them
|
- 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 [Modrinth format (`.mrpack`)](https://docs.modrinth.com/docs/modpacks/format_definition/)
|
||||||
- Export the pack to the [`packwiz`](https://packwiz.infra.link/) format
|
- 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 <path>`.
|
A new pack can be initialized using `horizr init <path>`.
|
||||||
|
|
||||||
## 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
|
## 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.
|
A pull request would be even better.
|
||||||
|
|
|
@ -8,7 +8,8 @@
|
||||||
"repository": "https://github.com/horizr/cli",
|
"repository": "https://github.com/horizr/cli",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "tsx src/main.ts",
|
"start": "tsx src/main.ts",
|
||||||
"build": "del dist && tsc"
|
"build": "del dist && tsc",
|
||||||
|
"check": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"horizr": "bin/horizr.js"
|
"horizr": "bin/horizr.js"
|
||||||
|
@ -35,6 +36,7 @@
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"loud-rejection": "^2.2.0",
|
"loud-rejection": "^2.2.0",
|
||||||
"nanoid": "^4.0.0",
|
"nanoid": "^4.0.0",
|
||||||
|
"open": "^8.4.0",
|
||||||
"ora": "^6.1.2",
|
"ora": "^6.1.2",
|
||||||
"p-event": "^5.0.1",
|
"p-event": "^5.0.1",
|
||||||
"p-limit": "^4.0.0",
|
"p-limit": "^4.0.0",
|
||||||
|
|
29
pnpm-lock.yaml
generated
29
pnpm-lock.yaml
generated
|
@ -27,6 +27,7 @@ specifiers:
|
||||||
lodash-es: ^4.17.21
|
lodash-es: ^4.17.21
|
||||||
loud-rejection: ^2.2.0
|
loud-rejection: ^2.2.0
|
||||||
nanoid: ^4.0.0
|
nanoid: ^4.0.0
|
||||||
|
open: ^8.4.0
|
||||||
ora: ^6.1.2
|
ora: ^6.1.2
|
||||||
p-event: ^5.0.1
|
p-event: ^5.0.1
|
||||||
p-limit: ^4.0.0
|
p-limit: ^4.0.0
|
||||||
|
@ -59,6 +60,7 @@ dependencies:
|
||||||
lodash-es: 4.17.21
|
lodash-es: 4.17.21
|
||||||
loud-rejection: 2.2.0
|
loud-rejection: 2.2.0
|
||||||
nanoid: 4.0.0
|
nanoid: 4.0.0
|
||||||
|
open: 8.4.0
|
||||||
ora: 6.1.2
|
ora: 6.1.2
|
||||||
p-event: 5.0.1
|
p-event: 5.0.1
|
||||||
p-limit: 4.0.0
|
p-limit: 4.0.0
|
||||||
|
@ -510,6 +512,11 @@ packages:
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/define-lazy-prop/2.0.0:
|
||||||
|
resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/del-cli/5.0.0:
|
/del-cli/5.0.0:
|
||||||
resolution: {integrity: sha512-rENFhUaYcjoMODwFhhlON+ogN7DoG+4+GFN+bsA1XeDt4w2OKQnQadFP1thHSAlK9FAtl88qgP66wOV+eFZZiQ==}
|
resolution: {integrity: sha512-rENFhUaYcjoMODwFhhlON+ogN7DoG+4+GFN+bsA1XeDt4w2OKQnQadFP1thHSAlK9FAtl88qgP66wOV+eFZZiQ==}
|
||||||
engines: {node: '>=14.16'}
|
engines: {node: '>=14.16'}
|
||||||
|
@ -1023,6 +1030,12 @@ packages:
|
||||||
has: 1.0.3
|
has: 1.0.3
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/is-docker/2.2.1:
|
||||||
|
resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
hasBin: true
|
||||||
|
dev: false
|
||||||
|
|
||||||
/is-extglob/2.1.1:
|
/is-extglob/2.1.1:
|
||||||
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
|
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
@ -1067,6 +1080,13 @@ packages:
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
dev: false
|
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:
|
/js-tokens/4.0.0:
|
||||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -1303,6 +1323,15 @@ packages:
|
||||||
mimic-fn: 2.1.0
|
mimic-fn: 2.1.0
|
||||||
dev: false
|
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:
|
/ora/6.1.2:
|
||||||
resolution: {integrity: sha512-EJQ3NiP5Xo94wJXIzAyOtSb0QEIAUu7m8t6UZ9krbz0vAJqr92JpcK/lEXg91q6B9pEGqrykkd2EQplnifDSBw==}
|
resolution: {integrity: sha512-EJQ3NiP5Xo94wJXIzAyOtSb0QEIAUu7m8t6UZ9krbz0vAJqr92JpcK/lEXg91q6B9pEGqrykkd2EQplnifDSBw==}
|
||||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import {
|
||||||
findMetaFileForModrinthMod,
|
findMetaFileForModrinthMod,
|
||||||
getMetaFileContentVersionForModrinth,
|
getMetaFileContentVersionForModrinth,
|
||||||
getSideOfModrinthMod,
|
getSideOfModrinthMod,
|
||||||
isModrinthVersionCompatible,
|
isModrinthVersionCompatible, resolveFullRelation,
|
||||||
resolveModrinthCode,
|
resolveModrinthCode,
|
||||||
sortModrinthVersionsByPreference
|
sortModrinthVersionsByPreference
|
||||||
} from "../../modrinth/index.js"
|
} from "../../modrinth/index.js"
|
||||||
|
@ -15,6 +15,7 @@ import kleur from "kleur"
|
||||||
import { META_FILE_EXTENSION, metaFileContentSchema, writeJsonFile } from "../../files.js"
|
import { META_FILE_EXTENSION, metaFileContentSchema, writeJsonFile } from "../../files.js"
|
||||||
import fs from "fs-extra"
|
import fs from "fs-extra"
|
||||||
import enquirer from "enquirer"
|
import enquirer from "enquirer"
|
||||||
|
import { orEmptyString } from "../../utils/strings.js"
|
||||||
|
|
||||||
export const activateCommand = new Command("activate")
|
export const activateCommand = new Command("activate")
|
||||||
.argument("<code>")
|
.argument("<code>")
|
||||||
|
@ -27,7 +28,7 @@ export const activateCommand = new Command("activate")
|
||||||
const modrinthMod = resolvedCode.modrinthMod
|
const modrinthMod = resolvedCode.modrinthMod
|
||||||
let modrinthVersion = resolvedCode.modrinthVersion
|
let modrinthVersion = resolvedCode.modrinthVersion
|
||||||
|
|
||||||
const existingMetaFile = findMetaFileForModrinthMod(pack.metaFiles, modrinthMod)
|
const existingMetaFile = findMetaFileForModrinthMod(pack.metaFiles, modrinthMod.id)
|
||||||
if (existingMetaFile !== null) {
|
if (existingMetaFile !== null) {
|
||||||
output.println(`The mod is already active: ${kleur.yellow(existingMetaFile.relativePath.toString())} ${kleur.blue(existingMetaFile.content.version.name)}`)
|
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)
|
await pack.registerCreatedSourceFile(relativePath)
|
||||||
|
|
||||||
output.println(kleur.green(`Successfully wrote ${kleur.yellow(relativePath.toString())}`))
|
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()
|
||||||
})
|
})
|
||||||
|
|
|
@ -2,15 +2,17 @@ import { Command } from "commander"
|
||||||
import { activateCommand } from "./activate.js"
|
import { activateCommand } from "./activate.js"
|
||||||
import dedent from "dedent"
|
import dedent from "dedent"
|
||||||
import kleur from "kleur"
|
import kleur from "kleur"
|
||||||
|
import { openCommand } from "./open.js"
|
||||||
|
|
||||||
export const modrinthCommand = new Command("modrinth")
|
export const modrinthCommand = new Command("modrinth")
|
||||||
.alias("mr")
|
.alias("mr")
|
||||||
.addCommand(activateCommand)
|
.addCommand(activateCommand)
|
||||||
.addHelpText("after", dedent`
|
.addCommand(openCommand)
|
||||||
${kleur.yellow("<code>")} may be one of the following:
|
.addHelpText("afterAll", dedent`
|
||||||
|
\n${kleur.yellow("<code>")} 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 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")})
|
- 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 project ID (${kleur.yellow("AANobbMI")} for Sodium)
|
||||||
- Modrinth version ID, prefixed with ${kleur.yellow("@")} (${kleur.yellow("@Yp8wLY1P")} for Sodium mc1.19-0.4.2)
|
- Modrinth version ID, prefixed with ${kleur.yellow("@")} (${kleur.yellow("@Yp8wLY1P")} for Sodium mc1.19-0.4.2)
|
||||||
`)
|
`)
|
||||||
|
|
24
src/commands/modrinth/open.ts
Normal file
24
src/commands/modrinth/open.ts
Normal file
|
@ -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("<path>")
|
||||||
|
.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.`)
|
||||||
|
})
|
|
@ -1,5 +1,5 @@
|
||||||
import { IterableElement } from "type-fest"
|
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 { sortBy } from "lodash-es"
|
||||||
import { MetaFile, Pack, releaseChannelOrder } from "../pack.js"
|
import { MetaFile, Pack, releaseChannelOrder } from "../pack.js"
|
||||||
import { MetaFileContentVersion } from "../files.js"
|
import { MetaFileContentVersion } from "../files.js"
|
||||||
|
@ -106,5 +106,23 @@ export async function resolveModrinthCode(code: string): Promise<{ modrinthMod:
|
||||||
return output.failAndExit(`Invalid ${kleur.yellow("<code>")}: ${kleur.yellow(code)}`)
|
return output.failAndExit(`Invalid ${kleur.yellow("<code>")}: ${kleur.yellow(code)}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const findMetaFileForModrinthMod = (metaFiles: MetaFile[], modrinthMod: ModrinthMod) =>
|
export const findMetaFileForModrinthMod = (metaFiles: MetaFile[], modrinthModId: string) =>
|
||||||
metaFiles.find(metaFile => metaFile.content.source?.type === "modrinth" && metaFile.content.source.modId === modrinthMod.id) ?? null
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -140,14 +140,13 @@ export const output = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
println(text: string) {
|
println(text: string) {
|
||||||
this.print(text + "\n")
|
this.print(text + "\n\n")
|
||||||
},
|
},
|
||||||
printlnWrapping(text: string) {
|
printlnWrapping(text: string) {
|
||||||
this.println(wrapAnsi(text, process.stdout.columns))
|
this.println(wrapAnsi(text, process.stdout.columns))
|
||||||
},
|
},
|
||||||
warn(text: string) {
|
warn(text: string) {
|
||||||
this.printlnWrapping(`${kleur.yellow(figures.pointer)} ${text}`)
|
this.printlnWrapping(`${kleur.yellow(figures.pointer)} ${text}`)
|
||||||
this.println("")
|
|
||||||
},
|
},
|
||||||
fail(text: string) {
|
fail(text: string) {
|
||||||
last(loadersStack)?.fail()
|
last(loadersStack)?.fail()
|
||||||
|
|
|
@ -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 <query...>")
|
|
||||||
.description("Search for mods.")
|
|
||||||
.option("-l, --limit <number>", "Limit the number of results", optionParsePositiveInteger, 8)
|
|
||||||
.option("-s, --skip <number>", "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<ModrinthMod["clientSide"], kleur.Color> = {
|
|
||||||
optional: kleur.blue,
|
|
||||||
required: kleur.green,
|
|
||||||
unsupported: kleur.red
|
|
||||||
}
|
|
||||||
|
|
||||||
const modrinthModCommand = modrinthCommand.command("mod")
|
|
||||||
|
|
||||||
modrinthModCommand.command("info <id>")
|
|
||||||
.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 <id>")
|
|
||||||
.description("Show a list of compatible versions of the mod.")
|
|
||||||
.option("-l, --limit <number>", "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 <id>")
|
|
||||||
.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<ModrinthVersionRelation["type"], kleur.Color> = {
|
|
||||||
"embedded_dependency": kleur.green,
|
|
||||||
"soft_dependency": kleur.magenta,
|
|
||||||
"hard_dependency": kleur.yellow,
|
|
||||||
"incompatible": kleur.red
|
|
||||||
}
|
|
||||||
|
|
||||||
const nullVersionStringByRelationType: Record<ModrinthVersionRelation["type"], string> = {
|
|
||||||
"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 <id>")
|
|
||||||
.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 <id>")
|
|
||||||
.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 }
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue