Files
fvtt-les-oublies/scripts/pack-builder.mjs
T
2026-05-06 20:26:31 +02:00

218 lines
6.1 KiB
JavaScript

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