veryfront 0.1.177 → 0.1.179

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.
package/esm/deno.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export default {
2
2
  "name": "veryfront",
3
- "version": "0.1.177",
3
+ "version": "0.1.179",
4
4
  "license": "Apache-2.0",
5
5
  "nodeModulesDir": "auto",
6
6
  "exclude": [
@@ -75,6 +75,9 @@ export declare class RenderPipeline {
75
75
  renderPage(slug: string, options?: RenderOptions): Promise<RenderResult>;
76
76
  /** Resolve page data for SPA client-side navigation without rendering HTML. */
77
77
  resolvePageData(slug: string, options?: RenderOptions): Promise<PageDataResponse>;
78
+ private extractMdxMetadata;
79
+ private resolvePageDataCss;
80
+ private generatePageCssFromHtml;
78
81
  /**
79
82
  * Build a cache key that is safe for multi-tenant + query-param aware caching.
80
83
  * Returns null when request contains sensitive headers (Authorization/Cookie) and
@@ -1 +1 @@
1
- {"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../../../src/src/rendering/orchestrator/pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAgBH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAEtE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AA6ChF,OAAO,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AAE1D;;;;GAIG;AACH,MAAM,WAAW,wBAAwB;IACvC,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;IACxE,aAAa,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACrF;AAED,MAAM,WAAW,oBAAoB;IACnC,YAAY,EAAE,YAAY,CAAC;IAC3B,gBAAgB,EAAE,wBAAwB,CAAC;IAC3C,YAAY,EAAE,YAAY,CAAC;IAC3B,kBAAkB,EAAE,kBAAkB,CAAC;IACvC,eAAe,EAAE,eAAe,CAAC;IACjC,OAAO,EAAE,cAAc,CAAC;IACxB,IAAI,EAAE,aAAa,GAAG,YAAY,CAAC;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,8EAA8E;IAC9E,iBAAiB,CAAC,EAAE,OAAO,qBAAqB,EAAE,sBAAsB,CAAC;CAC1E;AAeD,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,kBAAkB,CAAqB;gBAEnC,MAAM,EAAE,oBAAoB;IAaxC;;;OAGG;IACH,gBAAgB,IAAI,IAAI;IAKxB,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,sBAAsB;YAIhB,0BAA0B;IAaxC;;;;;;;;;OASG;YACW,qBAAqB;IAyDnC;;;OAGG;YACW,mBAAmB;IAiGjC,OAAO,CAAC,uBAAuB;IAkCzB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC;IA+P9E,+EAA+E;IACzE,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAgMvF;;;;;;OAMG;IACH,OAAO,CAAC,aAAa;CAetB"}
1
+ {"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../../../src/src/rendering/orchestrator/pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAgBH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAEtE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AA6ChF,OAAO,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AAE1D;;;;GAIG;AACH,MAAM,WAAW,wBAAwB;IACvC,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;IACxE,aAAa,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACrF;AAED,MAAM,WAAW,oBAAoB;IACnC,YAAY,EAAE,YAAY,CAAC;IAC3B,gBAAgB,EAAE,wBAAwB,CAAC;IAC3C,YAAY,EAAE,YAAY,CAAC;IAC3B,kBAAkB,EAAE,kBAAkB,CAAC;IACvC,eAAe,EAAE,eAAe,CAAC;IACjC,OAAO,EAAE,cAAc,CAAC;IACxB,IAAI,EAAE,aAAa,GAAG,YAAY,CAAC;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,8EAA8E;IAC9E,iBAAiB,CAAC,EAAE,OAAO,qBAAqB,EAAE,sBAAsB,CAAC;CAC1E;AAyBD,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,kBAAkB,CAAqB;gBAEnC,MAAM,EAAE,oBAAoB;IAaxC;;;OAGG;IACH,gBAAgB,IAAI,IAAI;IAKxB,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,sBAAsB;YAIhB,0BAA0B;IAaxC;;;;;;;;;OASG;YACW,qBAAqB;IAyDnC;;;OAGG;YACW,mBAAmB;IAiGjC,OAAO,CAAC,uBAAuB;IAkCzB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC;IA+P9E,+EAA+E;IACzE,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,gBAAgB,CAAC;YAsGzE,kBAAkB;YA2ClB,kBAAkB;YAmElB,uBAAuB;IAmBrC;;;;;;OAMG;IACH,OAAO,CAAC,aAAa;CAetB"}
@@ -411,31 +411,7 @@ export class RenderPipeline {
411
411
  for (const [layoutId, props] of dataResolution.layoutProps.entries()) {
412
412
  layoutProps[layoutId] = props;
413
413
  }
414
- let frontmatter = {};
415
- let headings = [];
416
- if (pageType === "mdx") {
417
- try {
418
- const bundleResult = await this.config.pageRenderer.preparePageBundles(pageInfo, slug, undefined, {
419
- ...options,
420
- ...(Object.keys(params).length > 0 ? { params } : {}),
421
- });
422
- if (bundleResult.pageBundle && "frontmatter" in bundleResult.pageBundle) {
423
- frontmatter =
424
- bundleResult.pageBundle.frontmatter ||
425
- {};
426
- }
427
- if (bundleResult.pageBundle && "headings" in bundleResult.pageBundle) {
428
- headings = bundleResult.pageBundle.headings || [];
429
- }
430
- }
431
- catch (error) {
432
- renderPipelineLog.error("Frontmatter/headings extraction failed", {
433
- slug,
434
- error: error instanceof Error ? error.message : String(error),
435
- stack: error instanceof Error ? error.stack : undefined,
436
- });
437
- }
438
- }
414
+ const { frontmatter, headings } = await this.extractMdxMetadata(pageType, pageInfo, slug, options, params);
439
415
  const layouts = layoutResult.nestedLayouts
440
416
  .filter((l) => l.componentPath || l.path)
441
417
  .map((l) => ({
@@ -457,58 +433,7 @@ export class RenderPipeline {
457
433
  break;
458
434
  }
459
435
  }
460
- let css;
461
- let cssError;
462
- const cssCacheKey = getPageCssCacheKey(options?.projectId, options?.environment, slug, projectUpdatedAt);
463
- const cachedCss = getCachedPageCss(cssCacheKey);
464
- if (cachedCss) {
465
- css = cachedCss;
466
- resolvePageDataLog.debug("CSS cache hit", { slug, cssLength: css.length });
467
- }
468
- else {
469
- try {
470
- const renderResult = await withTimeout(this.renderPage(slug, {
471
- ...options,
472
- delivery: "string",
473
- skipCacheCheck: true,
474
- skipCachePersist: true,
475
- }), CSS_SSR_TIMEOUT_MS, `CSS SSR for ${slug}`);
476
- if (renderResult?.html) {
477
- css = await this.resolveCssFromRenderedHtml(renderResult.html, options?.projectSlug ?? options?.projectId);
478
- if (css) {
479
- resolvePageDataLog.debug("Reused SSR CSS for page data", {
480
- slug,
481
- cssLength: css.length,
482
- source: "rendered-html-hash",
483
- });
484
- }
485
- else {
486
- const candidates = extractCandidates(renderResult.html);
487
- css = (await generateTailwindCSS(undefined, candidates, {
488
- projectSlug: options?.projectSlug,
489
- })).css;
490
- resolvePageDataLog.debug("Fell back to HTML candidate CSS generation", {
491
- slug,
492
- htmlLength: renderResult.html.length,
493
- cssLength: css?.length || 0,
494
- });
495
- }
496
- if (css)
497
- cachePageCss(cssCacheKey, css);
498
- }
499
- }
500
- catch (error) {
501
- const errorMessage = error instanceof Error ? error.message : String(error);
502
- // Surface CSS generation failures instead of silently swallowing them.
503
- // This allows clients to show a warning or fall back gracefully.
504
- cssError = `CSS generation failed: ${errorMessage}`;
505
- resolvePageDataLog.error("CSS generation failed", {
506
- slug,
507
- error: errorMessage,
508
- projectId: options?.projectId,
509
- });
510
- }
511
- }
436
+ const { css, cssError } = await this.resolvePageDataCss(slug, options, projectUpdatedAt);
512
437
  resolvePageDataLog.debug("Resolved page data", {
513
438
  slug,
514
439
  pagePath,
@@ -536,6 +461,93 @@ export class RenderPipeline {
536
461
  cssError,
537
462
  };
538
463
  }
464
+ async extractMdxMetadata(pageType, pageInfo, slug, options, params) {
465
+ if (pageType !== "mdx") {
466
+ return { frontmatter: {}, headings: [] };
467
+ }
468
+ try {
469
+ const bundleResult = await this.config.pageRenderer.preparePageBundles(pageInfo, slug, undefined, {
470
+ ...options,
471
+ ...(Object.keys(params).length > 0 ? { params } : {}),
472
+ });
473
+ const pageBundle = bundleResult.pageBundle;
474
+ return {
475
+ frontmatter: pageBundle && "frontmatter" in pageBundle
476
+ ? pageBundle.frontmatter || {}
477
+ : {},
478
+ headings: pageBundle && "headings" in pageBundle
479
+ ? pageBundle.headings || []
480
+ : [],
481
+ };
482
+ }
483
+ catch (error) {
484
+ renderPipelineLog.error("Frontmatter/headings extraction failed", {
485
+ slug,
486
+ error: error instanceof Error ? error.message : String(error),
487
+ stack: error instanceof Error ? error.stack : undefined,
488
+ });
489
+ return { frontmatter: {}, headings: [] };
490
+ }
491
+ }
492
+ async resolvePageDataCss(slug, options, projectUpdatedAt) {
493
+ const cssCacheKey = getPageCssCacheKey(options?.projectId, options?.environment, slug, projectUpdatedAt);
494
+ const cachedCss = getCachedPageCss(cssCacheKey);
495
+ if (cachedCss) {
496
+ resolvePageDataLog.debug("CSS cache hit", { slug, cssLength: cachedCss.length });
497
+ return { css: cachedCss, cssError: undefined };
498
+ }
499
+ try {
500
+ const renderResult = await withTimeout(this.renderPage(slug, {
501
+ ...options,
502
+ delivery: "string",
503
+ skipCacheCheck: true,
504
+ skipCachePersist: true,
505
+ }), CSS_SSR_TIMEOUT_MS, `CSS SSR for ${slug}`);
506
+ if (!renderResult?.html) {
507
+ return { css: undefined, cssError: undefined };
508
+ }
509
+ let css = await this.resolveCssFromRenderedHtml(renderResult.html, options?.projectSlug ?? options?.projectId);
510
+ if (css) {
511
+ resolvePageDataLog.debug("Reused SSR CSS for page data", {
512
+ slug,
513
+ cssLength: css.length,
514
+ source: "rendered-html-hash",
515
+ });
516
+ }
517
+ else {
518
+ css = await this.generatePageCssFromHtml(slug, renderResult.html, options);
519
+ }
520
+ if (css)
521
+ cachePageCss(cssCacheKey, css);
522
+ return { css, cssError: undefined };
523
+ }
524
+ catch (error) {
525
+ const errorMessage = error instanceof Error ? error.message : String(error);
526
+ // Surface CSS generation failures instead of silently swallowing them.
527
+ // This allows clients to show a warning or fall back gracefully.
528
+ resolvePageDataLog.error("CSS generation failed", {
529
+ slug,
530
+ error: errorMessage,
531
+ projectId: options?.projectId,
532
+ });
533
+ return {
534
+ css: undefined,
535
+ cssError: `CSS generation failed: ${errorMessage}`,
536
+ };
537
+ }
538
+ }
539
+ async generatePageCssFromHtml(slug, html, options) {
540
+ const candidates = extractCandidates(html);
541
+ const generatedCss = (await generateTailwindCSS(undefined, candidates, {
542
+ projectSlug: options?.projectSlug,
543
+ })).css;
544
+ resolvePageDataLog.debug("Fell back to HTML candidate CSS generation", {
545
+ slug,
546
+ htmlLength: html.length,
547
+ cssLength: generatedCss?.length || 0,
548
+ });
549
+ return generatedCss;
550
+ }
539
551
  /**
540
552
  * Build a cache key that is safe for multi-tenant + query-param aware caching.
541
553
  * Returns null when request contains sensitive headers (Authorization/Cookie) and
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "0.1.177";
1
+ export declare const VERSION = "0.1.179";
2
2
  //# sourceMappingURL=version-constant.d.ts.map
@@ -1,3 +1,3 @@
1
1
  // Keep in sync with deno.json version.
2
2
  // scripts/release.ts updates this constant during releases.
3
- export const VERSION = "0.1.177";
3
+ export const VERSION = "0.1.179";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "veryfront",
3
- "version": "0.1.177",
3
+ "version": "0.1.179",
4
4
  "description": "The simplest way to build AI-powered apps",
5
5
  "keywords": [
6
6
  "react",
package/src/deno.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export default {
2
2
  "name": "veryfront",
3
- "version": "0.1.177",
3
+ "version": "0.1.179",
4
4
  "license": "Apache-2.0",
5
5
  "nodeModulesDir": "auto",
6
6
  "exclude": [
@@ -111,6 +111,16 @@ interface DataResolutionResult {
111
111
  layoutProps: Map<string, Record<string, unknown>>;
112
112
  }
113
113
 
114
+ interface MdxMetadataResult {
115
+ frontmatter: Record<string, unknown>;
116
+ headings: Array<{ id: string; text: string; level: number }>;
117
+ }
118
+
119
+ interface PageCssResult {
120
+ css: string | undefined;
121
+ cssError: string | undefined;
122
+ }
123
+
114
124
  interface FetchedDataResult {
115
125
  type: "page" | "layout";
116
126
  id: string;
@@ -660,39 +670,13 @@ export class RenderPipeline {
660
670
  layoutProps[layoutId] = props;
661
671
  }
662
672
 
663
- let frontmatter: Record<string, unknown> = {};
664
- let headings: Array<{ id: string; text: string; level: number }> = [];
665
- if (pageType === "mdx") {
666
- try {
667
- const bundleResult = await this.config.pageRenderer.preparePageBundles(
668
- pageInfo,
669
- slug,
670
- undefined,
671
- {
672
- ...options,
673
- ...(Object.keys(params).length > 0 ? { params } : {}),
674
- },
675
- );
676
-
677
- if (bundleResult.pageBundle && "frontmatter" in bundleResult.pageBundle) {
678
- frontmatter =
679
- (bundleResult.pageBundle as { frontmatter?: Record<string, unknown> }).frontmatter ||
680
- {};
681
- }
682
-
683
- if (bundleResult.pageBundle && "headings" in bundleResult.pageBundle) {
684
- headings = (bundleResult.pageBundle as {
685
- headings?: Array<{ id: string; text: string; level: number }>;
686
- }).headings || [];
687
- }
688
- } catch (error) {
689
- renderPipelineLog.error("Frontmatter/headings extraction failed", {
690
- slug,
691
- error: error instanceof Error ? error.message : String(error),
692
- stack: error instanceof Error ? error.stack : undefined,
693
- });
694
- }
695
- }
673
+ const { frontmatter, headings } = await this.extractMdxMetadata(
674
+ pageType,
675
+ pageInfo,
676
+ slug,
677
+ options,
678
+ params,
679
+ );
696
680
 
697
681
  const layouts = layoutResult.nestedLayouts
698
682
  .filter((l: LayoutItem) => l.componentPath || l.path)
@@ -721,71 +705,7 @@ export class RenderPipeline {
721
705
  }
722
706
  }
723
707
 
724
- let css: string | undefined;
725
- let cssError: string | undefined;
726
- const cssCacheKey = getPageCssCacheKey(
727
- options?.projectId,
728
- options?.environment,
729
- slug,
730
- projectUpdatedAt,
731
- );
732
-
733
- const cachedCss = getCachedPageCss(cssCacheKey);
734
- if (cachedCss) {
735
- css = cachedCss;
736
- resolvePageDataLog.debug("CSS cache hit", { slug, cssLength: css.length });
737
- } else {
738
- try {
739
- const renderResult = await withTimeout(
740
- this.renderPage(slug, {
741
- ...options,
742
- delivery: "string",
743
- skipCacheCheck: true,
744
- skipCachePersist: true,
745
- }),
746
- CSS_SSR_TIMEOUT_MS,
747
- `CSS SSR for ${slug}`,
748
- );
749
-
750
- if (renderResult?.html) {
751
- css = await this.resolveCssFromRenderedHtml(
752
- renderResult.html,
753
- options?.projectSlug ?? options?.projectId,
754
- );
755
-
756
- if (css) {
757
- resolvePageDataLog.debug("Reused SSR CSS for page data", {
758
- slug,
759
- cssLength: css.length,
760
- source: "rendered-html-hash",
761
- });
762
- } else {
763
- const candidates = extractCandidates(renderResult.html);
764
- css = (await generateTailwindCSS(undefined, candidates, {
765
- projectSlug: options?.projectSlug,
766
- })).css;
767
-
768
- resolvePageDataLog.debug("Fell back to HTML candidate CSS generation", {
769
- slug,
770
- htmlLength: renderResult.html.length,
771
- cssLength: css?.length || 0,
772
- });
773
- }
774
-
775
- if (css) cachePageCss(cssCacheKey, css);
776
- }
777
- } catch (error) {
778
- const errorMessage = error instanceof Error ? error.message : String(error);
779
- // Surface CSS generation failures instead of silently swallowing them.
780
- // This allows clients to show a warning or fall back gracefully.
781
- cssError = `CSS generation failed: ${errorMessage}`;
782
- resolvePageDataLog.error("CSS generation failed", {
783
- slug,
784
- error: errorMessage,
785
- projectId: options?.projectId,
786
- });
787
- }
788
- }
708
+ const { css, cssError } = await this.resolvePageDataCss(slug, options, projectUpdatedAt);
789
709
 
790
710
  resolvePageDataLog.debug("Resolved page data", {
791
711
  slug,
@@ -816,6 +736,135 @@ export class RenderPipeline {
816
736
  };
817
737
  }
818
738
 
739
+ private async extractMdxMetadata(
740
+ pageType: PageDataResponse["pageType"],
741
+ pageInfo: Awaited<ReturnType<PageResolver["resolvePage"]>>,
742
+ slug: string,
743
+ options: RenderOptions | undefined,
744
+ params: Record<string, string | string[]>,
745
+ ): Promise<MdxMetadataResult> {
746
+ if (pageType !== "mdx") {
747
+ return { frontmatter: {}, headings: [] };
748
+ }
749
+
750
+ try {
751
+ const bundleResult = await this.config.pageRenderer.preparePageBundles(
752
+ pageInfo,
753
+ slug,
754
+ undefined,
755
+ {
756
+ ...options,
757
+ ...(Object.keys(params).length > 0 ? { params } : {}),
758
+ },
759
+ );
760
+
761
+ const pageBundle = bundleResult.pageBundle;
762
+ return {
763
+ frontmatter: pageBundle && "frontmatter" in pageBundle
764
+ ? (pageBundle as { frontmatter?: Record<string, unknown> }).frontmatter || {}
765
+ : {},
766
+ headings: pageBundle && "headings" in pageBundle
767
+ ? (pageBundle as {
768
+ headings?: Array<{ id: string; text: string; level: number }>;
769
+ }).headings || []
770
+ : [],
771
+ };
772
+ } catch (error) {
773
+ renderPipelineLog.error("Frontmatter/headings extraction failed", {
774
+ slug,
775
+ error: error instanceof Error ? error.message : String(error),
776
+ stack: error instanceof Error ? error.stack : undefined,
777
+ });
778
+ return { frontmatter: {}, headings: [] };
779
+ }
780
+ }
781
+
782
+ private async resolvePageDataCss(
783
+ slug: string,
784
+ options: RenderOptions | undefined,
785
+ projectUpdatedAt: string | undefined,
786
+ ): Promise<PageCssResult> {
787
+ const cssCacheKey = getPageCssCacheKey(
788
+ options?.projectId,
789
+ options?.environment,
790
+ slug,
791
+ projectUpdatedAt,
792
+ );
793
+
794
+ const cachedCss = getCachedPageCss(cssCacheKey);
795
+ if (cachedCss) {
796
+ resolvePageDataLog.debug("CSS cache hit", { slug, cssLength: cachedCss.length });
797
+ return { css: cachedCss, cssError: undefined };
798
+ }
799
+
800
+ try {
801
+ const renderResult = await withTimeout(
802
+ this.renderPage(slug, {
803
+ ...options,
804
+ delivery: "string",
805
+ skipCacheCheck: true,
806
+ skipCachePersist: true,
807
+ }),
808
+ CSS_SSR_TIMEOUT_MS,
809
+ `CSS SSR for ${slug}`,
810
+ );
811
+
812
+ if (!renderResult?.html) {
813
+ return { css: undefined, cssError: undefined };
814
+ }
815
+
816
+ let css = await this.resolveCssFromRenderedHtml(
817
+ renderResult.html,
818
+ options?.projectSlug ?? options?.projectId,
819
+ );
820
+
821
+ if (css) {
822
+ resolvePageDataLog.debug("Reused SSR CSS for page data", {
823
+ slug,
824
+ cssLength: css.length,
825
+ source: "rendered-html-hash",
826
+ });
827
+ } else {
828
+ css = await this.generatePageCssFromHtml(slug, renderResult.html, options);
829
+ }
830
+
831
+ if (css) cachePageCss(cssCacheKey, css);
832
+ return { css, cssError: undefined };
833
+ } catch (error) {
834
+ const errorMessage = error instanceof Error ? error.message : String(error);
835
+ // Surface CSS generation failures instead of silently swallowing them.
836
+ // This allows clients to show a warning or fall back gracefully.
837
+ resolvePageDataLog.error("CSS generation failed", {
838
+ slug,
839
+ error: errorMessage,
840
+ projectId: options?.projectId,
841
+ });
842
+ return {
843
+ css: undefined,
844
+ cssError: `CSS generation failed: ${errorMessage}`,
845
+ };
846
+ }
847
+ }
848
+
849
+ private async generatePageCssFromHtml(
850
+ slug: string,
851
+ html: string,
852
+ options: RenderOptions | undefined,
853
+ ): Promise<string | undefined> {
854
+ const candidates = extractCandidates(html);
855
+ const generatedCss = (await generateTailwindCSS(undefined, candidates, {
856
+ projectSlug: options?.projectSlug,
857
+ })).css;
858
+
859
+ resolvePageDataLog.debug("Fell back to HTML candidate CSS generation", {
860
+ slug,
861
+ htmlLength: html.length,
862
+ cssLength: generatedCss?.length || 0,
863
+ });
864
+
865
+ return generatedCss;
866
+ }
867
+
819
868
  /**
820
869
  * Build a cache key that is safe for multi-tenant + query-param aware caching.
821
870
  * Returns null when request contains sensitive headers (Authorization/Cookie) and
@@ -1,3 +1,3 @@
1
1
  // Keep in sync with deno.json version.
2
2
  // scripts/release.ts updates this constant during releases.
3
- export const VERSION = "0.1.177";
3
+ export const VERSION = "0.1.179";