Major update for v1 and tests

This commit is contained in:
khalid@traclabs.com
2026-05-23 03:28:58 -05:00
parent 628a6765f4
commit b1fb6fa3aa
1748 changed files with 27723 additions and 1854 deletions

145
scripts/fetch-fonts.mjs Normal file
View File

@@ -0,0 +1,145 @@
#!/usr/bin/env node
/**
* Downloads TTF font files for the Google Fonts used in the editor and saves
* them under ./fonts/. server.js scans that directory at startup and calls
* registerFont() for each file.
*
* Why we do this:
* node-canvas does not pick up Google Fonts automatically. It only knows
* about fonts that are either (a) registered explicitly via registerFont()
* or (b) installed system-wide and discoverable via fontconfig. Without
* this script, the server falls back to whatever Cairo can find for
* "Roboto", "Bebas Neue", etc., which is usually nothing — exports get
* substituted to a generic sans-serif and look nothing like the preview.
*
* Source:
* We pull TTFs from the Fontsource jsDelivr CDN. URLs look like
* https://cdn.jsdelivr.net/fontsource/fonts/<slug>@latest/latin-400-normal.ttf
* Slugs are the lowercase, hyphenated family name. The script asks for two
* weights per family (regular + bold) and falls through gracefully if a
* weight isn't published.
*
* Usage:
* npm run fetch-fonts
*
* Re-running is cheap: existing files are skipped unless --force is passed.
*/
import { mkdir, writeFile, access } from 'node:fs/promises';
import { constants as fsConstants } from 'node:fs';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const ROOT = join(__dirname, '..');
const FONTS_DIR = join(ROOT, 'fonts');
const FORCE = process.argv.includes('--force');
// Families used by the editor (FONTS in src/constants/fonts.js + the two
// CSS-level defaults). Keep this list in sync with that file.
const FAMILIES = [
'Roboto',
'Open Sans',
'Lato',
'Montserrat',
'Oswald',
'Raleway',
'Poppins',
'Roboto Condensed',
'Source Sans 3',
'Roboto Slab',
'Merriweather',
'Ubuntu',
'Playfair Display',
'Nunito',
'Rubik',
'Work Sans',
'Lora',
'Fira Sans',
'Barlow',
'Bebas Neue',
'DM Sans',
'Space Mono',
// Display + handwritten faces used by the Pawfectly Yours theme. Pacifico is
// the brand logo face; Caveat is a friendly script fallback exposed in the
// text-tab font picker so users can match the brand voice in their designs.
'Pacifico',
'Caveat',
];
const WEIGHTS = [
{ weight: 400, suffix: 'Regular' },
{ weight: 700, suffix: 'Bold' },
];
const familyToSlug = (family) => family.toLowerCase().replace(/\s+/g, '-');
// Filename convention used by server.js's registration scanner:
// "Family_Name-Variant.ttf" → family = "Family Name", variant = "Variant".
// Underscore-vs-space is so the file system stays portable.
const familyToFileBase = (family) => family.replace(/\s+/g, '_');
async function fileExists(path) {
try {
await access(path, fsConstants.R_OK);
return true;
} catch {
return false;
}
}
async function fetchOne(family, weight, suffix) {
const slug = familyToSlug(family);
const url = `https://cdn.jsdelivr.net/fontsource/fonts/${slug}@latest/latin-${weight}-normal.ttf`;
const dest = join(FONTS_DIR, `${familyToFileBase(family)}-${suffix}.ttf`);
if (!FORCE && await fileExists(dest)) {
return { family, weight, status: 'skipped', dest };
}
const res = await fetch(url);
if (!res.ok) {
return { family, weight, status: 'missing', dest, code: res.status };
}
const buf = Buffer.from(await res.arrayBuffer());
await writeFile(dest, buf);
return { family, weight, status: 'fetched', dest, bytes: buf.length };
}
async function main() {
await mkdir(FONTS_DIR, { recursive: true });
let fetched = 0, skipped = 0, missing = 0;
for (const family of FAMILIES) {
for (const { weight, suffix } of WEIGHTS) {
try {
const r = await fetchOne(family, weight, suffix);
if (r.status === 'fetched') {
fetched++;
console.log(`${family} ${weight}${r.dest} (${(r.bytes / 1024).toFixed(0)} KB)`);
} else if (r.status === 'skipped') {
skipped++;
console.log(`· ${family} ${weight} (already present)`);
} else {
missing++;
console.log(`! ${family} ${weight} HTTP ${r.code} — variant not published`);
}
} catch (err) {
missing++;
console.log(`! ${family} ${weight} ${err.message}`);
}
}
}
console.log(`\nDone. Fetched ${fetched}, skipped ${skipped}, missing ${missing}.`);
if (missing > 0) {
console.log('Missing variants are not fatal — the server will fall back to other registered weights.');
}
}
main().catch((err) => {
console.error('Font fetch failed:', err);
process.exit(1);
});