92 lines
3.6 KiB
JavaScript
92 lines
3.6 KiB
JavaScript
import { readFileSync } from "node:fs";
|
|
import { createServer } from "node:http";
|
|
import { isAbsolute, join, resolve } from "node:path";
|
|
|
|
import { chromium } from "@playwright/test";
|
|
|
|
function option(name) {
|
|
const index = process.argv.indexOf(name);
|
|
return index >= 0 ? process.argv[index + 1] : undefined;
|
|
}
|
|
|
|
function defaultConfigPath() {
|
|
if (!process.env.LOCALAPPDATA || !isAbsolute(process.env.LOCALAPPDATA)) {
|
|
throw new Error("local_app_data_unavailable");
|
|
}
|
|
return join(process.env.LOCALAPPDATA, "Dada", "P0A", "config", "instance.json");
|
|
}
|
|
|
|
const manifestPath = resolve(option("--manifest") ?? "config/runtime-assets-manifest.json");
|
|
const configPath = resolve(option("--config") ?? process.env.DADA_INSTANCE_CONFIG_PATH ?? defaultConfigPath());
|
|
const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
const configuration = JSON.parse(readFileSync(configPath, "utf8"));
|
|
if (typeof configuration.asset_root !== "string" || !isAbsolute(configuration.asset_root)) {
|
|
throw new Error("asset_root_configuration_invalid");
|
|
}
|
|
|
|
const fontEntries = manifest.entries
|
|
.filter((entry) => entry.assetId.startsWith("TEXT-FONT-") && entry.mimeType.startsWith("font/"))
|
|
.toSorted((left, right) => left.assetId.localeCompare(right.assetId));
|
|
const uniqueEntries = [...new Map(fontEntries.map((entry) => [entry.sha256, entry])).values()];
|
|
const fontPaths = new Map(
|
|
uniqueEntries.map((entry) => [
|
|
`/${encodeURIComponent(entry.assetId)}`,
|
|
{ mimeType: entry.mimeType, path: join(configuration.asset_root, entry.relativePath) },
|
|
]),
|
|
);
|
|
|
|
const server = createServer((request, response) => {
|
|
const font = fontPaths.get(request.url ?? "");
|
|
if (!font) {
|
|
response.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
response.end("<!doctype html><title>Dada browser font validation</title>");
|
|
return;
|
|
}
|
|
const bytes = readFileSync(font.path);
|
|
response.writeHead(200, { "Content-Length": bytes.length, "Content-Type": font.mimeType });
|
|
response.end(bytes);
|
|
});
|
|
|
|
await new Promise((resolveReady) => server.listen(0, "127.0.0.1", resolveReady));
|
|
const address = server.address();
|
|
if (!address || typeof address === "string") throw new Error("browser_font_probe_server_unavailable");
|
|
const origin = `http://127.0.0.1:${address.port}`;
|
|
const browser = await chromium.launch({ channel: option("--channel") ?? "msedge", headless: true });
|
|
const failures = [];
|
|
|
|
try {
|
|
let page = await browser.newPage();
|
|
await page.goto(origin);
|
|
for (let index = 0; index < uniqueEntries.length; index += 1) {
|
|
if (index > 0 && index % 25 === 0) {
|
|
await page.close();
|
|
page = await browser.newPage();
|
|
await page.goto(origin);
|
|
}
|
|
const entry = uniqueEntries[index];
|
|
const result = await page.evaluate(async ({ family, url }) => {
|
|
try {
|
|
const response = await fetch(url);
|
|
if (!response.ok) return `http_${response.status}`;
|
|
await new FontFace(family, await response.arrayBuffer()).load();
|
|
return "loaded";
|
|
} catch (error) {
|
|
return error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
|
}
|
|
}, { family: `DadaFontProbe${index}`, url: `${origin}/${encodeURIComponent(entry.assetId)}` });
|
|
if (result !== "loaded") failures.push({ asset_id: entry.assetId, error: result });
|
|
}
|
|
} finally {
|
|
await browser.close();
|
|
await new Promise((resolveClosed) => server.close(resolveClosed));
|
|
}
|
|
|
|
const result = {
|
|
checked_entries: fontEntries.length,
|
|
failed_fonts: failures,
|
|
status: failures.length === 0 ? "passed" : "failed",
|
|
unique_fonts: uniqueEntries.length,
|
|
};
|
|
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
if (failures.length > 0) process.exitCode = 1;
|