mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-01 19:42:03 +00:00
* fix(engine): suppress font-loading 404 noise in render console output Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(engine): downgrade resource 404s to buffer-only instead of suppressing Address review feedback: instead of silently dropping "Failed to load resource" errors (which could hide real asset failures), keep them in browserConsoleBuffer for diagnostics but don't print to stdout. Real asset 404s are still caught by the file server's own logging. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(engine): narrow 404 filter to font CDN domains and woff2 files only Address review: filter was too broad and could suppress real asset failures. Now only suppresses 404s matching fonts.googleapis, fonts.gstatic, or .woff2 file extensions. Missing images, scripts, and videos will still surface as [Browser:ERROR] in render output. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(fonts): add Playfair Display, Noto Sans JP, Roboto, and 4 more to deterministic font database Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
170 lines
5.3 KiB
TypeScript
170 lines
5.3 KiB
TypeScript
/**
|
|
* Generate embedded font data for deterministic font injection.
|
|
*
|
|
* Reads woff2 files from @fontsource/* packages at build time and produces
|
|
* a TypeScript module with base64 data URIs. This eliminates the runtime
|
|
* dependency on @fontsource packages, making the CLI self-contained when
|
|
* bundled via tsup.
|
|
*
|
|
* Usage: tsx scripts/generate-font-data.ts
|
|
*/
|
|
|
|
import { readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
import { createRequire } from "node:module";
|
|
import { dirname, join, resolve } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const require = createRequire(import.meta.url);
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
|
type FontFaceSpec = { weight: string; style?: "normal" | "italic" };
|
|
type CanonicalFontSpec = { packageName: string; faces: FontFaceSpec[] };
|
|
|
|
// Mirror of CANONICAL_FONTS from deterministicFonts.ts — single source of truth
|
|
const CANONICAL_FONTS: Record<string, CanonicalFontSpec> = {
|
|
inter: {
|
|
packageName: "@fontsource/inter",
|
|
faces: [{ weight: "400" }, { weight: "700" }, { weight: "900" }],
|
|
},
|
|
montserrat: {
|
|
packageName: "@fontsource/montserrat",
|
|
faces: [{ weight: "400" }, { weight: "700" }, { weight: "900" }],
|
|
},
|
|
outfit: {
|
|
packageName: "@fontsource/outfit",
|
|
faces: [{ weight: "400" }, { weight: "700" }, { weight: "900" }],
|
|
},
|
|
nunito: {
|
|
packageName: "@fontsource/nunito",
|
|
faces: [{ weight: "400" }, { weight: "700" }, { weight: "900" }],
|
|
},
|
|
oswald: {
|
|
packageName: "@fontsource/oswald",
|
|
faces: [{ weight: "400" }, { weight: "700" }],
|
|
},
|
|
"league-gothic": {
|
|
packageName: "@fontsource/league-gothic",
|
|
faces: [{ weight: "400" }],
|
|
},
|
|
"archivo-black": {
|
|
packageName: "@fontsource/archivo-black",
|
|
faces: [{ weight: "400" }],
|
|
},
|
|
"space-mono": {
|
|
packageName: "@fontsource/space-mono",
|
|
faces: [{ weight: "400" }, { weight: "700" }],
|
|
},
|
|
"ibm-plex-mono": {
|
|
packageName: "@fontsource/ibm-plex-mono",
|
|
faces: [{ weight: "400" }, { weight: "700" }],
|
|
},
|
|
"jetbrains-mono": {
|
|
packageName: "@fontsource/jetbrains-mono",
|
|
faces: [{ weight: "400" }, { weight: "700" }],
|
|
},
|
|
"eb-garamond": {
|
|
packageName: "@fontsource/eb-garamond",
|
|
faces: [{ weight: "400" }, { weight: "700" }],
|
|
},
|
|
"playfair-display": {
|
|
packageName: "@fontsource/playfair-display",
|
|
faces: [{ weight: "400" }, { weight: "700" }, { weight: "900" }],
|
|
},
|
|
"source-code-pro": {
|
|
packageName: "@fontsource/source-code-pro",
|
|
faces: [{ weight: "400" }, { weight: "700" }],
|
|
},
|
|
"noto-sans-jp": {
|
|
packageName: "@fontsource/noto-sans-jp",
|
|
faces: [{ weight: "400" }, { weight: "700" }],
|
|
},
|
|
roboto: {
|
|
packageName: "@fontsource/roboto",
|
|
faces: [{ weight: "400" }, { weight: "700" }, { weight: "900" }],
|
|
},
|
|
"open-sans": {
|
|
packageName: "@fontsource/open-sans",
|
|
faces: [{ weight: "400" }, { weight: "700" }],
|
|
},
|
|
lato: {
|
|
packageName: "@fontsource/lato",
|
|
faces: [{ weight: "400" }, { weight: "700" }, { weight: "900" }],
|
|
},
|
|
poppins: {
|
|
packageName: "@fontsource/poppins",
|
|
faces: [{ weight: "400" }, { weight: "700" }, { weight: "900" }],
|
|
},
|
|
};
|
|
|
|
function packageRoot(packageName: string): string {
|
|
const packageJsonPath = require.resolve(`${packageName}/package.json`);
|
|
return dirname(packageJsonPath);
|
|
}
|
|
|
|
function resolveFontFile(
|
|
packageName: string,
|
|
weight: string,
|
|
style: "normal" | "italic" = "normal",
|
|
): string {
|
|
const root = packageRoot(packageName);
|
|
const filesDir = join(root, "files");
|
|
const slug = packageName.replace("@fontsource/", "");
|
|
const files = readdirSync(filesDir);
|
|
|
|
const exact = `${slug}-latin-${weight}-${style}.woff2`;
|
|
if (files.includes(exact)) {
|
|
return join(filesDir, exact);
|
|
}
|
|
|
|
const relaxed = files.find((file) => {
|
|
return file.endsWith(`-${weight}-${style}.woff2`) && file.includes("-latin-");
|
|
});
|
|
if (relaxed) {
|
|
return join(filesDir, relaxed);
|
|
}
|
|
|
|
throw new Error(`No font asset found for ${packageName} weight=${weight} style=${style}`);
|
|
}
|
|
|
|
function main() {
|
|
const entries: Array<{ key: string; dataUri: string }> = [];
|
|
let totalBytes = 0;
|
|
|
|
for (const [, spec] of Object.entries(CANONICAL_FONTS)) {
|
|
for (const face of spec.faces) {
|
|
const style = face.style || "normal";
|
|
const key = `${spec.packageName}:${face.weight}:${style}`;
|
|
const fontPath = resolveFontFile(spec.packageName, face.weight, style);
|
|
const content = readFileSync(fontPath);
|
|
totalBytes += content.length;
|
|
const dataUri = `data:font/woff2;base64,${content.toString("base64")}`;
|
|
entries.push({ key, dataUri });
|
|
}
|
|
}
|
|
|
|
const lines = [
|
|
"/**",
|
|
" * AUTO-GENERATED — do not edit manually.",
|
|
` * Generated by: scripts/generate-font-data.ts`,
|
|
` * ${entries.length} font faces, ${Math.round(totalBytes / 1024)}KB raw woff2`,
|
|
" */",
|
|
"",
|
|
"export const EMBEDDED_FONT_DATA: ReadonlyMap<string, string> = new Map([",
|
|
];
|
|
|
|
for (const entry of entries) {
|
|
lines.push(` ["${entry.key}", "${entry.dataUri}"],`);
|
|
}
|
|
|
|
lines.push("]);");
|
|
lines.push("");
|
|
|
|
const outputPath = resolve(__dirname, "../src/services/fontData.generated.ts");
|
|
writeFileSync(outputPath, lines.join("\n"), "utf8");
|
|
console.log(
|
|
`[generate-font-data] Wrote ${entries.length} font faces (${Math.round(totalBytes / 1024)}KB) → ${outputPath}`,
|
|
);
|
|
}
|
|
|
|
main();
|