veryfront 0.1.72 → 0.1.73

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.
Files changed (34) hide show
  1. package/esm/deno.js +1 -1
  2. package/esm/src/html/html-shell-generator.d.ts.map +1 -1
  3. package/esm/src/html/html-shell-generator.js +6 -0
  4. package/esm/src/rendering/orchestrator/pipeline.d.ts.map +1 -1
  5. package/esm/src/rendering/orchestrator/pipeline.js +116 -105
  6. package/esm/src/server/dev-server/error-overlay/error-formatter.d.ts +4 -0
  7. package/esm/src/server/dev-server/error-overlay/error-formatter.d.ts.map +1 -1
  8. package/esm/src/server/dev-server/error-overlay/error-formatter.js +15 -0
  9. package/esm/src/server/dev-server/error-overlay/html-template.d.ts +1 -1
  10. package/esm/src/server/dev-server/error-overlay/html-template.d.ts.map +1 -1
  11. package/esm/src/server/dev-server/error-overlay/html-template.js +131 -8
  12. package/esm/src/server/dev-server/error-overlay/index.d.ts +1 -1
  13. package/esm/src/server/dev-server/error-overlay/index.d.ts.map +1 -1
  14. package/esm/src/server/dev-server/error-overlay/index.js +1 -1
  15. package/esm/src/server/dev-server/error-overlay/overlay-renderer.d.ts +1 -1
  16. package/esm/src/server/dev-server/error-overlay/overlay-renderer.d.ts.map +1 -1
  17. package/esm/src/server/dev-server/error-overlay/overlay-renderer.js +2 -2
  18. package/esm/src/server/dev-server/request-handler.d.ts.map +1 -1
  19. package/esm/src/server/dev-server/request-handler.js +6 -2
  20. package/esm/src/server/services/rendering/ssr.service.d.ts.map +1 -1
  21. package/esm/src/server/services/rendering/ssr.service.js +9 -2
  22. package/esm/src/server/utils/error-html.d.ts.map +1 -1
  23. package/esm/src/server/utils/error-html.js +26 -6
  24. package/package.json +1 -1
  25. package/src/deno.js +1 -1
  26. package/src/src/html/html-shell-generator.ts +9 -0
  27. package/src/src/rendering/orchestrator/pipeline.ts +186 -172
  28. package/src/src/server/dev-server/error-overlay/error-formatter.ts +21 -0
  29. package/src/src/server/dev-server/error-overlay/html-template.ts +139 -8
  30. package/src/src/server/dev-server/error-overlay/index.ts +1 -0
  31. package/src/src/server/dev-server/error-overlay/overlay-renderer.ts +2 -1
  32. package/src/src/server/dev-server/request-handler.ts +6 -2
  33. package/src/src/server/services/rendering/ssr.service.ts +9 -2
  34. package/src/src/server/utils/error-html.ts +29 -6
@@ -1 +1 @@
1
- {"version":3,"file":"ssr.service.d.ts","sourceRoot":"","sources":["../../../../../src/src/server/services/rendering/ssr.service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,2BAA2B,CAAC;AACrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAEL,KAAK,eAAe,EAErB,MAAM,kCAAkC,CAAC;AAmB1C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAItE,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC;IACpC,WAAW,EAAE,OAAO,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,UAAU,GAAG,OAAO,CAAC;IACpC,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,SAAS,CAAC,EAAE,WAAW,GAAG,YAAY,GAAG,cAAc,GAAG,SAAS,CAAC;IACpE,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC;IACzB,GAAG,EAAE,GAAG,CAAC;IACT,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,OAAO,CAAC;IACf,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAE,OAAO,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,CAA0B;gBAEzC,OAAO,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,eAAe,CAAC,MAAM,CAAC,CAAA;KAAE;IAI7D,mBAAmB,IAAI,YAAY;IAW7B,WAAW,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC;IAI1D,UAAU,CAAC,GAAG,EAAE,cAAc,EAAE,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC;IAiG1F,OAAO,CAAC,iBAAiB;IAqFzB,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe;CAS1D"}
1
+ {"version":3,"file":"ssr.service.d.ts","sourceRoot":"","sources":["../../../../../src/src/server/services/rendering/ssr.service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,2BAA2B,CAAC;AACrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAEL,KAAK,eAAe,EAErB,MAAM,kCAAkC,CAAC;AAmB1C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAItE,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC;IACpC,WAAW,EAAE,OAAO,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,UAAU,GAAG,OAAO,CAAC;IACpC,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,SAAS,CAAC,EAAE,WAAW,GAAG,YAAY,GAAG,cAAc,GAAG,SAAS,CAAC;IACpE,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC;IACzB,GAAG,EAAE,GAAG,CAAC;IACT,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,OAAO,CAAC;IACf,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAE,OAAO,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,CAA0B;gBAEzC,OAAO,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,eAAe,CAAC,MAAM,CAAC,CAAA;KAAE;IAI7D,mBAAmB,IAAI,YAAY;IAW7B,WAAW,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC;IAI1D,UAAU,CAAC,GAAG,EAAE,cAAc,EAAE,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC;IAiG1F,OAAO,CAAC,iBAAiB;IA4FzB,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe;CAS1D"}
@@ -6,7 +6,7 @@ import { VeryfrontError } from "../../../errors/index.js";
6
6
  import { getColorSchemeFromRequest } from "../../../security/http/client-hints.js";
7
7
  import { endRenderSession, startRenderSession, } from "../../../transforms/mdx/esm-module-loader/module-fetcher/index.js";
8
8
  import { getErrorCollector } from "../../../observability/error-collector.js";
9
- import { ErrorOverlay } from "../../dev-server/error-overlay/index.js";
9
+ import { ErrorOverlay, parseErrorLocation } from "../../dev-server/error-overlay/index.js";
10
10
  import { ErrorPages } from "../../utils/error-html.js";
11
11
  import { HTTP_INTERNAL_SERVER_ERROR, HTTP_NOT_FOUND, HTTP_OK, HTTP_UNAVAILABLE, } from "../../../utils/constants/index.js";
12
12
  const logger = serverLogger.component("ssr-service");
@@ -150,9 +150,16 @@ export class SSRService {
150
150
  url: request.url,
151
151
  slug,
152
152
  });
153
+ const sourceFile = errorObj.sourceFile;
154
+ const location = sourceFile ? parseErrorLocation(errorObj, sourceFile) : {};
153
155
  return {
154
156
  status: HTTP_INTERNAL_SERVER_ERROR,
155
- html: ErrorOverlay.createHTML({ error: errorObj, type: "runtime" }),
157
+ html: ErrorOverlay.createHTML({
158
+ error: errorObj,
159
+ type: "runtime",
160
+ ...(sourceFile ? { file: sourceFile } : {}),
161
+ ...location,
162
+ }, ctx.projectSlug),
156
163
  isStreaming: false,
157
164
  cacheStrategy: "no-cache",
158
165
  error: errorObj,
@@ -1 +1 @@
1
- {"version":3,"file":"error-html.d.ts","sourceRoot":"","sources":["../../../../src/src/server/utils/error-html.ts"],"names":[],"mappings":"AAAA,UAAU,gBAAgB;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,gDAAgD;IAChD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sDAAsD;IACtD,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,gBAAgB,GAAG,MAAM,CAQnE;AAuFD,eAAO,MAAM,UAAU;wBACD,MAAM,GAAG,MAAM;0BAUb,MAAM,GAAG,MAAM;kBAQvB,MAAM;sBAQF,MAAM;CAOzB,CAAC"}
1
+ {"version":3,"file":"error-html.d.ts","sourceRoot":"","sources":["../../../../src/src/server/utils/error-html.ts"],"names":[],"mappings":"AAEA,UAAU,gBAAgB;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,gDAAgD;IAChD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sDAAsD;IACtD,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,gBAAgB,GAAG,MAAM,CAQnE;AA4GD,eAAO,MAAM,UAAU;wBACD,MAAM,GAAG,MAAM;0BAUb,MAAM,GAAG,MAAM;kBAQvB,MAAM;sBAQF,MAAM;CAOzB,CAAC"}
@@ -1,3 +1,4 @@
1
+ import { escapeHTML } from "../../html/html-escape.js";
1
2
  export function generateErrorHtml(options) {
2
3
  const { statusCode, title, message, pathname, minimal } = options;
3
4
  if (minimal) {
@@ -6,13 +7,16 @@ export function generateErrorHtml(options) {
6
7
  return generateStyledErrorHtml(statusCode, title, message);
7
8
  }
8
9
  function generateStyledErrorHtml(statusCode, title, message) {
10
+ const errorMessage = title === "Not Found" ? `Page not found: ${message}` : message;
11
+ // 4xx = warning (routing/config issue), 5xx = error (something broke)
12
+ const errorType = statusCode >= 500 ? "error" : "warning";
9
13
  return `<!DOCTYPE html>
10
14
  <html lang="en">
11
15
  <head>
12
16
  <meta charset="utf-8">
13
17
  <meta name="viewport" content="width=device-width">
14
18
  <link rel="icon" type="image/png" href="https://cdn.veryfront.com/images/veryfront-favicon.png">
15
- <title>${statusCode} ${title} — Veryfront</title>
19
+ <title>${statusCode} ${escapeHTML(title)} — Veryfront</title>
16
20
  <style>
17
21
  :root {
18
22
  --bg: #ffffff;
@@ -61,9 +65,25 @@ function generateStyledErrorHtml(statusCode, title, message) {
61
65
  </head>
62
66
  <body>
63
67
  <div class="container">
64
- <h1 class="title">${title}</h1>
65
- <p class="message">${message}</p>
68
+ <h1 class="title">${escapeHTML(title)}</h1>
69
+ <p class="message">${escapeHTML(message)}</p>
66
70
  </div>
71
+ <script>
72
+ if (window.parent !== window) {
73
+ try {
74
+ window.parent.postMessage({
75
+ action: 'appUpdated',
76
+ isInitialLoad: true,
77
+ hasError: true,
78
+ url: window.location.href,
79
+ errors: [{
80
+ type: '${errorType}',
81
+ message: ${JSON.stringify(errorMessage).replace(/</g, "\\u003c")}
82
+ }]
83
+ }, '*');
84
+ } catch (e) { /* postMessage may fail in cross-origin iframes */ }
85
+ }
86
+ </script>
67
87
  </body>
68
88
  </html>`;
69
89
  }
@@ -74,11 +94,11 @@ function generateMinimalErrorHtml(statusCode, title, message, pathname) {
74
94
  <head>
75
95
  <meta charset="utf-8"/>
76
96
  <meta name="viewport" content="width=device-width, initial-scale=1"/>
77
- <title>${statusCode} ${title}</title>
97
+ <title>${statusCode} ${escapeHTML(title)}</title>
78
98
  </head>
79
99
  <body>
80
- <h1>${statusCode} ${title}</h1>
81
- <p>${fullMessage}</p>
100
+ <h1>${statusCode} ${escapeHTML(title)}</h1>
101
+ <p>${escapeHTML(fullMessage)}</p>
82
102
  </body>
83
103
  </html>`;
84
104
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "veryfront",
3
- "version": "0.1.72",
3
+ "version": "0.1.73",
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.72",
3
+ "version": "0.1.73",
4
4
  "license": "Apache-2.0",
5
5
  "nodeModulesDir": "auto",
6
6
  "exclude": [
@@ -208,6 +208,14 @@ async function generateHTMLShellPartsImpl(
208
208
 
209
209
  const nonceAttr = nonce ? ` nonce="${nonce}"` : "";
210
210
 
211
+ // Expose project slug for runtime error overlay "Fix in Veryfront" button
212
+ const overlaySlug = options.projectId || meta.slug;
213
+ const slugForOverlay = useDevScripts && overlaySlug
214
+ ? `<script${nonceAttr}>window.__VF_PROJECT_SLUG__=${
215
+ JSON.stringify(overlaySlug).replace(/</g, "\\u003c")
216
+ };</script>`
217
+ : "";
218
+
211
219
  const hydrationErrorSuppression = useDevScripts ? "" : `<script${nonceAttr}>
212
220
  (function(){
213
221
  var origError = console.error;
@@ -303,6 +311,7 @@ async function generateHTMLShellPartsImpl(
303
311
  ${linkTags}
304
312
  ${styleTags}
305
313
  ${modeStyles}
314
+ ${slugForOverlay}
306
315
  </head>
307
316
  <body${bodyClass ? ` class="${bodyClass}"` : ""} suppressHydrationWarning>
308
317
  <div ${rootAttributes}>`;
@@ -391,191 +391,205 @@ export class RenderPipeline {
391
391
  );
392
392
  timing.pageResolve = Math.round(performance.now() - pageResolveStart);
393
393
 
394
- const skipLayouts = isDotPath(slug, pageInfo.entity.path);
395
-
396
- const layoutCollectStart = performance.now();
397
- const layoutResult = skipLayouts ? EMPTY_LAYOUT_RESULT : await withSpan(
398
- "render.collect_layouts",
399
- () => this.config.layoutOrchestrator.collectLayouts(pageInfo),
400
- { "render.slug": slug },
394
+ const sourceFile = extractRelativePathShared(
395
+ pageInfo.entity.path,
396
+ this.config.projectDir,
401
397
  );
402
- timing.layoutCollect = Math.round(performance.now() - layoutCollectStart);
403
-
404
- const layoutPreloadPromise = !skipLayouts && layoutResult.nestedLayouts.length > 0
405
- ? this.config.layoutOrchestrator.preloadLayoutModules(layoutResult.nestedLayouts)
406
- : Promise.resolve();
407
-
408
- let dataFetchingProps: Record<string, unknown> | undefined;
409
- let resolvedParams: Record<string, string | string[]> = options?.params
410
- ? { ...options.params }
411
- : {};
412
- let layoutDataMap = new Map<string, Record<string, unknown>>();
413
-
414
- const dataFetchStart = performance.now();
415
- if (options?.request && options?.url) {
416
- await withSpan(
417
- "render.data_fetching",
418
- async () => {
419
- try {
420
- const dataResolution = await this.resolveDataFetching(
421
- slug,
422
- pageInfo.entity.path,
423
- layoutResult.nestedLayouts,
424
- options,
425
- );
426
- resolvedParams = dataResolution.params;
427
- dataFetchingProps = Object.keys(dataResolution.pageProps).length > 0
428
- ? dataResolution.pageProps
429
- : undefined;
430
- layoutDataMap = dataResolution.layoutProps;
431
- } catch (error) {
432
- if (error instanceof VeryfrontError) throw error;
433
-
434
- renderPageLog.error("Data fetching error", {
435
- slug,
436
- error: error instanceof Error ? error.message : String(error),
437
- });
438
- throw error;
439
- }
440
- },
398
+
399
+ try {
400
+ const skipLayouts = isDotPath(slug, pageInfo.entity.path);
401
+
402
+ const layoutCollectStart = performance.now();
403
+ const layoutResult = skipLayouts ? EMPTY_LAYOUT_RESULT : await withSpan(
404
+ "render.collect_layouts",
405
+ () => this.config.layoutOrchestrator.collectLayouts(pageInfo),
441
406
  { "render.slug": slug },
442
407
  );
443
- }
444
- timing.dataFetch = Math.round(performance.now() - dataFetchStart);
445
-
446
- const hasResolvedParams = Object.keys(resolvedParams).length > 0;
447
- const mergedOptions = (dataFetchingProps || hasResolvedParams)
448
- ? {
449
- ...options,
450
- ...(hasResolvedParams ? { params: resolvedParams } : {}),
451
- ...(dataFetchingProps ? { props: { ...options?.props, ...dataFetchingProps } } : {}),
408
+ timing.layoutCollect = Math.round(performance.now() - layoutCollectStart);
409
+
410
+ const layoutPreloadPromise = !skipLayouts && layoutResult.nestedLayouts.length > 0
411
+ ? this.config.layoutOrchestrator.preloadLayoutModules(layoutResult.nestedLayouts)
412
+ : Promise.resolve();
413
+
414
+ let dataFetchingProps: Record<string, unknown> | undefined;
415
+ let resolvedParams: Record<string, string | string[]> = options?.params
416
+ ? { ...options.params }
417
+ : {};
418
+ let layoutDataMap = new Map<string, Record<string, unknown>>();
419
+
420
+ const dataFetchStart = performance.now();
421
+ if (options?.request && options?.url) {
422
+ await withSpan(
423
+ "render.data_fetching",
424
+ async () => {
425
+ try {
426
+ const dataResolution = await this.resolveDataFetching(
427
+ slug,
428
+ pageInfo.entity.path,
429
+ layoutResult.nestedLayouts,
430
+ options,
431
+ );
432
+ resolvedParams = dataResolution.params;
433
+ dataFetchingProps = Object.keys(dataResolution.pageProps).length > 0
434
+ ? dataResolution.pageProps
435
+ : undefined;
436
+ layoutDataMap = dataResolution.layoutProps;
437
+ } catch (error) {
438
+ if (error instanceof VeryfrontError) throw error;
439
+
440
+ renderPageLog.error("Data fetching error", {
441
+ slug,
442
+ error: error instanceof Error ? error.message : String(error),
443
+ });
444
+ throw error;
445
+ }
446
+ },
447
+ { "render.slug": slug },
448
+ );
452
449
  }
453
- : options;
454
-
455
- const bundlePrepStart = performance.now();
456
- const pageBundleResult = await withSpan(
457
- "render.prepare_bundles",
458
- () =>
459
- this.config.pageRenderer.preparePageBundles(
460
- pageInfo,
461
- slug,
462
- cacheResult?.cachedModule,
463
- mergedOptions,
464
- ),
465
- { "render.slug": slug },
466
- );
467
- timing.bundlePrep = Math.round(performance.now() - bundlePrepStart);
450
+ timing.dataFetch = Math.round(performance.now() - dataFetchStart);
451
+
452
+ const hasResolvedParams = Object.keys(resolvedParams).length > 0;
453
+ const mergedOptions = (dataFetchingProps || hasResolvedParams)
454
+ ? {
455
+ ...options,
456
+ ...(hasResolvedParams ? { params: resolvedParams } : {}),
457
+ ...(dataFetchingProps
458
+ ? { props: { ...options?.props, ...dataFetchingProps } }
459
+ : {}),
460
+ }
461
+ : options;
462
+
463
+ const bundlePrepStart = performance.now();
464
+ const pageBundleResult = await withSpan(
465
+ "render.prepare_bundles",
466
+ () =>
467
+ this.config.pageRenderer.preparePageBundles(
468
+ pageInfo,
469
+ slug,
470
+ cacheResult?.cachedModule,
471
+ mergedOptions,
472
+ ),
473
+ { "render.slug": slug },
474
+ );
475
+ timing.bundlePrep = Math.round(performance.now() - bundlePrepStart);
468
476
 
469
- if (pageBundleResult.scriptResult) return pageBundleResult.scriptResult;
477
+ if (pageBundleResult.scriptResult) return pageBundleResult.scriptResult;
470
478
 
471
- if (!pageBundleResult.pageElement || !pageBundleResult.pageBundle) {
472
- throw RENDER_ERROR.create({
473
- detail: "Failed to prepare page bundle",
474
- context: { slug },
475
- });
476
- }
479
+ if (!pageBundleResult.pageElement || !pageBundleResult.pageBundle) {
480
+ throw RENDER_ERROR.create({
481
+ detail: "Failed to prepare page bundle",
482
+ context: { slug },
483
+ });
484
+ }
477
485
 
478
- const { pageElement, pageBundle } = pageBundleResult;
479
-
480
- const mergedFrontmatter = {
481
- ...pageInfo.entity.frontmatter,
482
- ...(pageBundle as MdxBundle).frontmatter,
483
- };
484
-
485
- const headings = (pageBundle as PageBundle).headings || [];
486
-
487
- await layoutPreloadPromise;
488
-
489
- const layoutApplyStart = performance.now();
490
- const wrappedElement = await withSpan(
491
- "render.apply_layouts",
492
- () =>
493
- this.config.layoutOrchestrator.applyLayoutsAndWrappers(
494
- pageElement,
495
- pageInfo,
496
- layoutResult.layoutBundle,
497
- layoutResult.nestedLayouts,
498
- layoutDataMap,
499
- options?.url,
500
- mergedFrontmatter,
501
- headings,
502
- options?.projectSlug,
503
- ),
504
- { "render.slug": slug, "render.layout_count": layoutResult.nestedLayouts.length },
505
- );
506
- timing.layoutApply = Math.round(performance.now() - layoutApplyStart);
507
-
508
- // Snapshot CSS imports collected during module loading (before SSR rendering).
509
- // These are passed to the HTML generator to be included in the output.
510
- const collectedCSSImports = getCSSImports();
511
-
512
- const ssrStart = performance.now();
513
- const ssrResult = await withSpan(
514
- "render.ssr",
515
- () =>
516
- withTimeoutThrow(
517
- this.config.ssrOrchestrator.performSSRRendering(
518
- wrappedElement,
519
- {
520
- pageInfo,
521
- pageBundle,
522
- layoutBundle: layoutResult.layoutBundle,
523
- nestedLayouts: layoutResult.nestedLayouts,
524
- collectedMetadata: pageBundleResult.collectedMetadata,
525
- slug,
526
- cssImports: collectedCSSImports,
527
- },
528
- mergedOptions,
486
+ const { pageElement, pageBundle } = pageBundleResult;
487
+
488
+ const mergedFrontmatter = {
489
+ ...pageInfo.entity.frontmatter,
490
+ ...(pageBundle as MdxBundle).frontmatter,
491
+ };
492
+
493
+ const headings = (pageBundle as PageBundle).headings || [];
494
+
495
+ await layoutPreloadPromise;
496
+
497
+ const layoutApplyStart = performance.now();
498
+ const wrappedElement = await withSpan(
499
+ "render.apply_layouts",
500
+ () =>
501
+ this.config.layoutOrchestrator.applyLayoutsAndWrappers(
502
+ pageElement,
503
+ pageInfo,
504
+ layoutResult.layoutBundle,
505
+ layoutResult.nestedLayouts,
506
+ layoutDataMap,
507
+ options?.url,
508
+ mergedFrontmatter,
509
+ headings,
510
+ options?.projectSlug,
529
511
  ),
530
- SSR_RENDER_TIMEOUT_MS,
531
- `SSR rendering for ${slug}`,
532
- ),
533
- { "render.slug": slug, "render.delivery": mergedOptions?.delivery || "full" },
534
- );
535
- timing.ssr = Math.round(performance.now() - ssrStart);
512
+ { "render.slug": slug, "render.layout_count": layoutResult.nestedLayouts.length },
513
+ );
514
+ timing.layoutApply = Math.round(performance.now() - layoutApplyStart);
515
+
516
+ // Snapshot CSS imports collected during module loading (before SSR rendering).
517
+ // These are passed to the HTML generator to be included in the output.
518
+ const collectedCSSImports = getCSSImports();
519
+
520
+ const ssrStart = performance.now();
521
+ const ssrResult = await withSpan(
522
+ "render.ssr",
523
+ () =>
524
+ withTimeoutThrow(
525
+ this.config.ssrOrchestrator.performSSRRendering(
526
+ wrappedElement,
527
+ {
528
+ pageInfo,
529
+ pageBundle,
530
+ layoutBundle: layoutResult.layoutBundle,
531
+ nestedLayouts: layoutResult.nestedLayouts,
532
+ collectedMetadata: pageBundleResult.collectedMetadata,
533
+ slug,
534
+ cssImports: collectedCSSImports,
535
+ },
536
+ mergedOptions,
537
+ ),
538
+ SSR_RENDER_TIMEOUT_MS,
539
+ `SSR rendering for ${slug}`,
540
+ ),
541
+ { "render.slug": slug, "render.delivery": mergedOptions?.delivery || "full" },
542
+ );
543
+ timing.ssr = Math.round(performance.now() - ssrStart);
536
544
 
537
- if (collectedCSSImports.length > 0) {
538
- renderPipelineLog.debug("CSS imports collected for HTML generation", {
539
- slug,
540
- count: collectedCSSImports.length,
541
- paths: collectedCSSImports.map((p) => p.split("/").pop()),
542
- });
543
- }
545
+ if (collectedCSSImports.length > 0) {
546
+ renderPipelineLog.debug("CSS imports collected for HTML generation", {
547
+ slug,
548
+ count: collectedCSSImports.length,
549
+ paths: collectedCSSImports.map((p) => p.split("/").pop()),
550
+ });
551
+ }
544
552
 
545
- const pageModule = pageBundleResult.clientModuleCode && pageBundleResult.pageModuleType
546
- ? {
547
- slug,
548
- code: pageBundleResult.clientModuleCode,
549
- type: pageBundleResult.pageModuleType,
553
+ const pageModule = pageBundleResult.clientModuleCode && pageBundleResult.pageModuleType
554
+ ? {
555
+ slug,
556
+ code: pageBundleResult.clientModuleCode,
557
+ type: pageBundleResult.pageModuleType,
558
+ }
559
+ : undefined;
560
+
561
+ const result: RenderResult = {
562
+ html: ssrResult.fullHtml,
563
+ frontmatter: (pageBundleResult.pageBundle as MdxBundle).frontmatter || {},
564
+ headings: pageBundleResult.pageBundle.headings || [],
565
+ nodeMap: pageBundleResult.pageBundle.nodeMap,
566
+ stream: ssrResult.finalStream,
567
+ ssrHash: ssrResult.ssrHash,
568
+ ...(pageModule ? { pageModule } : {}),
569
+ };
570
+
571
+ if (shouldCache && !options?.skipCachePersist) {
572
+ void this.config.cacheCoordinator.persistResult(result, slug, cacheKey).catch(
573
+ (error) => {
574
+ renderPipelineLog.error("Cache persist failed", {
575
+ slug,
576
+ error: error instanceof Error ? error.message : String(error),
577
+ stack: error instanceof Error ? error.stack : undefined,
578
+ });
579
+ },
580
+ );
550
581
  }
551
- : undefined;
552
-
553
- const result: RenderResult = {
554
- html: ssrResult.fullHtml,
555
- frontmatter: (pageBundleResult.pageBundle as MdxBundle).frontmatter || {},
556
- headings: pageBundleResult.pageBundle.headings || [],
557
- nodeMap: pageBundleResult.pageBundle.nodeMap,
558
- stream: ssrResult.finalStream,
559
- ssrHash: ssrResult.ssrHash,
560
- ...(pageModule ? { pageModule } : {}),
561
- };
562
-
563
- if (shouldCache && !options?.skipCachePersist) {
564
- void this.config.cacheCoordinator.persistResult(result, slug, cacheKey).catch(
565
- (error) => {
566
- renderPipelineLog.error("Cache persist failed", {
567
- slug,
568
- error: error instanceof Error ? error.message : String(error),
569
- stack: error instanceof Error ? error.stack : undefined,
570
- });
571
- },
572
- );
573
- }
574
582
 
575
- timing.total = Math.round(performance.now() - pipelineStartTime);
576
- renderPipelineLog.debug("Complete", { slug, timing });
583
+ timing.total = Math.round(performance.now() - pipelineStartTime);
584
+ renderPipelineLog.debug("Complete", { slug, timing });
577
585
 
578
- return result;
586
+ return result;
587
+ } catch (error) {
588
+ if (error instanceof Error) {
589
+ (error as Error & { sourceFile?: string }).sourceFile = sourceFile;
590
+ }
591
+ throw error;
592
+ }
579
593
  });
580
594
  return result;
581
595
  },
@@ -53,6 +53,27 @@ export function getSuggestion(error: Error): string | undefined {
53
53
  return undefined;
54
54
  }
55
55
 
56
+ export function parseErrorLocation(
57
+ error: Error,
58
+ file: string,
59
+ ): { line?: number; column?: number } {
60
+ if (!error.stack || !file) return {};
61
+
62
+ const lines = error.stack.split("\n");
63
+
64
+ // First try to match the known source file
65
+ for (const stackLine of lines) {
66
+ if (!stackLine.includes(file)) continue;
67
+
68
+ const match = stackLine.match(/:(\d+):(\d+)\)?$/);
69
+ if (match) {
70
+ return { line: Number(match[1]), column: Number(match[2]) };
71
+ }
72
+ }
73
+
74
+ return {};
75
+ }
76
+
56
77
  export function formatErrorType(type: ErrorType): string {
57
78
  const firstChar = type[0];
58
79
  if (!firstChar) return "";