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"
|
||||
},
|
||||
"dependencies": {
|
||||
"@root/walk": "^1.1.0",
|
||||
"commander": "^9.4.0",
|
||||
"cross-zip": "^4.0.0",
|
||||
"dedent": "^0.7.0",
|
||||
"env-paths": "^3.0.0",
|
||||
"figures": "^5.0.0",
|
||||
|
@ -37,20 +37,22 @@
|
|||
"loud-rejection": "^2.2.0",
|
||||
"nanoid": "^4.0.0",
|
||||
"ora": "^6.1.2",
|
||||
"p-event": "^5.0.1",
|
||||
"p-limit": "^4.0.0",
|
||||
"s-ago": "^2.2.0",
|
||||
"semver": "^7.3.7",
|
||||
"wrap-ansi": "^8.0.1",
|
||||
"yazl": "^2.5.1",
|
||||
"zod": "^3.18.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/cross-zip": "^4.0.0",
|
||||
"@types/dedent": "^0.7.0",
|
||||
"@types/fs-extra": "^9.0.13",
|
||||
"@types/lodash-es": "^4.17.6",
|
||||
"@types/node": "^18.7.3",
|
||||
"@types/semver": "^7.3.12",
|
||||
"@types/wrap-ansi": "^8.0.1",
|
||||
"@types/yazl": "^2.4.2",
|
||||
"tsx": "^3.8.2",
|
||||
"type-fest": "^2.18.0",
|
||||
"typescript": "^4.7.4"
|
||||
|
|
53
pnpm-lock.yaml
generated
53
pnpm-lock.yaml
generated
|
@ -1,15 +1,15 @@
|
|||
lockfileVersion: 5.4
|
||||
|
||||
specifiers:
|
||||
'@types/cross-zip': ^4.0.0
|
||||
'@root/walk': ^1.1.0
|
||||
'@types/dedent': ^0.7.0
|
||||
'@types/fs-extra': ^9.0.13
|
||||
'@types/lodash-es': ^4.17.6
|
||||
'@types/node': ^18.7.3
|
||||
'@types/semver': ^7.3.12
|
||||
'@types/wrap-ansi': ^8.0.1
|
||||
'@types/yazl': ^2.4.2
|
||||
commander: ^9.4.0
|
||||
cross-zip: ^4.0.0
|
||||
dedent: ^0.7.0
|
||||
env-paths: ^3.0.0
|
||||
figures: ^5.0.0
|
||||
|
@ -23,6 +23,7 @@ specifiers:
|
|||
loud-rejection: ^2.2.0
|
||||
nanoid: ^4.0.0
|
||||
ora: ^6.1.2
|
||||
p-event: ^5.0.1
|
||||
p-limit: ^4.0.0
|
||||
s-ago: ^2.2.0
|
||||
semver: ^7.3.7
|
||||
|
@ -30,11 +31,12 @@ specifiers:
|
|||
type-fest: ^2.18.0
|
||||
typescript: ^4.7.4
|
||||
wrap-ansi: ^8.0.1
|
||||
yazl: ^2.5.1
|
||||
zod: ^3.18.0
|
||||
|
||||
dependencies:
|
||||
'@root/walk': 1.1.0
|
||||
commander: 9.4.0
|
||||
cross-zip: 4.0.0
|
||||
dedent: 0.7.0
|
||||
env-paths: 3.0.0
|
||||
figures: 5.0.0
|
||||
|
@ -48,20 +50,22 @@ dependencies:
|
|||
loud-rejection: 2.2.0
|
||||
nanoid: 4.0.0
|
||||
ora: 6.1.2
|
||||
p-event: 5.0.1
|
||||
p-limit: 4.0.0
|
||||
s-ago: 2.2.0
|
||||
semver: 7.3.7
|
||||
wrap-ansi: 8.0.1
|
||||
yazl: 2.5.1
|
||||
zod: 3.18.0
|
||||
|
||||
devDependencies:
|
||||
'@types/cross-zip': 4.0.0
|
||||
'@types/dedent': 0.7.0
|
||||
'@types/fs-extra': 9.0.13
|
||||
'@types/lodash-es': 4.17.6
|
||||
'@types/node': 18.7.3
|
||||
'@types/semver': 7.3.12
|
||||
'@types/wrap-ansi': 8.0.1
|
||||
'@types/yazl': 2.4.2
|
||||
tsx: 3.8.2
|
||||
type-fest: 2.18.0
|
||||
typescript: 4.7.4
|
||||
|
@ -98,6 +102,10 @@ packages:
|
|||
dev: true
|
||||
optional: true
|
||||
|
||||
/@root/walk/1.1.0:
|
||||
resolution: {integrity: sha512-FfXPAta9u2dBuaXhPRawBcijNC9rmKVApmbi6lIZyg36VR/7L02ytxoY5K/14PJlHqiBUoYII73cTlekdKTUOw==}
|
||||
dev: false
|
||||
|
||||
/@sindresorhus/is/5.3.0:
|
||||
resolution: {integrity: sha512-CX6t4SYQ37lzxicAqsBtxA3OseeoVrh9cSJ5PFYam0GksYlupRfy1A+Q4aYD3zvcfECLc0zO2u+ZnR2UYKvCrw==}
|
||||
engines: {node: '>=14.16'}
|
||||
|
@ -119,10 +127,6 @@ packages:
|
|||
'@types/responselike': 1.0.0
|
||||
dev: false
|
||||
|
||||
/@types/cross-zip/4.0.0:
|
||||
resolution: {integrity: sha512-jZRaaM3aib7qgVhCWeo76vVNJW+8+ujJzP/JHPPA4WUp5YBNMsqiyu5uYWg2eS05+jgSfb5NN8eqrAG72Ab2kA==}
|
||||
dev: true
|
||||
|
||||
/@types/dedent/0.7.0:
|
||||
resolution: {integrity: sha512-EGlKlgMhnLt/cM4DbUSafFdrkeJoC9Mvnj0PUCU7tFmTjMjNRT957kXCx0wYm3JuEq4o4ZsS5vG+NlkM2DMd2A==}
|
||||
dev: true
|
||||
|
@ -174,6 +178,12 @@ packages:
|
|||
resolution: {integrity: sha512-cjwgM6WWy9YakrQ36Pq0vg5XoNblVEaNq+/pHngKl4GyyDIxTeskPoG+tp4LsRk0lHrA4LaLJqlvYridi7mzlw==}
|
||||
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:
|
||||
resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==}
|
||||
engines: {node: '>=12'}
|
||||
|
@ -201,6 +211,10 @@ packages:
|
|||
readable-stream: 3.6.0
|
||||
dev: false
|
||||
|
||||
/buffer-crc32/0.2.13:
|
||||
resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==}
|
||||
dev: false
|
||||
|
||||
/buffer-from/1.1.2:
|
||||
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
|
||||
dev: true
|
||||
|
@ -271,11 +285,6 @@ packages:
|
|||
json-buffer: 3.0.1
|
||||
dev: false
|
||||
|
||||
/cross-zip/4.0.0:
|
||||
resolution: {integrity: sha512-MEzGfZo0rqE10O/B+AEcCSJLZsrWuRUvmqJTqHNqBtALhaJc3E3ixLGLJNTRzEA2K34wbmOHC4fwYs9sVsdcCA==}
|
||||
engines: {node: '>=12.10'}
|
||||
dev: false
|
||||
|
||||
/currently-unhandled/0.4.1:
|
||||
resolution: {integrity: sha512-/fITjgjGU50vjQ4FH6eUoYu+iUoUKIXws2hL15JJpIR+BbTxaXQsMuuyjtNh2WqsSBS5nsaZHFsFecyw5CCAng==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
@ -825,6 +834,13 @@ packages:
|
|||
engines: {node: '>=12.20'}
|
||||
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:
|
||||
resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
|
@ -839,6 +855,11 @@ packages:
|
|||
p-limit: 4.0.0
|
||||
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:
|
||||
resolution: {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
|
@ -1005,6 +1026,12 @@ packages:
|
|||
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
|
||||
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:
|
||||
resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==}
|
||||
engines: {node: '>=12.20'}
|
||||
|
|
|
@ -2,7 +2,7 @@ import { Command } from "commander"
|
|||
import { take } from "lodash-es"
|
||||
import { usePack } from "../pack.js"
|
||||
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 figures from "figures"
|
||||
import {
|
||||
|
@ -267,14 +267,18 @@ modrinthVersionCommand.command("activate <id>")
|
|||
})
|
||||
|
||||
modrinthCommand.command("export")
|
||||
.description("Generate a Modrinth pack file suitable for uploading.")
|
||||
.option("-z, --no-zip", "Skip the creation of a zipped .mrpack file.")
|
||||
.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 loader = output.startLoading("Generating")
|
||||
|
||||
const outputDirectory = pack.rootDirectoryPath.resolve("modrinth-pack")
|
||||
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())
|
||||
|
||||
|
@ -295,31 +299,47 @@ modrinthCommand.command("export")
|
|||
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"
|
||||
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) {
|
||||
loader.setText(`Creating ${kleur.yellow(".mrpack")} file`)
|
||||
await zip(outputDirectory.toString(), pack.rootDirectoryPath.resolve("pack.mrpack").toString())
|
||||
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) {
|
||||
loader.setText("Removing the output directory")
|
||||
await fs.remove(outputDirectory.toString())
|
||||
output.println(kleur.green(`Removed the ${kleur.yellow("modrinth-pack")} directory`))
|
||||
}
|
||||
|
||||
loader.stop()
|
||||
})
|
||||
|
||||
async function handleActivate(modrinthMod: ModrinthMod, modrinthVersion: ModrinthVersion, force: boolean) {
|
||||
|
|
|
@ -14,8 +14,14 @@ interface IndexedFile {
|
|||
isMeta: boolean
|
||||
}
|
||||
|
||||
packwizCommand.command("import")
|
||||
.description("Import a packwiz pack.")
|
||||
.action(async () => {
|
||||
// TODO: Import packwiz pack
|
||||
})
|
||||
|
||||
packwizCommand.command("export")
|
||||
.description("Generates a packwiz pack in the packwiz directory")
|
||||
.description("Generate a packwiz pack in the packwiz directory.")
|
||||
.action(async () => {
|
||||
const pack = await usePack()
|
||||
|
||||
|
@ -24,7 +30,7 @@ packwizCommand.command("export")
|
|||
|
||||
const loader = output.startLoading("Generating")
|
||||
|
||||
const outputDirectoryPath = pack.rootDirectoryPath.resolve("packwiz")
|
||||
const outputDirectoryPath = pack.paths.generated.resolve("packwiz")
|
||||
const modsDirectoryPath = outputDirectoryPath.resolve("mods")
|
||||
await fs.remove(outputDirectoryPath.toString())
|
||||
await fs.mkdirp(modsDirectoryPath.toString())
|
||||
|
@ -88,7 +94,7 @@ packwizCommand.command("export")
|
|||
`)
|
||||
|
||||
loader.stop()
|
||||
output.println(kleur.green("Successfully generated packwiz pack."))
|
||||
output.println(kleur.green("Generated packwiz pack"))
|
||||
})
|
||||
|
||||
export { packwizCommand}
|
||||
|
|
10
src/files.ts
10
src/files.ts
|
@ -6,6 +6,8 @@ import { dirname } from "path"
|
|||
import { findUp } from "find-up"
|
||||
import { output } from "./output.js"
|
||||
import { Path } from "./path.js"
|
||||
import { Dirent } from "fs"
|
||||
import { sides } from "./shared.js"
|
||||
|
||||
export async function findPackDirectoryPath(): Promise<Path> {
|
||||
if (process.argv0.endsWith("/node")) { // run using pnpm
|
||||
|
@ -99,7 +101,7 @@ const modFileSchema = z.object({
|
|||
name: z.string(),
|
||||
enabled: z.boolean().default(true),
|
||||
ignoreUpdates: z.boolean().default(false),
|
||||
side: z.enum(["client", "server", "client+server"]),
|
||||
side: z.enum(sides),
|
||||
comment: z.string().optional(),
|
||||
file: modFileDataSchema,
|
||||
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))
|
||||
}
|
||||
|
||||
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 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.")}`)
|
||||
})
|
||||
|
@ -81,15 +81,20 @@ program.command("update [code]")
|
|||
}
|
||||
})
|
||||
|
||||
loudRejection()
|
||||
loudRejection(stack => {
|
||||
output.failAndExit(stack)
|
||||
})
|
||||
|
||||
program
|
||||
.addCommand(packwizCommand)
|
||||
.addCommand(modrinthCommand)
|
||||
.addHelpText("afterAll", "\n" + dedent`
|
||||
.addHelpText("after", "\n" + dedent`
|
||||
${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 ID of a Modrinth Project, prefixed with ${kleur.yellow("mr:")}
|
||||
- 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 { KeyvFile } from "keyv-file"
|
||||
import { delay } from "../utils.js"
|
||||
|
@ -15,8 +15,6 @@ const keyvCache = new KeyvFile({
|
|||
decode: JSON.parse
|
||||
})
|
||||
|
||||
const got = originalGot.extend()
|
||||
|
||||
async function getModrinthApiOptional(url: string): Promise<any | null> {
|
||||
let response: Response
|
||||
|
||||
|
@ -28,7 +26,30 @@ async function getModrinthApiOptional(url: string): Promise<any | null> {
|
|||
},
|
||||
cache: keyvCache,
|
||||
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")) {
|
||||
|
|
|
@ -23,6 +23,40 @@ export interface InternalLoader extends Loader {
|
|||
}
|
||||
|
||||
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 {
|
||||
const loader: InternalLoader = {
|
||||
isActive: false,
|
||||
|
@ -30,7 +64,8 @@ export const output = {
|
|||
text,
|
||||
spinner: ora({
|
||||
spinner: "dots4",
|
||||
color: "blue"
|
||||
color: "blue",
|
||||
prefixText: "\n"
|
||||
}),
|
||||
fail(message?: string) {
|
||||
if (this.state !== "running") throw new Error("state is not 'running'")
|
||||
|
@ -45,7 +80,16 @@ export const output = {
|
|||
}
|
||||
},
|
||||
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)
|
||||
},
|
||||
setText(text: string) {
|
||||
|
@ -91,7 +135,7 @@ export const output = {
|
|||
process.stdout.write(text)
|
||||
} else {
|
||||
loader.deactivate()
|
||||
process.stdout.write(text + "\n" + "\n")
|
||||
process.stdout.write(text)
|
||||
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 pLimit from "p-limit"
|
||||
import kleur from "kleur"
|
||||
import { modrinthApi } from "./modrinth/api.js"
|
||||
import semver from "semver"
|
||||
import { Path } from "./path.js"
|
||||
import { ReleaseChannel } from "./shared.js"
|
||||
import { ReleaseChannel, Side, sides } from "./shared.js"
|
||||
import { getModFileDataForModrinthVersion, sortModrinthVersionsByPreference } from "./modrinth/utils.js"
|
||||
|
||||
export interface Update {
|
||||
|
@ -16,13 +16,18 @@ export interface Update {
|
|||
}
|
||||
|
||||
export interface Pack {
|
||||
rootDirectoryPath: Path
|
||||
paths: {
|
||||
root: Path,
|
||||
generated: Path,
|
||||
overrides: Record<Side, Path>
|
||||
},
|
||||
horizrFile: HorizrFile
|
||||
mods: Mod[]
|
||||
|
||||
addMod(id: string, file: ModFile): Promise<void>
|
||||
findModByCode(code: string): Mod | null
|
||||
findModByCodeOrFail(code: string): Mod
|
||||
validateOverridesDirectories(): Promise<void>
|
||||
|
||||
checkForUpdates(allowedReleaseChannels: ReleaseChannel[]): Promise<Update[]>
|
||||
}
|
||||
|
@ -41,9 +46,18 @@ let pack: Pack
|
|||
export async function usePack(): Promise<Pack> {
|
||||
if (pack === undefined) {
|
||||
const rootDirectoryPath = await findPackDirectoryPath()
|
||||
const overridesDirectoryPath = rootDirectoryPath.resolve("overrides")
|
||||
|
||||
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),
|
||||
mods: await Promise.all((await readModIds(rootDirectoryPath)).map(async id => {
|
||||
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.")
|
||||
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[]> {
|
||||
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.
|
||||
*/
|
||||
resolve(...segments: (string | Path)[]) {
|
||||
if (this.isAbsolute()) return this
|
||||
else return new Path(pathModule.resolve(this.value, ...segments.map(s => s.toString())))
|
||||
return new Path(pathModule.resolve(this.value, ...segments.map(s => s.toString())))
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
export type ModLoader = "fabric" | "quilt"
|
||||
export type 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 hash, { HashaInput } from "hasha"
|
||||
import { zip as zipWithCallback } from "cross-zip"
|
||||
import { promisify } from "util"
|
||||
import { Path } from "./path.js"
|
||||
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))
|
||||
|
||||
|
|
1
test-pack/.gitignore
vendored
Normal file
1
test-pack/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/generated/
|
|
@ -1,15 +1,17 @@
|
|||
{
|
||||
"name": "Charm",
|
||||
"enabled": true,
|
||||
"ignoreUpdates": true,
|
||||
"side": "client+server",
|
||||
"ignoreUpdates": false,
|
||||
"side": "client-server",
|
||||
"file": {
|
||||
"version": "4.2.0+1.18.2",
|
||||
"name": "charm-fabric-1.18.2-4.2.0.jar",
|
||||
"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",
|
||||
"hashAlgorithm": "sha512",
|
||||
"hash": "3c8cd08ab1e37dcbf0f5a956cd20d84c98e58ab49fdc13faafb9c2af4dbf7fba7c8328cb5365997fe4414cfc5cb554ed13b3056a22df1c6bd335594f380facb6"
|
||||
"hashes": {
|
||||
"sha1": "ebb87cd7fa7935bc30e5ad0b379bb4ede8723a82",
|
||||
"sha512": "3c8cd08ab1e37dcbf0f5a956cd20d84c98e58ab49fdc13faafb9c2af4dbf7fba7c8328cb5365997fe4414cfc5cb554ed13b3056a22df1c6bd335594f380facb6"
|
||||
}
|
||||
},
|
||||
"source": {
|
||||
"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