92 lines
4.1 KiB
JavaScript
92 lines
4.1 KiB
JavaScript
import { readdir, readFile, stat } from "node:fs/promises";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import contentPolicy from "../../shared/content-policy.json" with { type: "json" };
|
|
|
|
const SCRIPT_PATH = fileURLToPath(import.meta.url);
|
|
const PROHIBITED_MODULE_PATTERNS = [
|
|
/(?:^|\/)src\/ArenaRuntime\.tsx$/,
|
|
/(?:^|\/)src\/app\/ShelvedModeRoutes\.tsx$/,
|
|
/(?:^|\/)src\/app\/ShelvedGameRuntime\.enabled\.tsx$/,
|
|
/(?:^|\/)src\/app\/ShelvedOnlineGroupRuntime\.enabled\.ts$/,
|
|
/(?:^|\/)src\/app\/(?:arenaClient|gmManastormRuntime|manastormShellRuntime)\.[jt]sx?$/,
|
|
/(?:^|\/)src\/game\/(?:arena|manastormAdmin|manastormCatalog|manastormInstalled)(?:\/|\.[jt]sx?$)/,
|
|
/(?:^|\/)src\/game\/ManastormBridge\.[jt]sx?$/,
|
|
/(?:^|\/)src\/ui\/(?:Arena|GmAdmin|GmReview|GmWorld|Manastorm)[^/]*\.[jt]sx?$/,
|
|
/(?:^|\/)src\/scene\/ShelvedGameScene\.enabled\.tsx$/,
|
|
/(?:^|\/)src\/game\/ShelvedDungeonSession\.enabled\.ts$/,
|
|
/(?:^|\/)src\/ui\/(?:ShelvedCompanionPanel|ShelvedHud)\.enabled\.tsx$/,
|
|
];
|
|
|
|
function fail(message) {
|
|
throw new Error(`Release bundle verification failed: ${message}`);
|
|
}
|
|
|
|
async function walkRelativeFiles(root) {
|
|
const files = [];
|
|
async function visit(directory) {
|
|
for (const entry of await readdir(directory, { withFileTypes: true })) {
|
|
const filePath = path.join(directory, entry.name);
|
|
if (entry.isDirectory()) await visit(filePath);
|
|
else if (entry.isFile()) files.push(path.relative(root, filePath).split(path.sep).join("/"));
|
|
}
|
|
}
|
|
await visit(root);
|
|
return files;
|
|
}
|
|
|
|
export async function verifyReleaseBundle(root) {
|
|
const manifest = JSON.parse(await readFile(path.join(root, "content", "bootstrap-manifest.json"), "utf8"));
|
|
const moduleReport = JSON.parse(await readFile(path.join(root, "bundle-module-report.json"), "utf8"));
|
|
if (moduleReport.shelvedModesEnabled !== false) fail("normal releases must compile with shelved modes disabled");
|
|
|
|
const expectedPacks = [...contentPolicy.bootstrapPackIds].sort();
|
|
const actualPacks = (manifest.packs ?? []).map((pack) => pack.id).sort();
|
|
if (JSON.stringify(actualPacks) !== JSON.stringify(expectedPacks)) {
|
|
fail(`bootstrap packs were ${actualPacks.join(", ")}; expected ${expectedPacks.join(", ")}`);
|
|
}
|
|
const hiddenPack = actualPacks.find((packId) => (
|
|
contentPolicy.hiddenContentPackIds.includes(packId)
|
|
|| contentPolicy.hiddenContentPackPrefixes.some((prefix) => packId.startsWith(prefix))
|
|
));
|
|
if (hiddenPack) fail(`bootstrap contains hidden pack ${hiddenPack}`);
|
|
|
|
const objects = new Map();
|
|
for (const entry of Object.values(manifest.files ?? {})) {
|
|
const object = entry.download ?? entry;
|
|
if (!objects.has(object.url)) objects.set(object.url, object.bytes);
|
|
}
|
|
const storedBytes = [...objects.values()].reduce((sum, bytes) => sum + Number(bytes ?? 0), 0);
|
|
if (storedBytes > contentPolicy.maxBootstrapStoredBytes) {
|
|
fail(`bootstrap uses ${storedBytes} stored bytes; limit is ${contentPolicy.maxBootstrapStoredBytes}`);
|
|
}
|
|
|
|
const prohibitedModules = moduleReport.chunks.flatMap((chunk) => chunk.modules).filter((moduleId) => (
|
|
PROHIBITED_MODULE_PATTERNS.some((pattern) => pattern.test(moduleId))
|
|
));
|
|
if (prohibitedModules.length) fail(`shelved client modules were emitted: ${[...new Set(prohibitedModules)].join(", ")}`);
|
|
|
|
const files = await walkRelativeFiles(root);
|
|
const arenaAsset = files.find((file) => /(?:^|\/)assets\/game\/arenas(?:\/|$)/i.test(file));
|
|
if (arenaAsset) fail(`arena asset was emitted at ${arenaAsset}`);
|
|
for (const objectUrl of objects.keys()) {
|
|
const objectPath = path.join(root, objectUrl.replace(/^\/+/, ""));
|
|
const objectStats = await stat(objectPath);
|
|
if (!objectStats.isFile()) fail(`bootstrap object is missing: ${objectUrl}`);
|
|
}
|
|
|
|
return {
|
|
root,
|
|
packs: actualPacks,
|
|
storedBytes,
|
|
storedMiB: Number((storedBytes / 1024 / 1024).toFixed(2)),
|
|
objectCount: objects.size,
|
|
chunkCount: moduleReport.chunks.length,
|
|
};
|
|
}
|
|
|
|
if (process.argv[1] && path.resolve(process.argv[1]) === path.resolve(SCRIPT_PATH)) {
|
|
const root = path.resolve(process.argv[2] ?? "dist");
|
|
console.log(JSON.stringify(await verifyReleaseBundle(root), null, 2));
|
|
}
|