import AdmZip from "adm-zip"; import prettyBytes from "pretty-bytes"; import { parseArgs } from "node:util"; import { access, copyFile, cp, lstat, mkdir, mkdtemp, rm, stat, writeFile } from "node:fs/promises"; import type { PathLike } from "node:fs"; import { tmpdir } from "node:os"; import { cwd } from "node:process"; import { resolve } from "node:path"; interface Override { common?: string; } interface Info { description: string; exports: Array<["all"] | Array>; name: string; url: string; versions: Array<[mcVersion: string, packFormat: number]>; overrides?: Record; } const { values: { indir: inDir, common: commonDir, revision: rev, outdir: outDir, info: infoPath } } = parseArgs({ args: Bun.argv, options: { indir: { type: "string", short: "i", default: cwd() }, common: { type: "string", short: "c", default: "common" }, revision: { type: "string", short: "r" }, outdir: { type: "string", short: "o", default: "dist" }, info: { type: "string", default: "info.json" }, }, strict: true, allowPositionals: true }); const directoryExists = (path: PathLike) => access(path).then(() => lstat(path).then(d => d.isDirectory())).catch(() => false); const dir = resolve(inDir!); console.log("Building packs for:", dir); if (!dir) { console.log("No directory specified"); process.exit(1); } if (!await directoryExists(dir)) { console.log(`Specified input directory "${inDir}" (${dir}) does not exist.`); process.exit(1); } const info = await Bun.file(infoPath ?? "info.json").json() as Info; const common = resolve(dir, commonDir!); const dist = resolve(dir, outDir!); if (!await directoryExists(common)) { console.log(`Specified common directory "${commonDir}" (${common}) does not exist.`); process.exit(1); } let revision = rev; if (!revision) { if (await directoryExists(`${dir}/.git`)) { revision = (await Bun.$`git rev-parse --short HEAD`.cwd(dir).text().catch(() => "")).slice(0, -1); if (!revision) { console.log("Failed to get git revision."); process.exit(1); } } else { console.log("No revision specified and no git directory found."); process.exit(1); } } const tempDir = await mkdtemp(`${tmpdir()}/${info.name.toLowerCase().replaceAll(" ", "-")}`); console.log("Temporary Directory:", tempDir); let totalSize = 0; async function createVersion(mcVersion: string, packFormat: number) { let commonDir = common; if (info.overrides) { const override = info.overrides[mcVersion]; if (override?.common) { commonDir = resolve(dir, override.common); if (!await directoryExists(commonDir)) { console.log(`[Override:${mcVersion}] Specified common directory "${override.common}" (${commonDir}) does not exist.`); process.exit(1); } } } console.log("Building %s (pf: %d)", mcVersion, packFormat); const mcmeta = { pack: { pack_format: packFormat, description: info.description, version: { git: revision, mc: mcVersion, url: info.url } } }; await mkdir(`${tempDir}/${mcVersion}`); await writeFile(`${tempDir}/${mcVersion}/pack.mcmeta`, JSON.stringify(mcmeta, null, 4)); await copyFile(`${commonDir}/pack.png`, `${tempDir}/${mcVersion}/pack.png`); await cp(`${commonDir}/assets`, `${tempDir}/${mcVersion}/assets`, { recursive: true }); const zip = new AdmZip(); await zip.addLocalFolderPromise(`${tempDir}/${mcVersion}`, {}); await zip.writeZipPromise(`${dist}/${info.name.replaceAll(" ", "")}-${mcVersion}.zip`); const { size } = await stat(`${dist}/${info.name.replaceAll(" ", "")}-${mcVersion}.zip`); totalSize += size; console.log("%s built, size: %s", mcVersion, prettyBytes(size)); } if (await directoryExists(dist)) { await rm(dist, { recursive: true, force: true }); } await mkdir(dist); for (const [mcVersion, packFormat] of info.versions) { await createVersion(mcVersion, packFormat); } await rm(tempDir, { recursive: true, force: true }); console.log("Total size: %s", prettyBytes(totalSize)); for (const ex of info.exports) { const name = (ex[0] === "all" ? info.name : `${info.name}-${ex.join("-")}`).replaceAll(" ", ""); console.log("Exporting %s", name); const zip = new AdmZip(); const versions = ex[0] === "all" ? info.versions.map(([mcVersion]) => mcVersion) : ex; for (const version of versions) { zip.addLocalFile(`${dist}/${info.name.replaceAll(" ", "")}-${version}.zip`); } await zip.writeZipPromise(`${dist}/${name}.zip`); const { size } = await stat(`${dist}/${name}.zip`); console.log("%s exported, size: %s", name, prettyBytes(size)); } console.log("Done");