// Exports this packwiz pack as a Modrinth modpack (.mrpack) for Modrinth App / Prism "Import". // node tools/export-mrpack.mjs → dist/CreateRP-.mrpack // Mods are referenced by their Modrinth CDN URL (sha1/sha512/size from the Modrinth API); // config/ goes into overrides/. Zipping uses Windows' bsdtar (tar -a) or `zip` on Linux. import fs from 'fs'; import path from 'path'; import { execFileSync } from 'child_process'; import { fileURLToPath } from 'url'; const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); const UA = { 'User-Agent': 'Pinecraft-CreateRP/mrpack-export' }; const field = (toml, key) => (toml.match(new RegExp(`^${key}\\s*=\\s*"([^"]*)"`, 'm')) || [])[1]; const pack = fs.readFileSync(path.join(root, 'pack.toml'), 'utf8'); const packName = field(pack, 'name'); const packVersion = field(pack, 'version'); const mc = field(pack, 'minecraft'); const neoforge = field(pack, 'neoforge'); const metas = fs.readdirSync(path.join(root, 'mods')).filter(f => f.endsWith('.pw.toml')).map(f => { const t = fs.readFileSync(path.join(root, 'mods', f), 'utf8'); return { file: f, filename: field(t, 'filename'), side: field(t, 'side') || 'both', url: field(t, 'url'), versionId: field(t, 'version') }; }); // sha1/sha512/size per version from Modrinth (batched) const versionInfo = {}; const ids = metas.map(m => m.versionId).filter(Boolean); for (let i = 0; i < ids.length; i += 100) { const chunk = ids.slice(i, i + 100); const res = await fetch('https://api.modrinth.com/v2/versions?ids=' + encodeURIComponent(JSON.stringify(chunk)), { headers: UA }); for (const v of await res.json()) versionInfo[v.id] = v; } const files = metas.map(m => { const v = versionInfo[m.versionId]; const f = v?.files.find(x => x.url === m.url) || v?.files.find(x => x.filename === m.filename); if (!f) throw new Error(`no Modrinth file for ${m.file}`); return { path: `mods/${f.filename}`, hashes: { sha1: f.hashes.sha1, sha512: f.hashes.sha512 }, env: { client: m.side === 'server' ? 'unsupported' : 'required', server: m.side === 'client' ? 'unsupported' : 'required' }, downloads: [f.url], fileSize: f.size }; }).sort((a, b) => a.path.localeCompare(b.path)); const index = { formatVersion: 1, game: 'minecraft', versionId: packVersion, name: packName, summary: 'Клієнтська збірка сервера Create RP', files, dependencies: { minecraft: mc, neoforge } }; // staging dir → zip const stage = path.join(root, 'dist', '.stage'); fs.rmSync(stage, { recursive: true, force: true }); fs.mkdirSync(path.join(stage, 'overrides'), { recursive: true }); fs.writeFileSync(path.join(stage, 'modrinth.index.json'), JSON.stringify(index, null, 2)); fs.cpSync(path.join(root, 'config'), path.join(stage, 'overrides', 'config'), { recursive: true }); const out = path.join(root, 'dist', `CreateRP-${packVersion}.mrpack`); fs.rmSync(out, { force: true }); if (process.platform === 'win32') { execFileSync('C:\\Windows\\System32\\tar.exe', ['--format', 'zip', '-c', '-f', out, '-C', stage, 'modrinth.index.json', 'overrides']); } else { execFileSync('zip', ['-qr', out, 'modrinth.index.json', 'overrides'], { cwd: stage }); } fs.rmSync(stage, { recursive: true, force: true }); console.log(`${out}: ${files.length} mods, ${(fs.statSync(out).size / 1024).toFixed(0)} KiB`);