@vertz/ui-server 0.2.38 → 0.2.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9,7 +9,7 @@ import {
9
9
 
10
10
  // src/bun-dev-server.ts
11
11
  import { execSync } from "child_process";
12
- import { existsSync as existsSync2, mkdirSync, readdirSync as readdirSync2, readFileSync as readFileSync2, watch as watch2, writeFileSync as writeFileSync2 } from "fs";
12
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync as readdirSync2, readFileSync as readFileSync3, watch as watch2, writeFileSync as writeFileSync3 } from "fs";
13
13
  import { dirname, normalize, resolve } from "path";
14
14
 
15
15
  // src/debug-logger.ts
@@ -288,7 +288,7 @@ function runWithScopedFetch(interceptor, fn) {
288
288
  // src/font-metrics.ts
289
289
  import { access as fsAccess } from "fs/promises";
290
290
  import { readFile } from "fs/promises";
291
- import { join as join2 } from "path";
291
+ import { isAbsolute, join as join2 } from "path";
292
292
  import { fromBuffer } from "@capsizecss/unpack";
293
293
  var SYSTEM_FONT_METRICS = {
294
294
  Arial: {
@@ -356,6 +356,12 @@ function getPrimarySrcPath(descriptor) {
356
356
  return null;
357
357
  }
358
358
  async function resolveFilePath(urlPath, rootDir) {
359
+ if (isAbsolute(urlPath)) {
360
+ try {
361
+ await fsAccess(urlPath);
362
+ return urlPath;
363
+ } catch {}
364
+ }
359
365
  const cleaned = urlPath.startsWith("/") ? urlPath.slice(1) : urlPath;
360
366
  const direct = join2(rootDir, cleaned);
361
367
  try {
@@ -395,6 +401,184 @@ async function extractFontMetrics(fonts, rootDir) {
395
401
  return result;
396
402
  }
397
403
 
404
+ // src/google-fonts-resolver.ts
405
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync as writeFileSync2 } from "fs";
406
+ import { createHash } from "crypto";
407
+ import { join as join3, relative } from "path";
408
+ var GOOGLE_FONTS_CSS2_URL = "https://fonts.googleapis.com/css2";
409
+ var MODERN_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36";
410
+ var MIN_WOFF2_SIZE = 100;
411
+ function buildGoogleFontsUrl(family, meta) {
412
+ const encodedFamily = family.replace(/ /g, "+");
413
+ const weightSpec = formatWeightSpec(meta.weight);
414
+ const hasItalic = meta.style.includes("italic");
415
+ let familyParam;
416
+ if (hasItalic && meta.style.includes("normal")) {
417
+ familyParam = `${encodedFamily}:ital,wght@0,${weightSpec};1,${weightSpec}`;
418
+ } else if (hasItalic) {
419
+ familyParam = `${encodedFamily}:ital,wght@1,${weightSpec}`;
420
+ } else {
421
+ familyParam = `${encodedFamily}:wght@${weightSpec}`;
422
+ }
423
+ return `${GOOGLE_FONTS_CSS2_URL}?family=${familyParam}&display=${meta.display}`;
424
+ }
425
+ function formatWeightSpec(weight) {
426
+ if (Array.isArray(weight)) {
427
+ return weight.join(";");
428
+ }
429
+ return String(weight);
430
+ }
431
+ function parseWoff2UrlsBySubset(css) {
432
+ const entries = [];
433
+ const blockRegex = /(?:\/\*\s*([\w-]+)\s*\*\/\s*)?@font-face\s*\{[^}]*url\(([^)]+\.woff2)\)[^}]*\}/g;
434
+ let match;
435
+ while ((match = blockRegex.exec(css)) !== null) {
436
+ entries.push({
437
+ subset: match[1] ?? "unknown",
438
+ url: match[2]
439
+ });
440
+ }
441
+ return entries;
442
+ }
443
+ function selectSubsetUrl(entries, requestedSubsets) {
444
+ for (const subset of requestedSubsets) {
445
+ const entry = entries.find((e) => e.subset === subset);
446
+ if (entry)
447
+ return entry.url;
448
+ }
449
+ const latin = entries.find((e) => e.subset === "latin");
450
+ if (latin)
451
+ return latin.url;
452
+ return entries[0].url;
453
+ }
454
+ function computeOptionsHash(meta) {
455
+ const data = JSON.stringify({
456
+ family: meta.family,
457
+ weight: meta.weight,
458
+ style: meta.style,
459
+ subsets: meta.subsets,
460
+ display: meta.display
461
+ });
462
+ return createHash("sha256").update(data).digest("hex").slice(0, 12);
463
+ }
464
+ function readManifest(cacheDir) {
465
+ const manifestPath = join3(cacheDir, "manifest.json");
466
+ if (existsSync(manifestPath)) {
467
+ try {
468
+ return JSON.parse(readFileSync(manifestPath, "utf-8"));
469
+ } catch {
470
+ return { entries: {} };
471
+ }
472
+ }
473
+ return { entries: {} };
474
+ }
475
+ function writeManifest(cacheDir, manifest) {
476
+ const manifestPath = join3(cacheDir, "manifest.json");
477
+ writeFileSync2(manifestPath, JSON.stringify(manifest, null, 2));
478
+ }
479
+ function isCacheValid(cacheDir, entry) {
480
+ for (let i = 0;i < entry.files.length; i++) {
481
+ const filePath = join3(cacheDir, entry.files[i]);
482
+ if (!existsSync(filePath))
483
+ return false;
484
+ const stat = Bun.file(filePath).size;
485
+ if (stat < MIN_WOFF2_SIZE)
486
+ return false;
487
+ }
488
+ return true;
489
+ }
490
+ async function downloadFile(url, destPath) {
491
+ const response = await fetch(url);
492
+ if (!response.ok) {
493
+ throw new Error(`Failed to download ${url}: ${response.status} ${response.statusText}`);
494
+ }
495
+ const buffer = await response.arrayBuffer();
496
+ const tmpPath = destPath + ".tmp";
497
+ writeFileSync2(tmpPath, Buffer.from(buffer));
498
+ renameSync(tmpPath, destPath);
499
+ return buffer.byteLength;
500
+ }
501
+ async function resolveGoogleFonts(fonts, cacheDir, projectRoot) {
502
+ mkdirSync(cacheDir, { recursive: true });
503
+ const manifest = readManifest(cacheDir);
504
+ const result = {};
505
+ const googleEntries = [];
506
+ for (const [key, descriptor] of Object.entries(fonts)) {
507
+ if (descriptor.__google) {
508
+ googleEntries.push({ key, descriptor });
509
+ } else {
510
+ result[key] = descriptor;
511
+ }
512
+ }
513
+ await Promise.all(googleEntries.map(async ({ key, descriptor }) => {
514
+ const meta = descriptor.__google;
515
+ const hash = computeOptionsHash(meta);
516
+ const cached = manifest.entries[hash];
517
+ if (cached && isCacheValid(cacheDir, cached)) {
518
+ const srcPath = toSrcPath(cacheDir, cached.files[0], projectRoot);
519
+ result[key] = createResolvedDescriptor(descriptor, srcPath);
520
+ return;
521
+ }
522
+ try {
523
+ const url = buildGoogleFontsUrl(meta.family, meta);
524
+ const response = await fetch(url, {
525
+ headers: { "User-Agent": MODERN_USER_AGENT }
526
+ });
527
+ if (!response.ok) {
528
+ console.error(`[vertz] Error: Google Fonts returned ${response.status} for family "${meta.family}".` + `
529
+ Check https://fonts.google.com for the correct name.`);
530
+ result[key] = descriptor;
531
+ return;
532
+ }
533
+ const css = await response.text();
534
+ const subsetEntries = parseWoff2UrlsBySubset(css);
535
+ if (subsetEntries.length === 0) {
536
+ console.error(`[vertz] Error: No .woff2 URLs found in Google Fonts response for "${meta.family}".`);
537
+ result[key] = descriptor;
538
+ return;
539
+ }
540
+ const selectedUrl = selectSubsetUrl(subsetEntries, meta.subsets);
541
+ const familySlug = meta.family.toLowerCase().replace(/\s+/g, "-");
542
+ const fileName = `${familySlug}-${hash}.woff2`;
543
+ const destPath = join3(cacheDir, fileName);
544
+ const size = await downloadFile(selectedUrl, destPath);
545
+ manifest.entries[hash] = { hash, files: [fileName], sizes: [size] };
546
+ const totalKB = Math.round(size / 1024);
547
+ console.log(`[vertz] Fetching Google Font: ${meta.family}... done (1 file, ${totalKB}KB)`);
548
+ const srcPath = toSrcPath(cacheDir, fileName, projectRoot);
549
+ result[key] = createResolvedDescriptor(descriptor, srcPath);
550
+ } catch (error) {
551
+ const msg = error instanceof Error ? error.message : String(error);
552
+ console.error(`[vertz] Error resolving Google Font "${meta.family}": ${msg}`);
553
+ result[key] = descriptor;
554
+ }
555
+ }));
556
+ writeManifest(cacheDir, manifest);
557
+ return result;
558
+ }
559
+ function toSrcPath(cacheDir, fileName, projectRoot) {
560
+ const absPath = join3(cacheDir, fileName);
561
+ if (projectRoot) {
562
+ return "/" + relative(projectRoot, absPath);
563
+ }
564
+ return absPath;
565
+ }
566
+ function createResolvedDescriptor(original, srcPath) {
567
+ return {
568
+ __brand: "FontDescriptor",
569
+ family: original.family,
570
+ weight: original.weight,
571
+ style: original.style,
572
+ display: original.display,
573
+ src: srcPath,
574
+ fallback: original.fallback,
575
+ subsets: original.subsets,
576
+ unicodeRange: original.unicodeRange,
577
+ adjustFontFallback: original.adjustFontFallback,
578
+ __google: original.__google
579
+ };
580
+ }
581
+
398
582
  // src/ready-gate.ts
399
583
  function createReadyGate(options) {
400
584
  let ready = false;
@@ -448,7 +632,7 @@ function createReadyGate(options) {
448
632
  }
449
633
 
450
634
  // src/source-map-resolver.ts
451
- import { readFileSync } from "fs";
635
+ import { readFileSync as readFileSync2 } from "fs";
452
636
  import { resolve as resolvePath } from "path";
453
637
  import { originalPositionFor, TraceMap } from "@jridgewell/trace-mapping";
454
638
  function extractInlineSourceMap(jsContent) {
@@ -475,7 +659,7 @@ function resolvePosition(sourceMapJSON, line, column) {
475
659
  }
476
660
  function readLineText(filePath, line) {
477
661
  try {
478
- const content = readFileSync(filePath, "utf-8");
662
+ const content = readFileSync2(filePath, "utf-8");
479
663
  const lines = content.split(`
480
664
  `);
481
665
  const idx = line - 1;
@@ -948,6 +1132,91 @@ function createPrefetchManifestManager(options) {
948
1132
  import { compileTheme } from "@vertz/ui";
949
1133
  import { EntityStore, MemoryCache, QueryEnvelopeStore } from "@vertz/ui/internals";
950
1134
 
1135
+ // src/css-filter.ts
1136
+ function extractClassNamesFromHTML(html) {
1137
+ const classes = new Set;
1138
+ const attrRegex = /\bclass(?:Name)?="([^"]*)"/g;
1139
+ let match;
1140
+ while ((match = attrRegex.exec(html)) !== null) {
1141
+ const value = match[1];
1142
+ for (const cls of value.split(/\s+/)) {
1143
+ if (cls)
1144
+ classes.add(cls);
1145
+ }
1146
+ }
1147
+ return classes;
1148
+ }
1149
+ function extractClassSelectorsFromCSS(css) {
1150
+ const selectors = new Set;
1151
+ const selectorRegex = /\.([\w-]+)/g;
1152
+ let match;
1153
+ while ((match = selectorRegex.exec(css)) !== null) {
1154
+ selectors.add(match[1]);
1155
+ }
1156
+ return selectors;
1157
+ }
1158
+ function isKeyframesBlock(css) {
1159
+ return /^\s*@keyframes\s/.test(css);
1160
+ }
1161
+ function hasOnlyClassSelectors(css) {
1162
+ let stripped = css.replace(/\/\*[\s\S]*?\*\//g, "");
1163
+ stripped = stripped.replace(/@keyframes\s+[\w-]+\s*\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}/g, "");
1164
+ stripped = stripped.replace(/@[\w-]+\s*\([^)]*\)\s*\{/g, "").replace(/^\s*\}/gm, "");
1165
+ const ruleRegex = /([^{}]+)\{/g;
1166
+ let match;
1167
+ let foundAnySelector = false;
1168
+ while ((match = ruleRegex.exec(stripped)) !== null) {
1169
+ const selector = match[1].trim();
1170
+ if (!selector)
1171
+ continue;
1172
+ foundAnySelector = true;
1173
+ if (!selector.startsWith(".")) {
1174
+ return false;
1175
+ }
1176
+ }
1177
+ return foundAnySelector;
1178
+ }
1179
+ function filterCSSByHTML(html, cssStrings) {
1180
+ if (cssStrings.length === 0 || !html)
1181
+ return [];
1182
+ const htmlClasses = extractClassNamesFromHTML(html);
1183
+ const kept = [];
1184
+ const pendingKeyframes = [];
1185
+ for (const css of cssStrings) {
1186
+ if (isKeyframesBlock(css)) {
1187
+ const nameMatch = /@keyframes\s+([\w-]+)/.exec(css);
1188
+ if (nameMatch) {
1189
+ pendingKeyframes.push({ css, name: nameMatch[1] });
1190
+ }
1191
+ continue;
1192
+ }
1193
+ if (!hasOnlyClassSelectors(css)) {
1194
+ kept.push(css);
1195
+ continue;
1196
+ }
1197
+ const cssClasses = extractClassSelectorsFromCSS(css);
1198
+ let used = false;
1199
+ for (const cls of cssClasses) {
1200
+ if (htmlClasses.has(cls)) {
1201
+ used = true;
1202
+ break;
1203
+ }
1204
+ }
1205
+ if (used)
1206
+ kept.push(css);
1207
+ }
1208
+ if (pendingKeyframes.length > 0) {
1209
+ const survivingCss = kept.join(`
1210
+ `);
1211
+ for (const kf of pendingKeyframes) {
1212
+ if (survivingCss.includes(kf.name)) {
1213
+ kept.push(kf.css);
1214
+ }
1215
+ }
1216
+ }
1217
+ return kept;
1218
+ }
1219
+
951
1220
  // src/dom-shim/index.ts
952
1221
  import { setAdapter } from "@vertz/ui/internals";
953
1222
 
@@ -1791,12 +2060,14 @@ function safeSerialize(data) {
1791
2060
 
1792
2061
  // src/ssr-render.ts
1793
2062
  var compiledThemeCache = new WeakMap;
2063
+ var compiledThemeWithMetricsCache = new WeakMap;
1794
2064
  function compileThemeCached(theme, fallbackMetrics) {
1795
- const cached = compiledThemeCache.get(theme);
2065
+ const cache = fallbackMetrics ? compiledThemeWithMetricsCache : compiledThemeCache;
2066
+ const cached = cache.get(theme);
1796
2067
  if (cached)
1797
2068
  return cached;
1798
2069
  const compiled = compileTheme(theme, { fallbackMetrics });
1799
- compiledThemeCache.set(theme, compiled);
2070
+ cache.set(theme, compiled);
1800
2071
  return compiled;
1801
2072
  }
1802
2073
  function createRequestContext(url) {
@@ -1832,7 +2103,7 @@ function resolveAppFactory(module) {
1832
2103
  }
1833
2104
  return createApp;
1834
2105
  }
1835
- function collectCSS(themeCss, module) {
2106
+ function collectCSS(themeCss, module, renderedHtml) {
1836
2107
  const alreadyIncluded = new Set;
1837
2108
  if (themeCss)
1838
2109
  alreadyIncluded.add(themeCss);
@@ -1841,8 +2112,13 @@ function collectCSS(themeCss, module) {
1841
2112
  alreadyIncluded.add(s);
1842
2113
  }
1843
2114
  const ssrCtx = ssrStorage.getStore();
1844
- const rawComponentCss = ssrCtx?.cssTracker?.size ? Array.from(ssrCtx.cssTracker) : module.getInjectedCSS?.() ?? [];
1845
- const componentCss = rawComponentCss.filter((s) => !alreadyIncluded.has(s));
2115
+ const tracker = ssrCtx?.cssTracker;
2116
+ const useTracker = tracker && tracker.size > 0;
2117
+ const rawComponentCss = useTracker ? Array.from(tracker) : module.getInjectedCSS?.() ?? [];
2118
+ let componentCss = rawComponentCss.filter((s) => !alreadyIncluded.has(s));
2119
+ if (!useTracker && componentCss.length > 0 && renderedHtml) {
2120
+ componentCss = filterCSSByHTML(renderedHtml, componentCss);
2121
+ }
1846
2122
  const themeTag = themeCss ? `<style data-vertz-css>${themeCss}</style>` : "";
1847
2123
  const globalTag = module.styles && module.styles.length > 0 ? `<style data-vertz-css>${module.styles.join(`
1848
2124
  `)}</style>` : "";
@@ -1923,7 +2199,7 @@ async function ssrRenderToString(module, url, options) {
1923
2199
  const vnode = toVNode(app);
1924
2200
  const stream = renderToStream(vnode);
1925
2201
  const html = await streamToString(stream);
1926
- const css = collectCSS(themeCss, module);
2202
+ const css = collectCSS(themeCss, module, html);
1927
2203
  const ssrData = resolvedQueries.length > 0 ? resolvedQueries.map(({ key, data }) => ({ key, data })) : [];
1928
2204
  return {
1929
2205
  html,
@@ -2226,7 +2502,7 @@ async function ssrRenderSinglePass(module, url, options) {
2226
2502
  const vnode = toVNode(app);
2227
2503
  const stream = renderToStream(vnode);
2228
2504
  const html = await streamToString(stream);
2229
- const css = collectCSS2(themeCss, module);
2505
+ const css = collectCSS2(themeCss, module, html);
2230
2506
  const ssrData = discoveredData.resolvedQueries.map(({ key, data }) => ({
2231
2507
  key,
2232
2508
  data
@@ -2378,7 +2654,7 @@ async function renderWithPrefetchedData(module, normalizedUrl, prefetchedData, o
2378
2654
  matchedRoutePatterns: renderCtx.matchedRoutePatterns
2379
2655
  };
2380
2656
  }
2381
- const css = collectCSS2(themeCss, module);
2657
+ const css = collectCSS2(themeCss, module, html);
2382
2658
  const ssrData = data.resolvedQueries.map(({ key, data: d }) => ({
2383
2659
  key,
2384
2660
  data: d
@@ -2451,7 +2727,7 @@ function extractMethodFromKey(key) {
2451
2727
  const segments = cleanPath.split("/").filter(Boolean);
2452
2728
  return segments.length > 1 ? "get" : "list";
2453
2729
  }
2454
- function collectCSS2(themeCss, module) {
2730
+ function collectCSS2(themeCss, module, renderedHtml) {
2455
2731
  const alreadyIncluded = new Set;
2456
2732
  if (themeCss)
2457
2733
  alreadyIncluded.add(themeCss);
@@ -2460,8 +2736,13 @@ function collectCSS2(themeCss, module) {
2460
2736
  alreadyIncluded.add(s);
2461
2737
  }
2462
2738
  const ssrCtx = ssrStorage.getStore();
2463
- const rawComponentCss = ssrCtx?.cssTracker?.size ? Array.from(ssrCtx.cssTracker) : module.getInjectedCSS?.() ?? [];
2464
- const componentCss = rawComponentCss.filter((s) => !alreadyIncluded.has(s));
2739
+ const tracker = ssrCtx?.cssTracker;
2740
+ const useTracker = tracker && tracker.size > 0;
2741
+ const rawComponentCss = useTracker ? Array.from(tracker) : module.getInjectedCSS?.() ?? [];
2742
+ let componentCss = rawComponentCss.filter((s) => !alreadyIncluded.has(s));
2743
+ if (!useTracker && componentCss.length > 0 && renderedHtml) {
2744
+ componentCss = filterCSSByHTML(renderedHtml, componentCss);
2745
+ }
2465
2746
  const themeTag = themeCss ? `<style data-vertz-css>${themeCss}</style>` : "";
2466
2747
  const globalTag = module.styles && module.styles.length > 0 ? `<style data-vertz-css>${module.styles.join(`
2467
2748
  `)}</style>` : "";
@@ -2472,21 +2753,21 @@ function collectCSS2(themeCss, module) {
2472
2753
  }
2473
2754
 
2474
2755
  // src/upstream-watcher.ts
2475
- import { existsSync, lstatSync, readdirSync, realpathSync, watch } from "fs";
2476
- import { join as join3 } from "path";
2756
+ import { existsSync as existsSync2, lstatSync, readdirSync, realpathSync, watch } from "fs";
2757
+ import { join as join4 } from "path";
2477
2758
  function resolveWorkspacePackages(projectRoot, filter) {
2478
2759
  const results = [];
2479
2760
  const packageNames = filter === true ? discoverVertzPackages(projectRoot) : parsePackageNames(filter);
2480
2761
  for (const { scope, name } of packageNames) {
2481
- const nmPath = join3(projectRoot, "node_modules", scope, name);
2482
- if (!existsSync(nmPath))
2762
+ const nmPath = join4(projectRoot, "node_modules", scope, name);
2763
+ if (!existsSync2(nmPath))
2483
2764
  continue;
2484
2765
  const stat = lstatSync(nmPath);
2485
2766
  if (!stat.isSymbolicLink())
2486
2767
  continue;
2487
2768
  const realPath = realpathSync(nmPath);
2488
- const distPath = join3(realPath, "dist");
2489
- if (!existsSync(distPath))
2769
+ const distPath = join4(realPath, "dist");
2770
+ if (!existsSync2(distPath))
2490
2771
  continue;
2491
2772
  const fullName = scope ? `${scope}/${name}` : name;
2492
2773
  results.push({ name: fullName, distPath });
@@ -2494,8 +2775,8 @@ function resolveWorkspacePackages(projectRoot, filter) {
2494
2775
  return results;
2495
2776
  }
2496
2777
  function discoverVertzPackages(projectRoot) {
2497
- const scopeDir = join3(projectRoot, "node_modules", "@vertz");
2498
- if (!existsSync(scopeDir))
2778
+ const scopeDir = join4(projectRoot, "node_modules", "@vertz");
2779
+ if (!existsSync2(scopeDir))
2499
2780
  return [];
2500
2781
  return readdirSync(scopeDir).filter((entry) => !entry.startsWith(".")).map((entry) => ({ scope: "@vertz", name: entry }));
2501
2782
  }
@@ -2553,7 +2834,7 @@ function createUpstreamWatcher(options) {
2553
2834
  // src/bun-dev-server.ts
2554
2835
  function detectFaviconTag(projectRoot) {
2555
2836
  const faviconPath = resolve(projectRoot, "public", "favicon.svg");
2556
- return existsSync2(faviconPath) ? '<link rel="icon" type="image/svg+xml" href="/favicon.svg">' : "";
2837
+ return existsSync3(faviconPath) ? '<link rel="icon" type="image/svg+xml" href="/favicon.svg">' : "";
2557
2838
  }
2558
2839
  var STALE_GRAPH_PATTERNS = [
2559
2840
  /Export named ['"].*['"] not found in module/i,
@@ -2982,7 +3263,7 @@ function createBunDevServer(options) {
2982
3263
  installFetchProxy();
2983
3264
  }
2984
3265
  const devDir = resolve(projectRoot, ".vertz", "dev");
2985
- mkdirSync(devDir, { recursive: true });
3266
+ mkdirSync2(devDir, { recursive: true });
2986
3267
  const logger = createDebugLogger(devDir);
2987
3268
  const diagnostics = new DiagnosticsCollector;
2988
3269
  let aotManifestManager = null;
@@ -3206,7 +3487,7 @@ function createBunDevServer(options) {
3206
3487
  if (!openapi)
3207
3488
  return null;
3208
3489
  try {
3209
- const specContent = readFileSync2(openapi.specPath, "utf-8");
3490
+ const specContent = readFileSync3(openapi.specPath, "utf-8");
3210
3491
  return JSON.parse(specContent);
3211
3492
  } catch (err) {
3212
3493
  console.error("[Server] Error reading OpenAPI spec:", err);
@@ -3214,7 +3495,7 @@ function createBunDevServer(options) {
3214
3495
  }
3215
3496
  };
3216
3497
  const setupOpenAPIWatcher = () => {
3217
- if (!openapi || !existsSync2(openapi.specPath))
3498
+ if (!openapi || !existsSync3(openapi.specPath))
3218
3499
  return;
3219
3500
  cachedSpec = loadOpenAPISpec();
3220
3501
  if (cachedSpec === null)
@@ -3240,7 +3521,7 @@ function createBunDevServer(options) {
3240
3521
  headers: { "Content-Type": "application/json" }
3241
3522
  });
3242
3523
  }
3243
- if (openapi && existsSync2(openapi.specPath)) {
3524
+ if (openapi && existsSync3(openapi.specPath)) {
3244
3525
  cachedSpec = loadOpenAPISpec();
3245
3526
  if (cachedSpec) {
3246
3527
  return new Response(JSON.stringify(cachedSpec), {
@@ -3330,10 +3611,10 @@ function createBunDevServer(options) {
3330
3611
  let ssrMod;
3331
3612
  try {
3332
3613
  if (isRestarting) {
3333
- mkdirSync(devDir, { recursive: true });
3614
+ mkdirSync2(devDir, { recursive: true });
3334
3615
  const ssrBootPath = resolve(devDir, "ssr-reload-entry.ts");
3335
3616
  const ts = Date.now();
3336
- writeFileSync2(ssrBootPath, `export * from '${entryPath}';
3617
+ writeFileSync3(ssrBootPath, `export * from '${entryPath}';
3337
3618
  `);
3338
3619
  ssrMod = await import(`${ssrBootPath}?t=${ts}`);
3339
3620
  } else {
@@ -3358,6 +3639,18 @@ function createBunDevServer(options) {
3358
3639
  process.exit(1);
3359
3640
  }
3360
3641
  }
3642
+ const fontCacheDir = resolve(projectRoot, ".vertz", "fonts");
3643
+ if (ssrMod.theme?.fonts) {
3644
+ try {
3645
+ const resolvedFonts = await resolveGoogleFonts(ssrMod.theme.fonts, fontCacheDir, projectRoot);
3646
+ ssrMod = {
3647
+ ...ssrMod,
3648
+ theme: { ...ssrMod.theme, fonts: resolvedFonts }
3649
+ };
3650
+ } catch (e) {
3651
+ console.warn("[Server] Failed to resolve Google Fonts:", e);
3652
+ }
3653
+ }
3361
3654
  let fontFallbackMetrics;
3362
3655
  if (ssrMod.theme?.fonts) {
3363
3656
  try {
@@ -3369,13 +3662,13 @@ function createBunDevServer(options) {
3369
3662
  let prefetchManager = null;
3370
3663
  const srcDir = resolve(projectRoot, "src");
3371
3664
  const routerCandidates = [resolve(srcDir, "router.tsx"), resolve(srcDir, "router.ts")];
3372
- const routerPath = routerCandidates.find((p) => existsSync2(p));
3665
+ const routerPath = routerCandidates.find((p) => existsSync3(p));
3373
3666
  if (routerPath) {
3374
3667
  prefetchManager = createPrefetchManifestManager({
3375
3668
  routerPath,
3376
3669
  readFile: (path) => {
3377
3670
  try {
3378
- return readFileSync2(path, "utf-8");
3671
+ return readFileSync3(path, "utf-8");
3379
3672
  } catch {
3380
3673
  return;
3381
3674
  }
@@ -3387,12 +3680,12 @@ function createBunDevServer(options) {
3387
3680
  const base = resolve(dir, specifier);
3388
3681
  for (const ext of [".tsx", ".ts", ".jsx", ".js"]) {
3389
3682
  const candidate = `${base}${ext}`;
3390
- if (existsSync2(candidate))
3683
+ if (existsSync3(candidate))
3391
3684
  return candidate;
3392
3685
  }
3393
3686
  for (const ext of [".tsx", ".ts"]) {
3394
3687
  const candidate = resolve(base, `index${ext}`);
3395
- if (existsSync2(candidate))
3688
+ if (existsSync3(candidate))
3396
3689
  return candidate;
3397
3690
  }
3398
3691
  return;
@@ -3416,7 +3709,7 @@ function createBunDevServer(options) {
3416
3709
  aotManifestManager = createAotManifestManager({
3417
3710
  readFile: (path) => {
3418
3711
  try {
3419
- return readFileSync2(path, "utf-8");
3712
+ return readFileSync3(path, "utf-8");
3420
3713
  } catch {
3421
3714
  return;
3422
3715
  }
@@ -3443,9 +3736,9 @@ function createBunDevServer(options) {
3443
3736
  console.warn("[Server] Failed to build AOT manifest:", e instanceof Error ? e.message : e);
3444
3737
  aotManifestManager = null;
3445
3738
  }
3446
- mkdirSync(devDir, { recursive: true });
3739
+ mkdirSync2(devDir, { recursive: true });
3447
3740
  const frInitPath = resolve(devDir, "fast-refresh-init.ts");
3448
- writeFileSync2(frInitPath, `import '@vertz/ui-server/fast-refresh-runtime';
3741
+ writeFileSync3(frInitPath, `import '@vertz/ui-server/fast-refresh-runtime';
3449
3742
  if (import.meta.hot) import.meta.hot.accept();
3450
3743
  `);
3451
3744
  const hmrShellHtml = `<!doctype html>
@@ -3457,7 +3750,7 @@ if (import.meta.hot) import.meta.hot.accept();
3457
3750
  <script type="module" src="${clientSrc}"></script>
3458
3751
  </body></html>`;
3459
3752
  const hmrShellPath = resolve(devDir, "hmr-shell.html");
3460
- writeFileSync2(hmrShellPath, hmrShellHtml);
3753
+ writeFileSync3(hmrShellPath, hmrShellHtml);
3461
3754
  const hmrShellModule = __require(hmrShellPath);
3462
3755
  setupOpenAPIWatcher();
3463
3756
  let bundledScriptUrl = null;
@@ -3924,7 +4217,7 @@ data: {}
3924
4217
  return false;
3925
4218
  }
3926
4219
  stopped = false;
3927
- if (existsSync2(srcDir)) {
4220
+ if (existsSync3(srcDir)) {
3928
4221
  srcWatcherRef = watch2(srcDir, { recursive: true }, (_event, filename) => {
3929
4222
  if (!filename)
3930
4223
  return;
@@ -4058,12 +4351,21 @@ data: {}
4058
4351
  const cacheCleared = clearSSRRequireCache();
4059
4352
  logger.log("watcher", "cache-cleared", { entries: cacheCleared });
4060
4353
  const ssrWrapperPath = resolve(devDir, "ssr-reload-entry.ts");
4061
- mkdirSync(devDir, { recursive: true });
4062
- writeFileSync2(ssrWrapperPath, `export * from '${entryPath}';
4354
+ mkdirSync2(devDir, { recursive: true });
4355
+ writeFileSync3(ssrWrapperPath, `export * from '${entryPath}';
4063
4356
  `);
4064
4357
  const ssrReloadStart = performance.now();
4065
4358
  try {
4066
- const freshMod = await import(`${ssrWrapperPath}?t=${Date.now()}`);
4359
+ let freshMod = await import(`${ssrWrapperPath}?t=${Date.now()}`);
4360
+ if (freshMod.theme?.fonts) {
4361
+ try {
4362
+ const resolvedFonts = await resolveGoogleFonts(freshMod.theme.fonts, fontCacheDir, projectRoot);
4363
+ freshMod = {
4364
+ ...freshMod,
4365
+ theme: { ...freshMod.theme, fonts: resolvedFonts }
4366
+ };
4367
+ } catch {}
4368
+ }
4067
4369
  ssrMod = freshMod;
4068
4370
  ssrFallback = false;
4069
4371
  if (freshMod.theme?.fonts) {
@@ -4088,11 +4390,20 @@ data: {}
4088
4390
  if (stopped)
4089
4391
  return;
4090
4392
  clearSSRRequireCache();
4091
- mkdirSync(devDir, { recursive: true });
4092
- writeFileSync2(ssrWrapperPath, `export * from '${entryPath}';
4393
+ mkdirSync2(devDir, { recursive: true });
4394
+ writeFileSync3(ssrWrapperPath, `export * from '${entryPath}';
4093
4395
  `);
4094
4396
  try {
4095
- const freshMod = await import(`${ssrWrapperPath}?t=${Date.now()}`);
4397
+ let freshMod = await import(`${ssrWrapperPath}?t=${Date.now()}`);
4398
+ if (freshMod.theme?.fonts) {
4399
+ try {
4400
+ const resolvedFonts = await resolveGoogleFonts(freshMod.theme.fonts, fontCacheDir, projectRoot);
4401
+ freshMod = {
4402
+ ...freshMod,
4403
+ theme: { ...freshMod.theme, fonts: resolvedFonts }
4404
+ };
4405
+ } catch {}
4406
+ }
4096
4407
  ssrMod = freshMod;
4097
4408
  ssrFallback = false;
4098
4409
  if (freshMod.theme?.fonts) {