import fs from "node:fs" import path from "node:path" import crypto from "node:crypto" import { Level } from "level" export const CONTENT_PACK_DEFINITIONS = [ { sourceFile: "armes.json", outputFolder: "armes", type: "Item" }, { sourceFile: "armures.json", outputFolder: "armures", type: "Item" }, { sourceFile: "equipements.json", outputFolder: "equipements", type: "Item" }, { sourceFile: "pouvoirs-compagnie.json", outputFolder: "pouvoirs-compagnie", type: "Item" }, { sourceFile: "competences.json", outputFolder: "competences", type: "Item" }, { sourceFile: "races.json", outputFolder: "races", type: "Item" }, { sourceFile: "tribus.json", outputFolder: "tribus", type: "Item" }, { sourceFile: "metiers.json", outputFolder: "metiers", type: "Item" }, { sourceFile: "sortileges.json", outputFolder: "sortileges", type: "Item" }, ] export const SYSTEM_PACK_DEFINITIONS = [ ...CONTENT_PACK_DEFINITIONS, { sourceFile: "aide-systeme.json", outputFolder: "aide-systeme", type: "JournalEntry" }, ] function slugId(input) { const hash = crypto.createHash("sha256").update(input).digest() const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" let id = "" for (let index = 0; id.length < 16; index += 1) { id += alphabet[hash[index % hash.length] % alphabet.length] } return id } function createDocumentStats({ documentSystemId, documentSystemVersion, coreVersion, createdTime, lastModifiedBy = "Copilot", } = {}) { return { systemId: documentSystemId, systemVersion: documentSystemVersion, coreVersion, createdTime, modifiedTime: createdTime, lastModifiedBy, compendiumSource: null, duplicateSource: null, exportSource: null, } } function toItemPackDocument(entry, index, options = {}) { const docId = slugId(`${entry.type}:${entry.name}`) return { key: `!items!${docId}`, value: { name: entry.name, type: entry.type, img: entry.img ?? "icons/svg/item-bag.svg", system: entry.system ?? {}, effects: Array.isArray(entry.effects) ? entry.effects : [], flags: entry.flags ?? {}, _stats: createDocumentStats(options), _id: docId, folder: null, sort: index * 1000, ownership: { default: 0, }, }, embedded: [], } } function toJournalPageDocument(entry, journalId, page, pageIndex, options = {}) { const pageId = slugId(`JournalEntryPage:${entry.name}:${page.name ?? page.title ?? pageIndex}`) return { _id: pageId, name: page.name ?? `Page ${pageIndex + 1}`, type: page.type ?? "text", title: { show: page.title?.show ?? true, level: page.title?.level ?? 1, }, text: { format: page.text?.format ?? 1, content: page.text?.content ?? "", }, system: page.system ?? {}, image: page.image ?? {}, video: page.video ?? { controls: true, volume: 0.5 }, src: page.src ?? null, category: page.category ?? null, sort: page.sort ?? pageIndex * 1000, ownership: page.ownership ?? { default: -1 }, flags: page.flags ?? {}, _stats: createDocumentStats(options), _key: `!journal.pages!${journalId}.${pageId}`, } } function toJournalPackDocument(entry, index, options = {}) { const docId = slugId(`JournalEntry:${entry.name}`) const pages = Array.isArray(entry.pages) ? entry.pages.map((page, pageIndex) => toJournalPageDocument(entry, docId, page, pageIndex, options)) : [] return { key: `!journal!${docId}`, value: { name: entry.name, pages: pages.map((page) => page._id), ownership: entry.ownership ?? { default: 0 }, flags: entry.flags ?? { core: {} }, _stats: createDocumentStats(options), folder: null, sort: entry.sort ?? index * 1000, _id: docId, categories: Array.isArray(entry.categories) ? entry.categories : [], }, embedded: pages.map((page) => ({ key: page._key, value: { name: page.name, type: page.type, title: page.title, text: page.text, system: page.system, image: page.image, video: page.video, src: page.src, category: page.category, sort: page.sort, ownership: page.ownership, flags: page.flags, _stats: page._stats, _id: page._id, }, })), } } function toPackDocument(entry, index, options = {}) { if (entry.type === "JournalEntry") return toJournalPackDocument(entry, index, options) return toItemPackDocument(entry, index, options) } async function buildPack({ sourcePath, outputPath, type, documentSystemId, documentSystemVersion, coreVersion, createdTime, lastModifiedBy, }) { const source = JSON.parse(fs.readFileSync(sourcePath, "utf8")) if (!Array.isArray(source)) { throw new Error(`Pack source must be an array: ${sourcePath}`) } fs.rmSync(outputPath, { recursive: true, force: true }) fs.mkdirSync(outputPath, { recursive: true }) const db = new Level(outputPath, { valueEncoding: "utf8" }) try { await db.open() const batch = db.batch() source.forEach((entry, index) => { if (!entry.type) { throw new Error(`Missing document type in ${sourcePath}: ${entry.name}`) } const doc = toPackDocument(entry, index, { documentSystemId, documentSystemVersion, coreVersion, createdTime, lastModifiedBy, }) batch.put(doc.key, JSON.stringify(doc.value)) for (const embedded of doc.embedded) { batch.put(embedded.key, JSON.stringify(embedded.value)) } }) await batch.write() } finally { await db.close() } } export async function buildPacks({ sourceRoot, outputRoot, packDefinitions = SYSTEM_PACK_DEFINITIONS, documentSystemId, documentSystemVersion, coreVersion, createdTime = Date.now(), lastModifiedBy = "Copilot", }) { for (const pack of packDefinitions) { await buildPack({ sourcePath: path.join(sourceRoot, pack.sourceFile), outputPath: path.join(outputRoot, pack.outputFolder), type: pack.type, documentSystemId, documentSystemVersion, coreVersion, createdTime, lastModifiedBy, }) } }