initial commit

This commit is contained in:
2026-09-16 23:35:30 -05:00
commit 4c2a2fb47f
7 changed files with 2459 additions and 0 deletions
+147
View File
@@ -0,0 +1,147 @@
import {createWriteStream} from "node:fs";
import {copyFile, mkdir, readFile, readdir, rm, stat} from "node:fs/promises";
import path from "node:path";
import {fileURLToPath} from "node:url";
import archiver from "archiver";
import {buildPacks, verifyPacks} from "./compendium-packs.mjs";
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const DIST = path.join(ROOT, "dist");
const RELEASE_DIRECTORIES = ["assets", "lang", "packs", "scripts", "styles", "templates"];
const RELEASE_FILES = [
"module.json",
"README.md",
"PATCHNOTES.md",
"LICENSE",
"LICENSE.md",
"THIRD_PARTY_NOTICES.md"
];
async function exists(target) {
try {
await stat(target);
return true;
} catch (error) {
if (error.code === "ENOENT") return false;
throw error;
}
}
function requestedTag() {
const index = process.argv.indexOf("--tag");
if (index === -1) return process.env.GITHUB_REF_NAME;
if (!process.argv[index + 1]) throw new Error("--tag requires a value.");
return process.argv[index + 1];
}
function validateManifest(manifest, tag) {
const expectedTag = `v${manifest.version}`;
const expectedArchive = `${manifest.id}.zip`;
const expectedDownload = `${manifest.url.replace(/\/$/, "")}/releases/download/${expectedTag}/${expectedArchive}`;
if (tag && tag !== expectedTag) {
throw new Error(`Release tag ${tag} does not match module.json version ${manifest.version}.`);
}
if (manifest.download !== expectedDownload) {
throw new Error(`module.json download must be ${expectedDownload}`);
}
}
async function validateCompendiumAssets(manifest) {
const sourceRoot = path.join(ROOT, "packs-src");
const failures = [];
async function inspect(value, location) {
if (Array.isArray(value)) {
for (let index = 0; index < value.length; index += 1) {
await inspect(value[index], `${location}[${index}]`);
}
return;
}
if (value && typeof value === "object") {
for (const [key, child] of Object.entries(value)) await inspect(child, `${location}.${key}`);
return;
}
if (typeof value !== "string") return;
if (/^[A-Za-z]:[\\/]/.test(value) || /^worlds[\\/]/i.test(value)) {
failures.push(`${location}: package-external path ${value}`);
return;
}
const prefix = `modules/${manifest.id}/`;
if (!value.startsWith(prefix)) return;
const relative = value.slice(prefix.length).split(/[?#]/, 1)[0];
const target = path.resolve(ROOT, relative);
if (!target.startsWith(`${ROOT}${path.sep}`) || !(await exists(target))) {
failures.push(`${location}: missing module asset ${value}`);
}
}
async function visit(directory) {
for (const entry of await readdir(directory, {withFileTypes: true})) {
const absolute = path.join(directory, entry.name);
if (entry.isDirectory()) await visit(absolute);
else if (entry.isFile() && entry.name.endsWith(".json")) {
const relative = path.relative(ROOT, absolute).split(path.sep).join("/");
await inspect(JSON.parse(await readFile(absolute, "utf8")), relative);
}
}
}
await visit(sourceRoot);
if (failures.length) throw new Error(`Invalid compendium asset references:\n${failures.join("\n")}`);
}
async function createZip(archivePath) {
const output = createWriteStream(archivePath);
const archive = archiver("zip", {zlib: {level: 9}});
const completed = new Promise((resolve, reject) => {
output.on("close", resolve);
output.on("error", reject);
archive.on("error", reject);
});
archive.pipe(output);
for (const filename of RELEASE_FILES) {
const source = path.join(ROOT, filename);
if (await exists(source)) archive.file(source, {name: filename});
}
for (const directory of RELEASE_DIRECTORIES) {
const source = path.join(ROOT, directory);
if (!(await exists(source))) throw new Error(`Required release directory is missing: ${directory}`);
archive.directory(source, directory);
}
await archive.finalize();
await completed;
}
async function main() {
const manifest = JSON.parse(await readFile(path.join(ROOT, "module.json"), "utf8"));
validateManifest(manifest, requestedTag());
await validateCompendiumAssets(manifest);
await buildPacks();
await verifyPacks();
await rm(DIST, {force: true, recursive: true, maxRetries: 10});
await mkdir(DIST, {recursive: true});
const archivePath = path.join(DIST, `${manifest.id}.zip`);
await createZip(archivePath);
await copyFile(path.join(ROOT, "module.json"), path.join(DIST, "module.json"));
const {size} = await stat(archivePath);
console.log(`Created ${path.relative(ROOT, archivePath)} (${size} bytes).`);
console.log(`Created ${path.relative(ROOT, path.join(DIST, "module.json"))}.`);
}
main().catch(error => {
console.error(error);
process.exitCode = 1;
});