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
@@ -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" },
@@ -15,7 +15,7 @@ import {
15
15
  startRenderSession,
16
16
  } from "../../../transforms/mdx/esm-module-loader/module-fetcher/index.js";
17
17
  import { getErrorCollector } from "../../../observability/error-collector.js";
18
- import { ErrorOverlay } from "../../dev-server/error-overlay/index.js";
18
+ import { ErrorOverlay, parseErrorLocation } from "../../dev-server/error-overlay/index.js";
19
19
  import { ErrorPages } from "../../utils/error-html.js";
20
20
  import {
21
21
  HTTP_INTERNAL_SERVER_ERROR,
@@ -240,9 +240,16 @@ export class SSRService {
240
240
  slug,
241
241
  });
242
242
 
243
+ const sourceFile = (errorObj as Error & { sourceFile?: string }).sourceFile;
244
+ const location = sourceFile ? parseErrorLocation(errorObj, sourceFile) : {};
243
245
  return {
244
246
  status: HTTP_INTERNAL_SERVER_ERROR,
245
- html: ErrorOverlay.createHTML({ error: errorObj, type: "runtime" }),
247
+ html: ErrorOverlay.createHTML({
248
+ error: errorObj,
249
+ type: "runtime",
250
+ ...(sourceFile ? { file: sourceFile } : {}),
251
+ ...location,
252
+ }, ctx.projectSlug),
246
253
  isStreaming: false,
247
254
  cacheStrategy: "no-cache",
248
255
  error: errorObj,
@@ -1,3 +1,5 @@
1
+ import { escapeHTML } from "../../html/html-escape.js";
2
+
1
3
  interface ErrorHtmlOptions {
2
4
  statusCode: number;
3
5
  title: string;
@@ -19,13 +21,18 @@ export function generateErrorHtml(options: ErrorHtmlOptions): string {
19
21
  }
20
22
 
21
23
  function generateStyledErrorHtml(statusCode: number, title: string, message: string): string {
24
+ const errorMessage = title === "Not Found" ? `Page not found: ${message}` : message;
25
+
26
+ // 4xx = warning (routing/config issue), 5xx = error (something broke)
27
+ const errorType = statusCode >= 500 ? "error" : "warning";
28
+
22
29
  return `<!DOCTYPE html>
23
30
  <html lang="en">
24
31
  <head>
25
32
  <meta charset="utf-8">
26
33
  <meta name="viewport" content="width=device-width">
27
34
  <link rel="icon" type="image/png" href="https://cdn.veryfront.com/images/veryfront-favicon.png">
28
- <title>${statusCode} ${title} — Veryfront</title>
35
+ <title>${statusCode} ${escapeHTML(title)} — Veryfront</title>
29
36
  <style>
30
37
  :root {
31
38
  --bg: #ffffff;
@@ -74,9 +81,25 @@ function generateStyledErrorHtml(statusCode: number, title: string, message: str
74
81
  </head>
75
82
  <body>
76
83
  <div class="container">
77
- <h1 class="title">${title}</h1>
78
- <p class="message">${message}</p>
84
+ <h1 class="title">${escapeHTML(title)}</h1>
85
+ <p class="message">${escapeHTML(message)}</p>
79
86
  </div>
87
+ <script>
88
+ if (window.parent !== window) {
89
+ try {
90
+ window.parent.postMessage({
91
+ action: 'appUpdated',
92
+ isInitialLoad: true,
93
+ hasError: true,
94
+ url: window.location.href,
95
+ errors: [{
96
+ type: '${errorType}',
97
+ message: ${JSON.stringify(errorMessage).replace(/</g, "\\u003c")}
98
+ }]
99
+ }, '*');
100
+ } catch (e) { /* postMessage may fail in cross-origin iframes */ }
101
+ }
102
+ </script>
80
103
  </body>
81
104
  </html>`;
82
105
  }
@@ -94,11 +117,11 @@ function generateMinimalErrorHtml(
94
117
  <head>
95
118
  <meta charset="utf-8"/>
96
119
  <meta name="viewport" content="width=device-width, initial-scale=1"/>
97
- <title>${statusCode} ${title}</title>
120
+ <title>${statusCode} ${escapeHTML(title)}</title>
98
121
  </head>
99
122
  <body>
100
- <h1>${statusCode} ${title}</h1>
101
- <p>${fullMessage}</p>
123
+ <h1>${statusCode} ${escapeHTML(title)}</h1>
124
+ <p>${escapeHTML(fullMessage)}</p>
102
125
  </body>
103
126
  </html>`;
104
127
  }