79 lines
3.2 KiB
JavaScript
79 lines
3.2 KiB
JavaScript
#!/usr/bin/env node
|
|
import { access, copyFile, mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
|
const shippingRoot = path.join(projectRoot, "public", "assets", "game", "dungeons");
|
|
const sharedRoot = path.join(projectRoot, "public", "assets", "game", "creatures", "shared");
|
|
const registryFile = path.join(projectRoot, "src", "game", "generated", "fivePlayerDungeonImports.json");
|
|
|
|
const exists = async (file) => access(file).then(() => true, () => false);
|
|
const readJson = async (file) => JSON.parse(await readFile(file, "utf8"));
|
|
const writeJson = (file, value) => writeFile(file, `${JSON.stringify(value, null, 2)}\n`);
|
|
|
|
const records = [];
|
|
for (const entry of await readdir(shippingRoot, { withFileTypes: true })) {
|
|
if (!entry.isDirectory()) continue;
|
|
const manifestFile = path.join(shippingRoot, entry.name, "dungeon-pack.json");
|
|
if (!await exists(manifestFile)) continue;
|
|
records.push({ manifestFile, manifest: await readJson(manifestFile) });
|
|
}
|
|
|
|
const referencesByChecksum = new Map();
|
|
for (const { manifest } of records) {
|
|
for (const model of Object.values(manifest.creatureModels ?? {})) {
|
|
if (!model.checksum || !model.url) continue;
|
|
const references = referencesByChecksum.get(model.checksum) ?? [];
|
|
references.push(model);
|
|
referencesByChecksum.set(model.checksum, references);
|
|
}
|
|
}
|
|
|
|
await mkdir(sharedRoot, { recursive: true });
|
|
let duplicateGroups = 0;
|
|
let relinkedModels = 0;
|
|
let createdSharedFiles = 0;
|
|
|
|
for (const [checksum, references] of referencesByChecksum) {
|
|
const urls = [...new Set(references.map((model) => model.url))];
|
|
if (urls.length <= 1) continue;
|
|
const sharedUrl = `/assets/game/creatures/shared/${checksum}.glb`;
|
|
const sharedFile = path.join(projectRoot, "public", sharedUrl.replace(/^\/+/, ""));
|
|
const preferredUrl = urls.find((url) => url === sharedUrl) ?? urls[0];
|
|
const sourceFile = path.join(projectRoot, "public", preferredUrl.replace(/^\/+/, ""));
|
|
if (!await exists(sourceFile)) throw new Error(`Missing shared-creature source: ${preferredUrl}`);
|
|
const sourceDetails = await stat(sourceFile);
|
|
const expectedSize = Number(references[0].size ?? references[0].byteLength);
|
|
if (Number.isFinite(expectedSize) && sourceDetails.size !== expectedSize) {
|
|
throw new Error(`Size mismatch for shared-creature source: ${preferredUrl}`);
|
|
}
|
|
if (!await exists(sharedFile)) {
|
|
await copyFile(sourceFile, sharedFile);
|
|
createdSharedFiles += 1;
|
|
}
|
|
for (const model of references) {
|
|
model.url = sharedUrl;
|
|
relinkedModels += 1;
|
|
}
|
|
duplicateGroups += 1;
|
|
}
|
|
|
|
for (const { manifestFile, manifest } of records) await writeJson(manifestFile, manifest);
|
|
const imports = records.map(({ manifest }) => manifest).sort((left, right) => (
|
|
left.slug.localeCompare(right.slug)
|
|
));
|
|
await writeJson(registryFile, {
|
|
schemaVersion: 1,
|
|
generatedAt: "deterministic",
|
|
imports,
|
|
});
|
|
|
|
console.log(JSON.stringify({
|
|
status: "green",
|
|
duplicateGroups,
|
|
relinkedModels,
|
|
createdSharedFiles,
|
|
sharedRoot: path.relative(projectRoot, sharedRoot).replaceAll("\\", "/"),
|
|
}, null, 2));
|