Files
HiveGrid-Wireframes/03 Quellen und Referenzen/hivegrid-cloud/scripts/verify-web-build-surfaces.mjs
T

202 lines
9.3 KiB
JavaScript

#!/usr/bin/env node
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
const portalRouteIds = new Set([
"root",
"routes/healthz",
"routes/readyz",
"routes/healthcheck",
"routes/api.metrics.portal",
"routes/customer-portal/layout",
"routes/customer-portal/dashboard",
"routes/customer-portal/projects",
"routes/customer-portal/project-detail",
"routes/customer-portal/document-download",
"routes/customer-portal/upload",
"routes/customer-portal/settings",
"routes/customer-portal/branding-logo",
"routes/customer-portal/export-download",
"routes/customer-portal/project-upload",
"routes/customer-portal/project-complaint",
"routes/customer-portal/logout",
"routes/customer-portal/auth",
"routes/customer-portal/layout.__custom-host",
"routes/customer-portal/dashboard.__custom-host",
"routes/customer-portal/projects.__custom-host",
"routes/customer-portal/upload.__custom-host",
"routes/customer-portal/settings.__custom-host",
"routes/customer-portal/branding-logo.__custom-host",
"routes/customer-portal/export-download.__custom-host",
"routes/customer-portal/project-upload.__custom-host",
"routes/customer-portal/project-complaint.__custom-host",
"routes/customer-portal/project-detail.__custom-host",
"routes/customer-portal/document-download.__custom-host",
"routes/customer-portal/logout.__custom-host",
"routes/customer-portal/auth.login.__custom-host",
"routes/customer-portal/auth.forgot-password.__custom-host",
"routes/customer-portal/auth.setup.__custom-host",
"routes/customer-portal/auth.verify-email.__custom-host",
"routes/customer-portal/auth.reset-password.__custom-host",
]);
const forbiddenServerMarkers = {
core: ["portal-root.server", "loadPortalRootData", "handlePortalRootAction"],
portal: ["root-core.server", "runCoreRootMiddleware", "loadCoreRootData", "handleCoreRootAction", "cache-bootstrap", "startCacheSubscribers"],
};
const forbiddenClientMarkers = {
core: ["Portal build received Core root data"],
portal: [
"Core build received Portal root data",
"__Host-hivegrid_app_session",
"__Host-hivegrid_admin_session",
"crisp-client/",
"cookies.items.crisp",
"setCookieConsent",
"app_analytics",
"front.footer.termsAndConditions",
"PublicFooter",
],
};
const portalLocales = ["de", "en", "pl", "ru", "uk", "hu"];
const portalPublicFiles = new Set([
"favicon-32x32.png",
"fonts/instrument-sans-latin.woff2",
"fonts/instrument-sans-latin-ext.woff2",
...portalLocales.map((locale) => `locales/${locale}/translations.json`),
]);
export function verifyWebBuildSurfaces(rootDirectory) {
const core = readReactRouterManifest(rootDirectory, "build");
const portal = readReactRouterManifest(rootDirectory, "build-portal");
const coreIds = new Set(Object.keys(core.routes));
const portalIds = new Set(Object.keys(portal.routes));
const corePortalIds = [...coreIds].filter((id) => id.startsWith("routes/customer-portal/"));
if (corePortalIds.length > 0) {
throw new Error(`Core manifest contains Customer Portal routes: ${corePortalIds.join(", ")}`);
}
const unexpectedPortalIds = [...portalIds].filter((id) => !portalRouteIds.has(id));
const missingPortalIds = [...portalRouteIds].filter((id) => !portalIds.has(id));
if (unexpectedPortalIds.length > 0 || missingPortalIds.length > 0) {
throw new Error(
`Portal manifest route isolation failed (unexpected=${unexpectedPortalIds.join(",") || "none"}; missing=${missingPortalIds.join(",") || "none"})`
);
}
const portalLayout = portal.routes["routes/customer-portal/layout"];
if (portalLayout?.path !== ":tenant") {
throw new Error(`Portal manifest must expose the public :tenant path, received ${portalLayout?.path ?? "missing"}`);
}
const customHostLayout = portal.routes["routes/customer-portal/layout.__custom-host"];
if (customHostLayout?.path !== undefined || customHostLayout?.parentId !== "root") {
throw new Error("Portal manifest must expose the pathless custom-host layout directly below root");
}
const customHostChildren = [...portalIds].filter((id) => id.endsWith(".__custom-host") && id !== "routes/customer-portal/layout.__custom-host");
if (customHostChildren.some((id) => portal.routes[id]?.parentId !== "routes/customer-portal/layout.__custom-host")) {
throw new Error("Portal custom-host aliases must remain isolated below the pathless custom-host layout");
}
if (JSON.stringify(portal.routes).includes("__portal")) {
throw new Error("Portal manifest must not contain the trusted internal namespace");
}
assertNoOppositeSurfaceServerCode(rootDirectory, "build", "core");
assertNoOppositeSurfaceServerCode(rootDirectory, "build-portal", "portal");
assertNoOppositeSurfaceClientCode(rootDirectory, "build", "core");
assertNoOppositeSurfaceClientCode(rootDirectory, "build-portal", "portal");
assertPortalPublicArtifact(rootDirectory);
return { coreRouteCount: coreIds.size, portalRouteCount: portalIds.size };
}
function assertNoOppositeSurfaceServerCode(rootDirectory, buildDirectory, surface) {
const serverDirectory = path.join(rootDirectory, "apps", "web", buildDirectory, "server");
for (const filePath of listFiles(serverDirectory).filter((candidate) => candidate.endsWith(".js"))) {
const searchable = `${path.basename(filePath)}\n${fs.readFileSync(filePath, "utf8")}`;
const marker = forbiddenServerMarkers[surface].find((candidate) => searchable.includes(candidate));
if (marker) {
throw new Error(
`${surface === "portal" ? "Portal" : "Core"} server build contains opposite-surface code (${marker}) in ${path.relative(serverDirectory, filePath)}`
);
}
}
}
function assertPortalPublicArtifact(rootDirectory) {
const clientDirectory = path.join(rootDirectory, "apps", "web", "build-portal", "client");
const unexpected = listFiles(clientDirectory)
.map((filePath) => path.relative(clientDirectory, filePath).split(path.sep).join("/"))
.filter((relativePath) => {
const identityPath = relativePath.replace(/\.(?:br|gz)$/u, "");
return !identityPath.startsWith("assets/") && !portalPublicFiles.has(identityPath);
});
if (unexpected.length > 0) {
throw new Error(`Portal public artifact contains Core/unapproved files: ${unexpected.join(", ")}`);
}
for (const locale of portalLocales) {
const relativePath = `locales/${locale}/translations.json`;
const filePath = path.join(clientDirectory, ...relativePath.split("/"));
if (!fs.existsSync(filePath)) {
throw new Error(`Portal public artifact is missing ${relativePath}`);
}
const catalog = JSON.parse(fs.readFileSync(filePath, "utf8"));
const keys = Object.keys(catalog);
if (keys.length !== 1 || keys[0] !== "customerPortal" || !catalog.customerPortal || typeof catalog.customerPortal !== "object") {
throw new Error(`Portal locale ${relativePath} must contain only the customerPortal catalog`);
}
}
}
function assertNoOppositeSurfaceClientCode(rootDirectory, buildDirectory, surface) {
const clientDirectory = path.join(rootDirectory, "apps", "web", buildDirectory, "client");
for (const filePath of listFiles(clientDirectory).filter((candidate) => candidate.endsWith(".js"))) {
const searchable = `${path.basename(filePath)}\n${fs.readFileSync(filePath, "utf8")}`;
const marker = forbiddenClientMarkers[surface].find((candidate) => searchable.includes(candidate));
if (marker) {
throw new Error(
`${surface === "portal" ? "Portal" : "Core"} client build contains opposite-surface code (${marker}) in ${path.relative(clientDirectory, filePath)}`
);
}
}
}
function listFiles(directory) {
const files = [];
const pending = [directory];
while (pending.length > 0) {
const current = pending.pop();
for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
const entryPath = path.join(current, entry.name);
if (entry.isDirectory()) pending.push(entryPath);
else if (entry.isFile()) files.push(entryPath);
}
}
return files;
}
function readReactRouterManifest(rootDirectory, buildDirectory) {
const serverEntry = path.join(rootDirectory, "apps", "web", buildDirectory, "server", "index.js");
const assetsDirectory = path.join(rootDirectory, "apps", "web", buildDirectory, "client", "assets");
if (!fs.existsSync(serverEntry)) {
throw new Error(`Missing ${buildDirectory} server build`);
}
const manifestFiles = fs.readdirSync(assetsDirectory).filter((name) => /^manifest-.+\.js$/u.test(name));
if (manifestFiles.length !== 1) {
throw new Error(`Expected one ${buildDirectory} client route manifest, found ${manifestFiles.length}`);
}
const source = fs.readFileSync(path.join(assetsDirectory, manifestFiles[0]), "utf8");
const prefix = "window.__reactRouterManifest=";
if (!source.startsWith(prefix) || !source.endsWith(";")) {
throw new Error(`Invalid ${buildDirectory} client route manifest`);
}
return JSON.parse(source.slice(prefix.length, -1));
}
const invokedDirectly = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
if (invokedDirectly) {
const rootDirectory = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const result = verifyWebBuildSurfaces(rootDirectory);
console.log(`PASS web build surfaces core_routes=${result.coreRouteCount} portal_routes=${result.portalRouteCount}`);
}