Implement overrides for Modrinth export
This commit is contained in:
parent
862048d169
commit
98007b36a0
18 changed files with 313 additions and 81 deletions
|
@ -22,8 +22,8 @@
|
||||||
"horizr": "./bin/horizr"
|
"horizr": "./bin/horizr"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@root/walk": "^1.1.0",
|
||||||
"commander": "^9.4.0",
|
"commander": "^9.4.0",
|
||||||
"cross-zip": "^4.0.0",
|
|
||||||
"dedent": "^0.7.0",
|
"dedent": "^0.7.0",
|
||||||
"env-paths": "^3.0.0",
|
"env-paths": "^3.0.0",
|
||||||
"figures": "^5.0.0",
|
"figures": "^5.0.0",
|
||||||
|
@ -37,20 +37,22 @@
|
||||||
"loud-rejection": "^2.2.0",
|
"loud-rejection": "^2.2.0",
|
||||||
"nanoid": "^4.0.0",
|
"nanoid": "^4.0.0",
|
||||||
"ora": "^6.1.2",
|
"ora": "^6.1.2",
|
||||||
|
"p-event": "^5.0.1",
|
||||||
"p-limit": "^4.0.0",
|
"p-limit": "^4.0.0",
|
||||||
"s-ago": "^2.2.0",
|
"s-ago": "^2.2.0",
|
||||||
"semver": "^7.3.7",
|
"semver": "^7.3.7",
|
||||||
"wrap-ansi": "^8.0.1",
|
"wrap-ansi": "^8.0.1",
|
||||||
|
"yazl": "^2.5.1",
|
||||||
"zod": "^3.18.0"
|
"zod": "^3.18.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/cross-zip": "^4.0.0",
|
|
||||||
"@types/dedent": "^0.7.0",
|
"@types/dedent": "^0.7.0",
|
||||||
"@types/fs-extra": "^9.0.13",
|
"@types/fs-extra": "^9.0.13",
|
||||||
"@types/lodash-es": "^4.17.6",
|
"@types/lodash-es": "^4.17.6",
|
||||||
"@types/node": "^18.7.3",
|
"@types/node": "^18.7.3",
|
||||||
"@types/semver": "^7.3.12",
|
"@types/semver": "^7.3.12",
|
||||||
"@types/wrap-ansi": "^8.0.1",
|
"@types/wrap-ansi": "^8.0.1",
|
||||||
|
"@types/yazl": "^2.4.2",
|
||||||
"tsx": "^3.8.2",
|
"tsx": "^3.8.2",
|
||||||
"type-fest": "^2.18.0",
|
"type-fest": "^2.18.0",
|
||||||
"typescript": "^4.7.4"
|
"typescript": "^4.7.4"
|
||||||
|
|
53
pnpm-lock.yaml
generated
53
pnpm-lock.yaml
generated
|
@ -1,15 +1,15 @@
|
||||||
lockfileVersion: 5.4
|
lockfileVersion: 5.4
|
||||||
|
|
||||||
specifiers:
|
specifiers:
|
||||||
'@types/cross-zip': ^4.0.0
|
'@root/walk': ^1.1.0
|
||||||
'@types/dedent': ^0.7.0
|
'@types/dedent': ^0.7.0
|
||||||
'@types/fs-extra': ^9.0.13
|
'@types/fs-extra': ^9.0.13
|
||||||
'@types/lodash-es': ^4.17.6
|
'@types/lodash-es': ^4.17.6
|
||||||
'@types/node': ^18.7.3
|
'@types/node': ^18.7.3
|
||||||
'@types/semver': ^7.3.12
|
'@types/semver': ^7.3.12
|
||||||
'@types/wrap-ansi': ^8.0.1
|
'@types/wrap-ansi': ^8.0.1
|
||||||
|
'@types/yazl': ^2.4.2
|
||||||
commander: ^9.4.0
|
commander: ^9.4.0
|
||||||
cross-zip: ^4.0.0
|
|
||||||
dedent: ^0.7.0
|
dedent: ^0.7.0
|
||||||
env-paths: ^3.0.0
|
env-paths: ^3.0.0
|
||||||
figures: ^5.0.0
|
figures: ^5.0.0
|
||||||
|
@ -23,6 +23,7 @@ specifiers:
|
||||||
loud-rejection: ^2.2.0
|
loud-rejection: ^2.2.0
|
||||||
nanoid: ^4.0.0
|
nanoid: ^4.0.0
|
||||||
ora: ^6.1.2
|
ora: ^6.1.2
|
||||||
|
p-event: ^5.0.1
|
||||||
p-limit: ^4.0.0
|
p-limit: ^4.0.0
|
||||||
s-ago: ^2.2.0
|
s-ago: ^2.2.0
|
||||||
semver: ^7.3.7
|
semver: ^7.3.7
|
||||||
|
@ -30,11 +31,12 @@ specifiers:
|
||||||
type-fest: ^2.18.0
|
type-fest: ^2.18.0
|
||||||
typescript: ^4.7.4
|
typescript: ^4.7.4
|
||||||
wrap-ansi: ^8.0.1
|
wrap-ansi: ^8.0.1
|
||||||
|
yazl: ^2.5.1
|
||||||
zod: ^3.18.0
|
zod: ^3.18.0
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@root/walk': 1.1.0
|
||||||
commander: 9.4.0
|
commander: 9.4.0
|
||||||
cross-zip: 4.0.0
|
|
||||||
dedent: 0.7.0
|
dedent: 0.7.0
|
||||||
env-paths: 3.0.0
|
env-paths: 3.0.0
|
||||||
figures: 5.0.0
|
figures: 5.0.0
|
||||||
|
@ -48,20 +50,22 @@ dependencies:
|
||||||
loud-rejection: 2.2.0
|
loud-rejection: 2.2.0
|
||||||
nanoid: 4.0.0
|
nanoid: 4.0.0
|
||||||
ora: 6.1.2
|
ora: 6.1.2
|
||||||
|
p-event: 5.0.1
|
||||||
p-limit: 4.0.0
|
p-limit: 4.0.0
|
||||||
s-ago: 2.2.0
|
s-ago: 2.2.0
|
||||||
semver: 7.3.7
|
semver: 7.3.7
|
||||||
wrap-ansi: 8.0.1
|
wrap-ansi: 8.0.1
|
||||||
|
yazl: 2.5.1
|
||||||
zod: 3.18.0
|
zod: 3.18.0
|
||||||
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@types/cross-zip': 4.0.0
|
|
||||||
'@types/dedent': 0.7.0
|
'@types/dedent': 0.7.0
|
||||||
'@types/fs-extra': 9.0.13
|
'@types/fs-extra': 9.0.13
|
||||||
'@types/lodash-es': 4.17.6
|
'@types/lodash-es': 4.17.6
|
||||||
'@types/node': 18.7.3
|
'@types/node': 18.7.3
|
||||||
'@types/semver': 7.3.12
|
'@types/semver': 7.3.12
|
||||||
'@types/wrap-ansi': 8.0.1
|
'@types/wrap-ansi': 8.0.1
|
||||||
|
'@types/yazl': 2.4.2
|
||||||
tsx: 3.8.2
|
tsx: 3.8.2
|
||||||
type-fest: 2.18.0
|
type-fest: 2.18.0
|
||||||
typescript: 4.7.4
|
typescript: 4.7.4
|
||||||
|
@ -98,6 +102,10 @@ packages:
|
||||||
dev: true
|
dev: true
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
/@root/walk/1.1.0:
|
||||||
|
resolution: {integrity: sha512-FfXPAta9u2dBuaXhPRawBcijNC9rmKVApmbi6lIZyg36VR/7L02ytxoY5K/14PJlHqiBUoYII73cTlekdKTUOw==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@sindresorhus/is/5.3.0:
|
/@sindresorhus/is/5.3.0:
|
||||||
resolution: {integrity: sha512-CX6t4SYQ37lzxicAqsBtxA3OseeoVrh9cSJ5PFYam0GksYlupRfy1A+Q4aYD3zvcfECLc0zO2u+ZnR2UYKvCrw==}
|
resolution: {integrity: sha512-CX6t4SYQ37lzxicAqsBtxA3OseeoVrh9cSJ5PFYam0GksYlupRfy1A+Q4aYD3zvcfECLc0zO2u+ZnR2UYKvCrw==}
|
||||||
engines: {node: '>=14.16'}
|
engines: {node: '>=14.16'}
|
||||||
|
@ -119,10 +127,6 @@ packages:
|
||||||
'@types/responselike': 1.0.0
|
'@types/responselike': 1.0.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@types/cross-zip/4.0.0:
|
|
||||||
resolution: {integrity: sha512-jZRaaM3aib7qgVhCWeo76vVNJW+8+ujJzP/JHPPA4WUp5YBNMsqiyu5uYWg2eS05+jgSfb5NN8eqrAG72Ab2kA==}
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@types/dedent/0.7.0:
|
/@types/dedent/0.7.0:
|
||||||
resolution: {integrity: sha512-EGlKlgMhnLt/cM4DbUSafFdrkeJoC9Mvnj0PUCU7tFmTjMjNRT957kXCx0wYm3JuEq4o4ZsS5vG+NlkM2DMd2A==}
|
resolution: {integrity: sha512-EGlKlgMhnLt/cM4DbUSafFdrkeJoC9Mvnj0PUCU7tFmTjMjNRT957kXCx0wYm3JuEq4o4ZsS5vG+NlkM2DMd2A==}
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -174,6 +178,12 @@ packages:
|
||||||
resolution: {integrity: sha512-cjwgM6WWy9YakrQ36Pq0vg5XoNblVEaNq+/pHngKl4GyyDIxTeskPoG+tp4LsRk0lHrA4LaLJqlvYridi7mzlw==}
|
resolution: {integrity: sha512-cjwgM6WWy9YakrQ36Pq0vg5XoNblVEaNq+/pHngKl4GyyDIxTeskPoG+tp4LsRk0lHrA4LaLJqlvYridi7mzlw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@types/yazl/2.4.2:
|
||||||
|
resolution: {integrity: sha512-T+9JH8O2guEjXNxqmybzQ92mJUh2oCwDDMSSimZSe1P+pceZiFROZLYmcbqkzV5EUwz6VwcKXCO2S2yUpra6XQ==}
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 18.7.3
|
||||||
|
dev: true
|
||||||
|
|
||||||
/ansi-regex/6.0.1:
|
/ansi-regex/6.0.1:
|
||||||
resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==}
|
resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
@ -201,6 +211,10 @@ packages:
|
||||||
readable-stream: 3.6.0
|
readable-stream: 3.6.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/buffer-crc32/0.2.13:
|
||||||
|
resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/buffer-from/1.1.2:
|
/buffer-from/1.1.2:
|
||||||
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
|
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -271,11 +285,6 @@ packages:
|
||||||
json-buffer: 3.0.1
|
json-buffer: 3.0.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/cross-zip/4.0.0:
|
|
||||||
resolution: {integrity: sha512-MEzGfZo0rqE10O/B+AEcCSJLZsrWuRUvmqJTqHNqBtALhaJc3E3ixLGLJNTRzEA2K34wbmOHC4fwYs9sVsdcCA==}
|
|
||||||
engines: {node: '>=12.10'}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/currently-unhandled/0.4.1:
|
/currently-unhandled/0.4.1:
|
||||||
resolution: {integrity: sha512-/fITjgjGU50vjQ4FH6eUoYu+iUoUKIXws2hL15JJpIR+BbTxaXQsMuuyjtNh2WqsSBS5nsaZHFsFecyw5CCAng==}
|
resolution: {integrity: sha512-/fITjgjGU50vjQ4FH6eUoYu+iUoUKIXws2hL15JJpIR+BbTxaXQsMuuyjtNh2WqsSBS5nsaZHFsFecyw5CCAng==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
@ -825,6 +834,13 @@ packages:
|
||||||
engines: {node: '>=12.20'}
|
engines: {node: '>=12.20'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/p-event/5.0.1:
|
||||||
|
resolution: {integrity: sha512-dd589iCQ7m1L0bmC5NLlVYfy3TbBEsMUfWx9PyAgPeIcFZ/E2yaTZ4Rz4MiBmmJShviiftHVXOqfnfzJ6kyMrQ==}
|
||||||
|
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||||
|
dependencies:
|
||||||
|
p-timeout: 5.1.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/p-limit/4.0.0:
|
/p-limit/4.0.0:
|
||||||
resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==}
|
resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==}
|
||||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||||
|
@ -839,6 +855,11 @@ packages:
|
||||||
p-limit: 4.0.0
|
p-limit: 4.0.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/p-timeout/5.1.0:
|
||||||
|
resolution: {integrity: sha512-auFDyzzzGZZZdHz3BtET9VEz0SE/uMEAx7uWfGPucfzEwwe/xH0iVeZibQmANYE/hp9T2+UUZT5m+BKyrDp3Ew==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/path-exists/5.0.0:
|
/path-exists/5.0.0:
|
||||||
resolution: {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==}
|
resolution: {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==}
|
||||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||||
|
@ -1005,6 +1026,12 @@ packages:
|
||||||
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
|
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/yazl/2.5.1:
|
||||||
|
resolution: {integrity: sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw==}
|
||||||
|
dependencies:
|
||||||
|
buffer-crc32: 0.2.13
|
||||||
|
dev: false
|
||||||
|
|
||||||
/yocto-queue/1.0.0:
|
/yocto-queue/1.0.0:
|
||||||
resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==}
|
resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==}
|
||||||
engines: {node: '>=12.20'}
|
engines: {node: '>=12.20'}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { Command } from "commander"
|
||||||
import { take } from "lodash-es"
|
import { take } from "lodash-es"
|
||||||
import { usePack } from "../pack.js"
|
import { usePack } from "../pack.js"
|
||||||
import kleur from "kleur"
|
import kleur from "kleur"
|
||||||
import { optionParsePositiveInteger, truncateWithEllipsis, zip } from "../utils.js"
|
import { optionParsePositiveInteger, truncateWithEllipsis, zipDirectory } from "../utils.js"
|
||||||
import { default as wrapAnsi } from "wrap-ansi"
|
import { default as wrapAnsi } from "wrap-ansi"
|
||||||
import figures from "figures"
|
import figures from "figures"
|
||||||
import {
|
import {
|
||||||
|
@ -267,59 +267,79 @@ modrinthVersionCommand.command("activate <id>")
|
||||||
})
|
})
|
||||||
|
|
||||||
modrinthCommand.command("export")
|
modrinthCommand.command("export")
|
||||||
.description("Generate a Modrinth pack file suitable for uploading.")
|
.description("Export a Modrinth pack.")
|
||||||
.option("-z, --no-zip", "Skip the creation of a zipped .mrpack file.")
|
.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.")
|
.option("-c, --clear", "Remove the output directory afterwards.")
|
||||||
.action(async options => {
|
.action(async options => {
|
||||||
const pack = await usePack()
|
const pack = await usePack()
|
||||||
const loader = output.startLoading("Generating")
|
|
||||||
|
|
||||||
const outputDirectory = pack.rootDirectoryPath.resolve("modrinth-pack")
|
const outputDirectory = pack.paths.generated.resolve("modrinth-pack")
|
||||||
await fs.remove(outputDirectory.toString())
|
|
||||||
await fs.mkdirp(outputDirectory.toString())
|
|
||||||
|
|
||||||
await fs.writeJson(outputDirectory.resolve("modrinth.index.json").toString(), {
|
if (options.generate) {
|
||||||
formatVersion: 1,
|
const loader = output.startLoading("Generating")
|
||||||
game: "minecraft",
|
await pack.validateOverridesDirectories()
|
||||||
versionId: pack.horizrFile.meta.version,
|
await fs.remove(outputDirectory.toString())
|
||||||
name: pack.horizrFile.meta.name,
|
await fs.mkdirp(outputDirectory.toString())
|
||||||
summary: pack.horizrFile.meta.description,
|
|
||||||
dependencies: {
|
await fs.writeJson(outputDirectory.resolve("modrinth.index.json").toString(), {
|
||||||
minecraft: pack.horizrFile.versions.minecraft,
|
formatVersion: 1,
|
||||||
[`${pack.horizrFile.loader}-loader`]: pack.horizrFile.versions.loader
|
game: "minecraft",
|
||||||
},
|
versionId: pack.horizrFile.meta.version,
|
||||||
files: pack.mods.map(mod => ({
|
name: pack.horizrFile.meta.name,
|
||||||
path: `mods/${mod.modFile.file.name}`,
|
summary: pack.horizrFile.meta.description,
|
||||||
hashes: {
|
dependencies: {
|
||||||
sha1: mod.modFile.file.hashes.sha1,
|
minecraft: pack.horizrFile.versions.minecraft,
|
||||||
sha512: mod.modFile.file.hashes.sha512
|
[`${pack.horizrFile.loader}-loader`]: pack.horizrFile.versions.loader
|
||||||
},
|
},
|
||||||
env: {
|
files: pack.mods.map(mod => ({
|
||||||
client: mod.modFile.side === "client" || mod.modFile.side === "client+server" ? "required" : "unsupported",
|
path: `mods/${mod.modFile.file.name}`,
|
||||||
server: mod.modFile.side === "server" || mod.modFile.side === "client+server" ? "required" : "unsupported"
|
hashes: {
|
||||||
},
|
sha1: mod.modFile.file.hashes.sha1,
|
||||||
downloads: [
|
sha512: mod.modFile.file.hashes.sha512
|
||||||
mod.modFile.file.downloadUrl
|
},
|
||||||
],
|
env: {
|
||||||
fileSize: mod.modFile.file.size
|
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 (!options.clear) output.println(kleur.green(`Generated Modrinth pack in ${kleur.yellow("modrinth-pack")}`))
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
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 (options.zip) {
|
||||||
loader.setText(`Creating ${kleur.yellow(".mrpack")} file`)
|
if (!(await fs.pathExists(outputDirectory.toString())))
|
||||||
await zip(outputDirectory.toString(), pack.rootDirectoryPath.resolve("pack.mrpack").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")}`))
|
output.println(kleur.green(`Created ${kleur.yellow("pack.mrpack")}`))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.clear) {
|
if (options.clear) {
|
||||||
loader.setText("Removing the output directory")
|
|
||||||
await fs.remove(outputDirectory.toString())
|
await fs.remove(outputDirectory.toString())
|
||||||
output.println(kleur.green(`Removed the ${kleur.yellow("modrinth-pack")} directory`))
|
output.println(kleur.green(`Removed the ${kleur.yellow("modrinth-pack")} directory`))
|
||||||
}
|
}
|
||||||
|
|
||||||
loader.stop()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
async function handleActivate(modrinthMod: ModrinthMod, modrinthVersion: ModrinthVersion, force: boolean) {
|
async function handleActivate(modrinthMod: ModrinthMod, modrinthVersion: ModrinthVersion, force: boolean) {
|
||||||
|
|
|
@ -14,8 +14,14 @@ interface IndexedFile {
|
||||||
isMeta: boolean
|
isMeta: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
packwizCommand.command("import")
|
||||||
|
.description("Import a packwiz pack.")
|
||||||
|
.action(async () => {
|
||||||
|
// TODO: Import packwiz pack
|
||||||
|
})
|
||||||
|
|
||||||
packwizCommand.command("export")
|
packwizCommand.command("export")
|
||||||
.description("Generates a packwiz pack in the packwiz directory")
|
.description("Generate a packwiz pack in the packwiz directory.")
|
||||||
.action(async () => {
|
.action(async () => {
|
||||||
const pack = await usePack()
|
const pack = await usePack()
|
||||||
|
|
||||||
|
@ -24,7 +30,7 @@ packwizCommand.command("export")
|
||||||
|
|
||||||
const loader = output.startLoading("Generating")
|
const loader = output.startLoading("Generating")
|
||||||
|
|
||||||
const outputDirectoryPath = pack.rootDirectoryPath.resolve("packwiz")
|
const outputDirectoryPath = pack.paths.generated.resolve("packwiz")
|
||||||
const modsDirectoryPath = outputDirectoryPath.resolve("mods")
|
const modsDirectoryPath = outputDirectoryPath.resolve("mods")
|
||||||
await fs.remove(outputDirectoryPath.toString())
|
await fs.remove(outputDirectoryPath.toString())
|
||||||
await fs.mkdirp(modsDirectoryPath.toString())
|
await fs.mkdirp(modsDirectoryPath.toString())
|
||||||
|
@ -88,7 +94,7 @@ packwizCommand.command("export")
|
||||||
`)
|
`)
|
||||||
|
|
||||||
loader.stop()
|
loader.stop()
|
||||||
output.println(kleur.green("Successfully generated packwiz pack."))
|
output.println(kleur.green("Generated packwiz pack"))
|
||||||
})
|
})
|
||||||
|
|
||||||
export { packwizCommand}
|
export { packwizCommand}
|
||||||
|
|
10
src/files.ts
10
src/files.ts
|
@ -6,6 +6,8 @@ import { dirname } from "path"
|
||||||
import { findUp } from "find-up"
|
import { findUp } from "find-up"
|
||||||
import { output } from "./output.js"
|
import { output } from "./output.js"
|
||||||
import { Path } from "./path.js"
|
import { Path } from "./path.js"
|
||||||
|
import { Dirent } from "fs"
|
||||||
|
import { sides } from "./shared.js"
|
||||||
|
|
||||||
export async function findPackDirectoryPath(): Promise<Path> {
|
export async function findPackDirectoryPath(): Promise<Path> {
|
||||||
if (process.argv0.endsWith("/node")) { // run using pnpm
|
if (process.argv0.endsWith("/node")) { // run using pnpm
|
||||||
|
@ -99,7 +101,7 @@ const modFileSchema = z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
enabled: z.boolean().default(true),
|
enabled: z.boolean().default(true),
|
||||||
ignoreUpdates: z.boolean().default(false),
|
ignoreUpdates: z.boolean().default(false),
|
||||||
side: z.enum(["client", "server", "client+server"]),
|
side: z.enum(sides),
|
||||||
comment: z.string().optional(),
|
comment: z.string().optional(),
|
||||||
file: modFileDataSchema,
|
file: modFileDataSchema,
|
||||||
source: z.discriminatedUnion("type", [
|
source: z.discriminatedUnion("type", [
|
||||||
|
@ -129,3 +131,9 @@ export async function readModIds(packPath: Path) {
|
||||||
|
|
||||||
return files.filter(file => file.isFile() && file.name.endsWith(".json")).map(file => file.name.slice(0, -5))
|
return files.filter(file => file.isFile() && file.name.endsWith(".json")).map(file => file.name.slice(0, -5))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getOverrideDirents(overridesDirectoryPath: Path): Promise<Dirent[]> {
|
||||||
|
if (!await fs.pathExists(overridesDirectoryPath.toString())) return []
|
||||||
|
|
||||||
|
return await fs.readdir(overridesDirectoryPath.toString(), { withFileTypes: true })
|
||||||
|
}
|
||||||
|
|
13
src/main.ts
13
src/main.ts
|
@ -39,7 +39,7 @@ program.command("remove <code>")
|
||||||
const pack = await usePack()
|
const pack = await usePack()
|
||||||
const mod = pack.findModByCodeOrFail(code)
|
const mod = pack.findModByCodeOrFail(code)
|
||||||
|
|
||||||
await removeModFile(pack.rootDirectoryPath, mod.id)
|
await removeModFile(pack.paths.root, mod.id)
|
||||||
|
|
||||||
output.println(`${mod.modFile.name} ${kleur.green("was removed from the pack.")}`)
|
output.println(`${mod.modFile.name} ${kleur.green("was removed from the pack.")}`)
|
||||||
})
|
})
|
||||||
|
@ -81,15 +81,20 @@ program.command("update [code]")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
loudRejection()
|
loudRejection(stack => {
|
||||||
|
output.failAndExit(stack)
|
||||||
|
})
|
||||||
|
|
||||||
program
|
program
|
||||||
.addCommand(packwizCommand)
|
.addCommand(packwizCommand)
|
||||||
.addCommand(modrinthCommand)
|
.addCommand(modrinthCommand)
|
||||||
.addHelpText("afterAll", "\n" + dedent`
|
.addHelpText("after", "\n" + dedent`
|
||||||
${kleur.blue("code")} can be one of the following:
|
${kleur.blue("code")} can be one of the following:
|
||||||
- The name of a file in the ${kleur.yellow("mods")} directory, optionally without the ${kleur.yellow(".json")} extension
|
- The name of a file in the ${kleur.yellow("mods")} directory, optionally without the ${kleur.yellow(".json")} extension
|
||||||
- The ID of a Modrinth Project, prefixed with ${kleur.yellow("mr:")}
|
- The ID of a Modrinth Project, prefixed with ${kleur.yellow("mr:")}
|
||||||
- The ID of a Modrinth Version, prefixed with ${kleur.yellow("mrv:")}
|
- The ID of a Modrinth Version, prefixed with ${kleur.yellow("mrv:")}
|
||||||
`)
|
`)
|
||||||
.parse(process.argv)
|
.parseAsync(process.argv)
|
||||||
|
.catch(error => {
|
||||||
|
output.failAndExit(error.message)
|
||||||
|
})
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import originalGot, { HTTPError, Response } from "got"
|
import got, { HTTPError, Response } from "got"
|
||||||
import kleur from "kleur"
|
import kleur from "kleur"
|
||||||
import { KeyvFile } from "keyv-file"
|
import { KeyvFile } from "keyv-file"
|
||||||
import { delay } from "../utils.js"
|
import { delay } from "../utils.js"
|
||||||
|
@ -15,8 +15,6 @@ const keyvCache = new KeyvFile({
|
||||||
decode: JSON.parse
|
decode: JSON.parse
|
||||||
})
|
})
|
||||||
|
|
||||||
const got = originalGot.extend()
|
|
||||||
|
|
||||||
async function getModrinthApiOptional(url: string): Promise<any | null> {
|
async function getModrinthApiOptional(url: string): Promise<any | null> {
|
||||||
let response: Response
|
let response: Response
|
||||||
|
|
||||||
|
@ -28,7 +26,30 @@ async function getModrinthApiOptional(url: string): Promise<any | null> {
|
||||||
},
|
},
|
||||||
cache: keyvCache,
|
cache: keyvCache,
|
||||||
responseType: "json",
|
responseType: "json",
|
||||||
throwHttpErrors: false
|
throwHttpErrors: false,
|
||||||
|
retry: {
|
||||||
|
limit: 3,
|
||||||
|
maxRetryAfter: 10,
|
||||||
|
statusCodes: [
|
||||||
|
408,
|
||||||
|
413,
|
||||||
|
// 429,
|
||||||
|
500,
|
||||||
|
502,
|
||||||
|
503,
|
||||||
|
504,
|
||||||
|
521,
|
||||||
|
522,
|
||||||
|
524,
|
||||||
|
]
|
||||||
|
},
|
||||||
|
hooks: {
|
||||||
|
beforeRetry: [
|
||||||
|
(error, retryCount) => {
|
||||||
|
output.warn(`Request to ${kleur.yellow(error.request!.requestUrl!.toString())} failed, retrying ${kleur.gray(`(${retryCount}/3)`)}`)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if (response.statusCode.toString().startsWith("2")) {
|
if (response.statusCode.toString().startsWith("2")) {
|
||||||
|
|
|
@ -23,6 +23,40 @@ export interface InternalLoader extends Loader {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const output = {
|
export const output = {
|
||||||
|
async withLoading<T>(promise: Promise<T>, options: string | {
|
||||||
|
text: string
|
||||||
|
exitOnError?: boolean,
|
||||||
|
getErrorMessage?: (e: Error) => string,
|
||||||
|
}): Promise<T> {
|
||||||
|
const actualOptions = typeof options === "string" ? {
|
||||||
|
text: options,
|
||||||
|
exitOnError: true
|
||||||
|
} : {
|
||||||
|
text: options.text,
|
||||||
|
exitOnError: options.exitOnError ?? true,
|
||||||
|
getErrorMessage: options.getErrorMessage ?? (options.exitOnError !== false ? ((e: Error) => e.message) : undefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
const loader = this.startLoading(actualOptions.text)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await promise
|
||||||
|
loader.stop()
|
||||||
|
return result
|
||||||
|
} catch (e: unknown) {
|
||||||
|
const error = e as Error
|
||||||
|
|
||||||
|
if (actualOptions.exitOnError) {
|
||||||
|
if (actualOptions.getErrorMessage) loader.failAndExit(actualOptions.getErrorMessage(error))
|
||||||
|
else loader.failAndExit()
|
||||||
|
} else {
|
||||||
|
if (actualOptions.getErrorMessage) loader.fail(actualOptions.getErrorMessage(error))
|
||||||
|
else loader.fail()
|
||||||
|
}
|
||||||
|
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
},
|
||||||
startLoading(text: string): Loader {
|
startLoading(text: string): Loader {
|
||||||
const loader: InternalLoader = {
|
const loader: InternalLoader = {
|
||||||
isActive: false,
|
isActive: false,
|
||||||
|
@ -30,7 +64,8 @@ export const output = {
|
||||||
text,
|
text,
|
||||||
spinner: ora({
|
spinner: ora({
|
||||||
spinner: "dots4",
|
spinner: "dots4",
|
||||||
color: "blue"
|
color: "blue",
|
||||||
|
prefixText: "\n"
|
||||||
}),
|
}),
|
||||||
fail(message?: string) {
|
fail(message?: string) {
|
||||||
if (this.state !== "running") throw new Error("state is not 'running'")
|
if (this.state !== "running") throw new Error("state is not 'running'")
|
||||||
|
@ -45,7 +80,16 @@ export const output = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
failAndExit(message?: string): never {
|
failAndExit(message?: string): never {
|
||||||
this.fail(message)
|
if (this.state !== "running") throw new Error("state is not 'running'")
|
||||||
|
|
||||||
|
if (message !== undefined) this.text = this.text + " — " + kleur.red(message)
|
||||||
|
|
||||||
|
if (!this.isActive) {
|
||||||
|
last(loadersStack)?.deactivate()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.spinner.fail(this.text)
|
||||||
|
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
},
|
},
|
||||||
setText(text: string) {
|
setText(text: string) {
|
||||||
|
@ -91,7 +135,7 @@ export const output = {
|
||||||
process.stdout.write(text)
|
process.stdout.write(text)
|
||||||
} else {
|
} else {
|
||||||
loader.deactivate()
|
loader.deactivate()
|
||||||
process.stdout.write(text + "\n" + "\n")
|
process.stdout.write(text)
|
||||||
loader.activate()
|
loader.activate()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
36
src/pack.ts
36
src/pack.ts
|
@ -1,11 +1,11 @@
|
||||||
import { findPackDirectoryPath, HorizrFile, ModFile, ModFileModrinthSource, readHorizrFile, readModFile, readModIds, writeModFile } from "./files.js"
|
import { findPackDirectoryPath, getOverrideDirents, HorizrFile, ModFile, ModFileModrinthSource, readHorizrFile, readModFile, readModIds, writeModFile } from "./files.js"
|
||||||
import { output } from "./output.js"
|
import { output } from "./output.js"
|
||||||
import pLimit from "p-limit"
|
import pLimit from "p-limit"
|
||||||
import kleur from "kleur"
|
import kleur from "kleur"
|
||||||
import { modrinthApi } from "./modrinth/api.js"
|
import { modrinthApi } from "./modrinth/api.js"
|
||||||
import semver from "semver"
|
import semver from "semver"
|
||||||
import { Path } from "./path.js"
|
import { Path } from "./path.js"
|
||||||
import { ReleaseChannel } from "./shared.js"
|
import { ReleaseChannel, Side, sides } from "./shared.js"
|
||||||
import { getModFileDataForModrinthVersion, sortModrinthVersionsByPreference } from "./modrinth/utils.js"
|
import { getModFileDataForModrinthVersion, sortModrinthVersionsByPreference } from "./modrinth/utils.js"
|
||||||
|
|
||||||
export interface Update {
|
export interface Update {
|
||||||
|
@ -16,13 +16,18 @@ export interface Update {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Pack {
|
export interface Pack {
|
||||||
rootDirectoryPath: Path
|
paths: {
|
||||||
|
root: Path,
|
||||||
|
generated: Path,
|
||||||
|
overrides: Record<Side, Path>
|
||||||
|
},
|
||||||
horizrFile: HorizrFile
|
horizrFile: HorizrFile
|
||||||
mods: Mod[]
|
mods: Mod[]
|
||||||
|
|
||||||
addMod(id: string, file: ModFile): Promise<void>
|
addMod(id: string, file: ModFile): Promise<void>
|
||||||
findModByCode(code: string): Mod | null
|
findModByCode(code: string): Mod | null
|
||||||
findModByCodeOrFail(code: string): Mod
|
findModByCodeOrFail(code: string): Mod
|
||||||
|
validateOverridesDirectories(): Promise<void>
|
||||||
|
|
||||||
checkForUpdates(allowedReleaseChannels: ReleaseChannel[]): Promise<Update[]>
|
checkForUpdates(allowedReleaseChannels: ReleaseChannel[]): Promise<Update[]>
|
||||||
}
|
}
|
||||||
|
@ -41,9 +46,18 @@ let pack: Pack
|
||||||
export async function usePack(): Promise<Pack> {
|
export async function usePack(): Promise<Pack> {
|
||||||
if (pack === undefined) {
|
if (pack === undefined) {
|
||||||
const rootDirectoryPath = await findPackDirectoryPath()
|
const rootDirectoryPath = await findPackDirectoryPath()
|
||||||
|
const overridesDirectoryPath = rootDirectoryPath.resolve("overrides")
|
||||||
|
|
||||||
pack = {
|
pack = {
|
||||||
rootDirectoryPath,
|
paths: {
|
||||||
|
root: rootDirectoryPath,
|
||||||
|
generated: rootDirectoryPath.resolve("generated"),
|
||||||
|
overrides: {
|
||||||
|
client: overridesDirectoryPath.resolve("client"),
|
||||||
|
server: overridesDirectoryPath.resolve("server"),
|
||||||
|
"client-server": overridesDirectoryPath.resolve("client-server")
|
||||||
|
}
|
||||||
|
},
|
||||||
horizrFile: await readHorizrFile(rootDirectoryPath),
|
horizrFile: await readHorizrFile(rootDirectoryPath),
|
||||||
mods: await Promise.all((await readModIds(rootDirectoryPath)).map(async id => {
|
mods: await Promise.all((await readModIds(rootDirectoryPath)).map(async id => {
|
||||||
const mod: Mod = {
|
const mod: Mod = {
|
||||||
|
@ -120,6 +134,20 @@ export async function usePack(): Promise<Pack> {
|
||||||
if (mod === null) return output.failAndExit("The mod could not be found.")
|
if (mod === null) return output.failAndExit("The mod could not be found.")
|
||||||
return mod
|
return mod
|
||||||
},
|
},
|
||||||
|
async validateOverridesDirectories() {
|
||||||
|
const dirents = await getOverrideDirents(overridesDirectoryPath)
|
||||||
|
|
||||||
|
const notDirectories = dirents.filter(dirent => !dirent.isDirectory())
|
||||||
|
if (notDirectories.length !== 0)
|
||||||
|
output.failAndExit(
|
||||||
|
`The ${kleur.yellow("overrides")} directory contains files that are not directories:\n${notDirectories.slice(0, 5).map(e => `- ${e.name}`).join("\n")}` +
|
||||||
|
(notDirectories.length > 5 ? `\n${kleur.gray(`and ${notDirectories.length - 5} more`)}` : "") +
|
||||||
|
`\n\nAll files must reside in one of these sub-directories: ${sides.map(kleur.yellow).join(", ")}`
|
||||||
|
)
|
||||||
|
|
||||||
|
if (dirents.some(dirent => !(sides as string[]).includes(dirent.name)))
|
||||||
|
output.failAndExit(`The ${kleur.yellow("overrides")} directory may only contain the following sub-directories:\n${sides.map(side => `- ${side}`).join("\n")}`)
|
||||||
|
},
|
||||||
async checkForUpdates(allowedReleaseChannels: ReleaseChannel[]): Promise<Update[]> {
|
async checkForUpdates(allowedReleaseChannels: ReleaseChannel[]): Promise<Update[]> {
|
||||||
const limit = pLimit(5)
|
const limit = pLimit(5)
|
||||||
|
|
||||||
|
|
|
@ -9,8 +9,7 @@ export class Path {
|
||||||
* Returns an absolute path by resolving the last segment against the other segments, this path and the current working directory.
|
* Returns an absolute path by resolving the last segment against the other segments, this path and the current working directory.
|
||||||
*/
|
*/
|
||||||
resolve(...segments: (string | Path)[]) {
|
resolve(...segments: (string | Path)[]) {
|
||||||
if (this.isAbsolute()) return this
|
return new Path(pathModule.resolve(this.value, ...segments.map(s => s.toString())))
|
||||||
else return new Path(pathModule.resolve(this.value, ...segments.map(s => s.toString())))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
export type ModLoader = "fabric" | "quilt"
|
export type ModLoader = "fabric" | "quilt"
|
||||||
export type ReleaseChannel = "alpha" | "beta" | "release"
|
export type ReleaseChannel = "alpha" | "beta" | "release"
|
||||||
export const releaseChannelOrder: ReleaseChannel[] = ["alpha", "beta", "release"]
|
export const releaseChannelOrder: ReleaseChannel[] = ["alpha", "beta", "release"]
|
||||||
|
|
||||||
|
export type Side = "client" | "server" | "client-server"
|
||||||
|
export const sides: [Side, ...Side[]] = ["client", "server", "client-server"]
|
||||||
|
|
6
src/types.d.ts
vendored
Normal file
6
src/types.d.ts
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
declare module "@root/walk" {
|
||||||
|
import { Dirent } from "fs"
|
||||||
|
export type Visitor = (error: Error, path: string, dirent: Dirent) => Promise<boolean | undefined>
|
||||||
|
|
||||||
|
export function walk(path: string, visitor: Visitor): Promise<void>
|
||||||
|
}
|
23
src/utils.ts
23
src/utils.ts
|
@ -1,9 +1,26 @@
|
||||||
import { InvalidArgumentError } from "commander"
|
import { InvalidArgumentError } from "commander"
|
||||||
import hash, { HashaInput } from "hasha"
|
import hash, { HashaInput } from "hasha"
|
||||||
import { zip as zipWithCallback } from "cross-zip"
|
import { Path } from "./path.js"
|
||||||
import { promisify } from "util"
|
import { ZipFile } from "yazl"
|
||||||
|
import { walk } from "@root/walk"
|
||||||
|
import fs from "fs-extra"
|
||||||
|
import { pEvent } from "p-event"
|
||||||
|
|
||||||
export const zip = promisify(zipWithCallback)
|
export async function zipDirectory(directoryPath: Path, outputFilePath: Path) {
|
||||||
|
const zipFile = new ZipFile()
|
||||||
|
zipFile.outputStream.pipe(fs.createWriteStream(outputFilePath.toString()))
|
||||||
|
|
||||||
|
await walk(directoryPath.toString(), async (error, path, dirent) => {
|
||||||
|
if (error) return
|
||||||
|
if (directoryPath.toString() === path) return true
|
||||||
|
if (dirent.name.startsWith(".")) return false
|
||||||
|
|
||||||
|
if (dirent.isFile()) zipFile.addFile(path, directoryPath.relative(Path.create(path)).toString(), { compress: true })
|
||||||
|
})
|
||||||
|
|
||||||
|
zipFile.end()
|
||||||
|
await pEvent(zipFile.outputStream, "close")
|
||||||
|
}
|
||||||
|
|
||||||
export const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))
|
export const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))
|
||||||
|
|
||||||
|
|
1
test-pack/.gitignore
vendored
Normal file
1
test-pack/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/generated/
|
|
@ -1,15 +1,17 @@
|
||||||
{
|
{
|
||||||
"name": "Charm",
|
"name": "Charm",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"ignoreUpdates": true,
|
"ignoreUpdates": false,
|
||||||
"side": "client+server",
|
"side": "client-server",
|
||||||
"file": {
|
"file": {
|
||||||
"version": "4.2.0+1.18.2",
|
"version": "4.2.0+1.18.2",
|
||||||
"name": "charm-fabric-1.18.2-4.2.0.jar",
|
"name": "charm-fabric-1.18.2-4.2.0.jar",
|
||||||
"size": 3413876,
|
"size": 3413876,
|
||||||
"downloadUrl": "https://cdn.modrinth.com/data/pOQTcQmj/versions/4.2.0+1.18.2/charm-fabric-1.18.2-4.2.0.jar",
|
"downloadUrl": "https://cdn.modrinth.com/data/pOQTcQmj/versions/4.2.0+1.18.2/charm-fabric-1.18.2-4.2.0.jar",
|
||||||
"hashAlgorithm": "sha512",
|
"hashes": {
|
||||||
"hash": "3c8cd08ab1e37dcbf0f5a956cd20d84c98e58ab49fdc13faafb9c2af4dbf7fba7c8328cb5365997fe4414cfc5cb554ed13b3056a22df1c6bd335594f380facb6"
|
"sha1": "ebb87cd7fa7935bc30e5ad0b379bb4ede8723a82",
|
||||||
|
"sha512": "3c8cd08ab1e37dcbf0f5a956cd20d84c98e58ab49fdc13faafb9c2af4dbf7fba7c8328cb5365997fe4414cfc5cb554ed13b3056a22df1c6bd335594f380facb6"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"source": {
|
"source": {
|
||||||
"type": "modrinth",
|
"type": "modrinth",
|
||||||
|
|
21
test-pack/mods/fabric-api.json
Normal file
21
test-pack/mods/fabric-api.json
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"name": "Fabric API",
|
||||||
|
"enabled": true,
|
||||||
|
"ignoreUpdates": false,
|
||||||
|
"side": "client-server",
|
||||||
|
"file": {
|
||||||
|
"version": "0.58.0+1.18.2",
|
||||||
|
"name": "fabric-api-0.58.0+1.18.2.jar",
|
||||||
|
"size": 1445029,
|
||||||
|
"downloadUrl": "https://cdn.modrinth.com/data/P7dR8mSH/versions/0.58.0+1.18.2/fabric-api-0.58.0%2B1.18.2.jar",
|
||||||
|
"hashes": {
|
||||||
|
"sha1": "b9ab9ab267f8cdff525f9a8edb26435d3e2455f6",
|
||||||
|
"sha512": "92317b8d48b20d1b370ab67e4954d1db4861b8fb561935edc0c0fc8a525fefbd3c159f3cfbf83ec3455e3179561fab554645138c6d79f5f597abea77dc1a03ed"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"source": {
|
||||||
|
"type": "modrinth",
|
||||||
|
"modId": "P7dR8mSH",
|
||||||
|
"versionId": "4XRtXhtL"
|
||||||
|
}
|
||||||
|
}
|
21
test-pack/mods/sodium.json
Normal file
21
test-pack/mods/sodium.json
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"name": "Sodium",
|
||||||
|
"enabled": true,
|
||||||
|
"ignoreUpdates": false,
|
||||||
|
"side": "client",
|
||||||
|
"file": {
|
||||||
|
"version": "mc1.18.2-0.4.1",
|
||||||
|
"name": "sodium-fabric-mc1.18.2-0.4.1+build.15.jar",
|
||||||
|
"size": 1318645,
|
||||||
|
"downloadUrl": "https://cdn.modrinth.com/data/AANobbMI/versions/mc1.18.2-0.4.1/sodium-fabric-mc1.18.2-0.4.1%2Bbuild.15.jar",
|
||||||
|
"hashes": {
|
||||||
|
"sha1": "f839863a6be7014b8d80058ea1f361521148d049",
|
||||||
|
"sha512": "86eb4db8fdb9f0bb06274c4f150b55273b5b770ffc89e0ba68011152a231b79ebe0b1adda0dd194f92cdcb386f7a60863d9fee5d15c1c3547ffa22a19083a1ee"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"source": {
|
||||||
|
"type": "modrinth",
|
||||||
|
"modId": "AANobbMI",
|
||||||
|
"versionId": "74Y5Z8fo"
|
||||||
|
}
|
||||||
|
}
|
1
test-pack/overrides/client/options.txt
Normal file
1
test-pack/overrides/client/options.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
option=1
|
Loading…
Add table
Reference in a new issue