Implement overrides for Modrinth export

This commit is contained in:
Moritz Ruth 2022-08-16 15:11:55 +02:00
parent 862048d169
commit 98007b36a0
18 changed files with 313 additions and 81 deletions

View file

@ -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
View file

@ -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'}

View file

@ -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) {

View file

@ -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}

View file

@ -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 })
}

View file

@ -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)
})

View file

@ -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")) {

View file

@ -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()
}
},

View file

@ -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)

View file

@ -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())))
}
/**

View file

@ -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
View 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>
}

View file

@ -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
View file

@ -0,0 +1 @@
/generated/

View file

@ -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",

View 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"
}
}

View 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"
}
}

View file

@ -0,0 +1 @@
option=1