veryfront 0.1.71 → 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 (110) hide show
  1. package/esm/cli/commands/files/command-help.d.ts +3 -0
  2. package/esm/cli/commands/files/command-help.d.ts.map +1 -0
  3. package/esm/cli/commands/files/command-help.js +38 -0
  4. package/esm/cli/commands/files/command.d.ts +105 -0
  5. package/esm/cli/commands/files/command.d.ts.map +1 -0
  6. package/esm/cli/commands/files/command.js +250 -0
  7. package/esm/cli/commands/files/handler.d.ts +3 -0
  8. package/esm/cli/commands/files/handler.d.ts.map +1 -0
  9. package/esm/cli/commands/files/handler.js +4 -0
  10. package/esm/cli/commands/files/index.d.ts +4 -0
  11. package/esm/cli/commands/files/index.d.ts.map +1 -0
  12. package/esm/cli/commands/files/index.js +2 -0
  13. package/esm/cli/commands/knowledge/command-help.d.ts +3 -0
  14. package/esm/cli/commands/knowledge/command-help.d.ts.map +1 -0
  15. package/esm/cli/commands/knowledge/command-help.js +38 -0
  16. package/esm/cli/commands/knowledge/command.d.ts +122 -0
  17. package/esm/cli/commands/knowledge/command.d.ts.map +1 -0
  18. package/esm/cli/commands/knowledge/command.js +382 -0
  19. package/esm/cli/commands/knowledge/handler.d.ts +3 -0
  20. package/esm/cli/commands/knowledge/handler.d.ts.map +1 -0
  21. package/esm/cli/commands/knowledge/handler.js +4 -0
  22. package/esm/cli/commands/knowledge/index.d.ts +3 -0
  23. package/esm/cli/commands/knowledge/index.d.ts.map +1 -0
  24. package/esm/cli/commands/knowledge/index.js +2 -0
  25. package/esm/cli/commands/knowledge/parser-source.d.ts +2 -0
  26. package/esm/cli/commands/knowledge/parser-source.d.ts.map +1 -0
  27. package/esm/cli/commands/knowledge/parser-source.js +415 -0
  28. package/esm/cli/commands/uploads/command-help.d.ts +3 -0
  29. package/esm/cli/commands/uploads/command-help.d.ts.map +1 -0
  30. package/esm/cli/commands/uploads/command-help.js +43 -0
  31. package/esm/cli/commands/uploads/command.d.ts +140 -0
  32. package/esm/cli/commands/uploads/command.d.ts.map +1 -0
  33. package/esm/cli/commands/uploads/command.js +323 -0
  34. package/esm/cli/commands/uploads/handler.d.ts +3 -0
  35. package/esm/cli/commands/uploads/handler.d.ts.map +1 -0
  36. package/esm/cli/commands/uploads/handler.js +4 -0
  37. package/esm/cli/commands/uploads/index.d.ts +4 -0
  38. package/esm/cli/commands/uploads/index.d.ts.map +1 -0
  39. package/esm/cli/commands/uploads/index.js +2 -0
  40. package/esm/cli/help/command-definitions.d.ts.map +1 -1
  41. package/esm/cli/help/command-definitions.js +6 -0
  42. package/esm/cli/router.d.ts.map +1 -1
  43. package/esm/cli/router.js +6 -0
  44. package/esm/deno.js +1 -1
  45. package/esm/src/errors/error-registry.d.ts +2 -0
  46. package/esm/src/errors/error-registry.d.ts.map +1 -1
  47. package/esm/src/errors/error-registry.js +8 -0
  48. package/esm/src/errors/index.d.ts +1 -1
  49. package/esm/src/errors/index.d.ts.map +1 -1
  50. package/esm/src/errors/index.js +1 -1
  51. package/esm/src/html/html-shell-generator.d.ts.map +1 -1
  52. package/esm/src/html/html-shell-generator.js +6 -0
  53. package/esm/src/platform/compat/media-types.d.ts +5 -0
  54. package/esm/src/platform/compat/media-types.d.ts.map +1 -0
  55. package/esm/src/platform/compat/media-types.js +19 -0
  56. package/esm/src/platform/index.d.ts +1 -0
  57. package/esm/src/platform/index.d.ts.map +1 -1
  58. package/esm/src/platform/index.js +2 -0
  59. package/esm/src/rendering/orchestrator/pipeline.d.ts.map +1 -1
  60. package/esm/src/rendering/orchestrator/pipeline.js +116 -105
  61. package/esm/src/server/bootstrap.js +5 -1
  62. package/esm/src/server/dev-server/error-overlay/error-formatter.d.ts +4 -0
  63. package/esm/src/server/dev-server/error-overlay/error-formatter.d.ts.map +1 -1
  64. package/esm/src/server/dev-server/error-overlay/error-formatter.js +15 -0
  65. package/esm/src/server/dev-server/error-overlay/html-template.d.ts +1 -1
  66. package/esm/src/server/dev-server/error-overlay/html-template.d.ts.map +1 -1
  67. package/esm/src/server/dev-server/error-overlay/html-template.js +131 -8
  68. package/esm/src/server/dev-server/error-overlay/index.d.ts +1 -1
  69. package/esm/src/server/dev-server/error-overlay/index.d.ts.map +1 -1
  70. package/esm/src/server/dev-server/error-overlay/index.js +1 -1
  71. package/esm/src/server/dev-server/error-overlay/overlay-renderer.d.ts +1 -1
  72. package/esm/src/server/dev-server/error-overlay/overlay-renderer.d.ts.map +1 -1
  73. package/esm/src/server/dev-server/error-overlay/overlay-renderer.js +2 -2
  74. package/esm/src/server/dev-server/request-handler.d.ts.map +1 -1
  75. package/esm/src/server/dev-server/request-handler.js +6 -2
  76. package/esm/src/server/services/rendering/ssr.service.d.ts.map +1 -1
  77. package/esm/src/server/services/rendering/ssr.service.js +9 -2
  78. package/esm/src/server/utils/error-html.d.ts.map +1 -1
  79. package/esm/src/server/utils/error-html.js +26 -6
  80. package/package.json +2 -1
  81. package/src/cli/commands/files/command-help.ts +40 -0
  82. package/src/cli/commands/files/command.ts +328 -0
  83. package/src/cli/commands/files/handler.ts +6 -0
  84. package/src/cli/commands/files/index.ts +19 -0
  85. package/src/cli/commands/knowledge/command-help.ts +40 -0
  86. package/src/cli/commands/knowledge/command.ts +513 -0
  87. package/src/cli/commands/knowledge/handler.ts +6 -0
  88. package/src/cli/commands/knowledge/index.ts +2 -0
  89. package/src/cli/commands/knowledge/parser-source.ts +415 -0
  90. package/src/cli/commands/uploads/command-help.ts +45 -0
  91. package/src/cli/commands/uploads/command.ts +465 -0
  92. package/src/cli/commands/uploads/handler.ts +6 -0
  93. package/src/cli/commands/uploads/index.ts +23 -0
  94. package/src/cli/help/command-definitions.ts +6 -0
  95. package/src/cli/router.ts +6 -0
  96. package/src/deno.js +1 -1
  97. package/src/src/errors/error-registry.ts +9 -0
  98. package/src/src/errors/index.ts +1 -0
  99. package/src/src/html/html-shell-generator.ts +9 -0
  100. package/src/src/platform/compat/media-types.ts +23 -0
  101. package/src/src/platform/index.ts +3 -0
  102. package/src/src/rendering/orchestrator/pipeline.ts +186 -172
  103. package/src/src/server/bootstrap.ts +6 -1
  104. package/src/src/server/dev-server/error-overlay/error-formatter.ts +21 -0
  105. package/src/src/server/dev-server/error-overlay/html-template.ts +139 -8
  106. package/src/src/server/dev-server/error-overlay/index.ts +1 -0
  107. package/src/src/server/dev-server/error-overlay/overlay-renderer.ts +2 -1
  108. package/src/src/server/dev-server/request-handler.ts +6 -2
  109. package/src/src/server/services/rendering/ssr.service.ts +9 -2
  110. package/src/src/server/utils/error-html.ts +29 -6
@@ -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
  },
@@ -278,7 +278,12 @@ function validateProductionEnvironment(_adapter: RuntimeAdapter): void {
278
278
  );
279
279
  }
280
280
 
281
- if (!controlPlanePublicKey) {
281
+ if (!controlPlanePublicKey && nodeEnv === "development") {
282
+ logger.warn(
283
+ "[Bootstrap:Prod] CHANNEL_DISPATCH_SIGNING_PUBLIC_KEY is not set. " +
284
+ "Channel dispatch verification will be unavailable (local dev mode).",
285
+ );
286
+ } else if (!controlPlanePublicKey) {
282
287
  logger.error(
283
288
  "[Bootstrap:Prod] CRITICAL: CHANNEL_DISPATCH_SIGNING_PUBLIC_KEY is not set in proxy mode. " +
284
289
  "Hosted runtimes cannot verify control-plane requests without it.",
@@ -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 "";
@@ -10,6 +10,13 @@ const WS_RECONNECT_MAX_DELAY_MS = 5_000;
10
10
  /** Maximum number of WebSocket reconnection attempts before giving up */
11
11
  const WS_MAX_RECONNECT_ATTEMPTS = 10;
12
12
 
13
+ /** JSON.stringify that escapes `<` to prevent `</script>` breaking inline scripts */
14
+ function jsonForScript(value: unknown): string {
15
+ const json = JSON.stringify(value);
16
+ // JSON.stringify(undefined) returns undefined (not a string)
17
+ return json === undefined ? "undefined" : json.replace(/</g, "\\u003c");
18
+ }
19
+
13
20
  export function generateRuntimeScript(): string {
14
21
  return `
15
22
  // Veryfront Error Overlay Runtime
@@ -110,21 +117,76 @@ export function generateRuntimeScript(): string {
110
117
  </div>
111
118
 
112
119
  <button type="button" onclick="document.getElementById('veryfront-error-overlay').remove()" style="
113
- background: #333;
114
- border: 1px solid #555;
115
- color: #ccc;
116
- padding: 8px 16px;
117
- border-radius: 4px;
120
+ background: #fff;
121
+ border: none;
122
+ color: #000;
123
+ padding: 10px 20px;
124
+ border-radius: 9999px;
118
125
  cursor: pointer;
119
126
  font-family: inherit;
120
127
  ">
121
128
  Dismiss
122
129
  </button>
130
+ \${window.__VF_PROJECT_SLUG__ ? \`
131
+ <button type="button" id="vf-fix-btn-runtime" style="
132
+ background: transparent;
133
+ border: 1px solid rgba(255, 255, 255, 0.2);
134
+ color: rgba(255, 255, 255, 0.7);
135
+ padding: 10px 20px;
136
+ border-radius: 9999px;
137
+ cursor: pointer;
138
+ font-family: inherit;
139
+ margin-left: 8px;
140
+ ">
141
+ Fix in Veryfront
142
+ </button>
143
+ \` : ''}
123
144
  </div>
124
145
  </div>
125
146
  \`;
126
147
 
127
148
  document.body.appendChild(overlay);
149
+
150
+ // Notify Studio of runtime error
151
+ if (window.parent !== window) {
152
+ try {
153
+ window.parent.postMessage({
154
+ action: 'runtimeError',
155
+ url: window.location.href,
156
+ hasError: true,
157
+ errors: [{
158
+ type: 'error',
159
+ message: (errorInfo.error && errorInfo.error.message) || 'Unknown error',
160
+ file: errorInfo.file ? String(errorInfo.file) : undefined,
161
+ line: errorInfo.line ? Number(errorInfo.line) : undefined,
162
+ column: errorInfo.column ? Number(errorInfo.column) : undefined
163
+ }]
164
+ }, '*');
165
+ } catch (e) { /* postMessage may fail */ }
166
+ }
167
+
168
+ if (window.__VF_PROJECT_SLUG__) {
169
+ var fixBtn = document.getElementById('vf-fix-btn-runtime');
170
+ if (fixBtn) {
171
+ fixBtn.addEventListener('click', function() {
172
+ var rawName = (errorInfo.error && errorInfo.error.name) || 'Error';
173
+ var rawMessage = (errorInfo.error && errorInfo.error.message) || 'Unknown error';
174
+ var rawFile = errorInfo.file ? String(errorInfo.file) : null;
175
+ var rawLine = errorInfo.line ? String(errorInfo.line) : null;
176
+ var rawColumn = errorInfo.column ? String(errorInfo.column) : null;
177
+ var loc = rawFile ? rawFile + (rawLine ? ':' + rawLine : '') + (rawColumn ? ':' + rawColumn : '') : null;
178
+ var bt = String.fromCharCode(96);
179
+ var prompt = 'Find and fix the following error' +
180
+ (loc ? ' in ' + bt + loc + bt : '') +
181
+ '\\n\\n' + bt + rawMessage + bt;
182
+ if (window.parent !== window) {
183
+ window.parent.postMessage({ action: 'chatMessage', prompt: prompt }, '*');
184
+ } else {
185
+ window.open('https://veryfront.com/projects/' + window.__VF_PROJECT_SLUG__ + '?prompt=' + encodeURIComponent(prompt));
186
+ }
187
+ });
188
+ }
189
+ }
128
190
  };
129
191
 
130
192
  window.addEventListener('error', (event) => {
@@ -146,7 +208,11 @@ export function generateRuntimeScript(): string {
146
208
  `;
147
209
  }
148
210
 
149
- export function generateErrorHTML(errorInfo: ErrorInfo, suggestion?: string): string {
211
+ export function generateErrorHTML(
212
+ errorInfo: ErrorInfo,
213
+ suggestion?: string,
214
+ projectSlug?: string,
215
+ ): string {
150
216
  const errorType = escapeHtml(formatErrorType(errorInfo.type));
151
217
  const errorName = escapeHtml(errorInfo.error.name);
152
218
  const errorMessage = escapeHtml(errorInfo.error.message);
@@ -182,6 +248,10 @@ export function generateErrorHTML(errorInfo: ErrorInfo, suggestion?: string): st
182
248
  `
183
249
  : "";
184
250
 
251
+ const fixButtonHtml = projectSlug
252
+ ? `<button type="button" id="vf-fix-btn" class="btn btn-fix">Fix in Veryfront</button>`
253
+ : "";
254
+
185
255
  return `
186
256
  <!DOCTYPE html>
187
257
  <html>
@@ -253,6 +323,32 @@ export function generateErrorHTML(errorInfo: ErrorInfo, suggestion?: string): st
253
323
  overflow-x: auto;
254
324
  font-size: 12px;
255
325
  }
326
+ .btn {
327
+ padding: 10px 20px;
328
+ border-radius: 9999px;
329
+ cursor: pointer;
330
+ font-family: inherit;
331
+ font-size: 14px;
332
+ border: none;
333
+ }
334
+ .btn-dismiss {
335
+ background: #fff;
336
+ color: #000;
337
+ }
338
+ .btn-dismiss:hover {
339
+ background: #e5e5e5;
340
+ }
341
+ .btn-fix {
342
+ background: transparent;
343
+ color: rgba(255, 255, 255, 0.7);
344
+ border: 1px solid rgba(255, 255, 255, 0.2);
345
+ margin-left: 8px;
346
+ }
347
+ .btn-fix:hover {
348
+ background: rgba(255, 255, 255, 0.1);
349
+ color: #fff;
350
+ border-color: rgba(255, 255, 255, 0.4);
351
+ }
256
352
  </style>
257
353
  </head>
258
354
  <body>
@@ -267,8 +363,36 @@ export function generateErrorHTML(errorInfo: ErrorInfo, suggestion?: string): st
267
363
  ${suggestionSection}
268
364
  ${stackSection}
269
365
  </div>
366
+ ${fixButtonHtml}
270
367
  </div>
271
- <script>
368
+ <script>${
369
+ projectSlug
370
+ ? `
371
+ (function() {
372
+ var slug = ${jsonForScript(projectSlug)};
373
+ var errorName = ${jsonForScript(errorInfo.error.name)};
374
+ var errorMessage = ${jsonForScript(errorInfo.error.message)};
375
+ var errorFile = ${jsonForScript(errorInfo.file ?? null)};
376
+ var errorLine = ${jsonForScript(errorInfo.line ?? null)};
377
+ var errorColumn = ${jsonForScript(errorInfo.column ?? null)};
378
+ var btn = document.getElementById('vf-fix-btn');
379
+ if (btn) {
380
+ btn.addEventListener('click', function() {
381
+ var loc = errorFile ? errorFile + (errorLine ? ':' + errorLine : '') + (errorColumn ? ':' + errorColumn : '') : null;
382
+ var bt = String.fromCharCode(96);
383
+ var prompt = 'Find and fix the following error' +
384
+ (loc ? ' in ' + bt + loc + bt : '') +
385
+ '\\n\\n' + bt + errorMessage + bt;
386
+ if (window.parent !== window) {
387
+ window.parent.postMessage({ action: 'chatMessage', prompt: prompt }, '*');
388
+ } else {
389
+ window.open('https://veryfront.com/projects/' + slug + '?prompt=' + encodeURIComponent(prompt));
390
+ }
391
+ });
392
+ }
393
+ })();`
394
+ : ""
395
+ }
272
396
  // Notify Studio (parent) that page has loaded with an error
273
397
  // This hides the loading spinner in Studio's preview iframe
274
398
  if (window.parent !== window) {
@@ -277,7 +401,14 @@ export function generateErrorHTML(errorInfo: ErrorInfo, suggestion?: string): st
277
401
  action: 'appUpdated',
278
402
  isInitialLoad: true,
279
403
  hasError: true,
280
- url: window.location.href
404
+ url: window.location.href,
405
+ errors: [{
406
+ type: 'error',
407
+ message: ${jsonForScript(errorInfo.error.message)},
408
+ file: ${jsonForScript(errorInfo.file || undefined)},
409
+ line: ${errorInfo.line ? String(errorInfo.line) : "undefined"},
410
+ column: ${errorInfo.column ? String(errorInfo.column) : "undefined"}
411
+ }]
281
412
  }, '*');
282
413
  } catch (e) { /* postMessage may fail in cross-origin iframes */ }
283
414
  }
@@ -10,6 +10,7 @@ export {
10
10
  type ErrorType,
11
11
  formatErrorType,
12
12
  getSuggestion,
13
+ parseErrorLocation,
13
14
  } from "./error-formatter.js";
14
15
  export { generateErrorHTML, generateRuntimeScript } from "./html-template.js";
15
16
  export {
@@ -4,10 +4,11 @@ import { generateErrorHTML, generateRuntimeScript } from "./html-template.js";
4
4
  export const ErrorOverlay = {
5
5
  getRuntime: generateRuntimeScript,
6
6
  getSuggestion,
7
- createHTML(errorInfo: ErrorInfo): string {
7
+ createHTML(errorInfo: ErrorInfo, projectSlug?: string): string {
8
8
  return generateErrorHTML(
9
9
  errorInfo,
10
10
  errorInfo.suggestion ?? getSuggestion(errorInfo.error),
11
+ projectSlug,
11
12
  );
12
13
  },
13
14
  };
@@ -10,7 +10,7 @@ import {
10
10
  import type { RuntimeAdapter } from "../../platform/adapters/base.js";
11
11
  import type { VeryfrontConfig } from "../../config/index.js";
12
12
  import { clearConfigCache } from "../../config/index.js";
13
- import { ErrorOverlay } from "./error-overlay/index.js";
13
+ import { ErrorOverlay, parseErrorLocation } from "./error-overlay/index.js";
14
14
  import { createResponseBuilder } from "../../security/index.js";
15
15
  import { resetApiHandler } from "../handlers/request/api/pages-api-handler.js";
16
16
  import { clearLayoutDiscoveryCache } from "../../rendering/layouts/index.js";
@@ -169,11 +169,15 @@ export class RequestHandler {
169
169
  const err = error as Error;
170
170
  getErrorCollector().addRuntimeError(err.message, err.stack, { source: "request-handler" });
171
171
 
172
+ const sourceFile = (err as Error & { sourceFile?: string }).sourceFile;
173
+ const location = sourceFile ? parseErrorLocation(err, sourceFile) : {};
172
174
  return new dntShim.Response(
173
175
  ErrorOverlay.createHTML({
174
176
  type: "runtime",
175
177
  error: err,
176
- }),
178
+ ...(sourceFile ? { file: sourceFile } : {}),
179
+ ...location,
180
+ }, this.defaultProjectSlug),
177
181
  {
178
182
  status: HTTP_SERVER_ERROR,
179
183
  headers: { "content-type": "text/html; charset=utf-8" },