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.
- package/esm/deno.js +1 -1
- package/esm/src/html/html-shell-generator.d.ts.map +1 -1
- package/esm/src/html/html-shell-generator.js +6 -0
- package/esm/src/rendering/orchestrator/pipeline.d.ts.map +1 -1
- package/esm/src/rendering/orchestrator/pipeline.js +116 -105
- package/esm/src/server/dev-server/error-overlay/error-formatter.d.ts +4 -0
- package/esm/src/server/dev-server/error-overlay/error-formatter.d.ts.map +1 -1
- package/esm/src/server/dev-server/error-overlay/error-formatter.js +15 -0
- package/esm/src/server/dev-server/error-overlay/html-template.d.ts +1 -1
- package/esm/src/server/dev-server/error-overlay/html-template.d.ts.map +1 -1
- package/esm/src/server/dev-server/error-overlay/html-template.js +131 -8
- package/esm/src/server/dev-server/error-overlay/index.d.ts +1 -1
- package/esm/src/server/dev-server/error-overlay/index.d.ts.map +1 -1
- package/esm/src/server/dev-server/error-overlay/index.js +1 -1
- package/esm/src/server/dev-server/error-overlay/overlay-renderer.d.ts +1 -1
- package/esm/src/server/dev-server/error-overlay/overlay-renderer.d.ts.map +1 -1
- package/esm/src/server/dev-server/error-overlay/overlay-renderer.js +2 -2
- package/esm/src/server/dev-server/request-handler.d.ts.map +1 -1
- package/esm/src/server/dev-server/request-handler.js +6 -2
- package/esm/src/server/services/rendering/ssr.service.d.ts.map +1 -1
- package/esm/src/server/services/rendering/ssr.service.js +9 -2
- package/esm/src/server/utils/error-html.d.ts.map +1 -1
- package/esm/src/server/utils/error-html.js +26 -6
- package/package.json +1 -1
- package/src/deno.js +1 -1
- package/src/src/html/html-shell-generator.ts +9 -0
- package/src/src/rendering/orchestrator/pipeline.ts +186 -172
- package/src/src/server/dev-server/error-overlay/error-formatter.ts +21 -0
- package/src/src/server/dev-server/error-overlay/html-template.ts +139 -8
- package/src/src/server/dev-server/error-overlay/index.ts +1 -0
- package/src/src/server/dev-server/error-overlay/overlay-renderer.ts +2 -1
- package/src/src/server/dev-server/request-handler.ts +6 -2
- package/src/src/server/services/rendering/ssr.service.ts +9 -2
- 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: #
|
|
114
|
-
border:
|
|
115
|
-
color: #
|
|
116
|
-
padding:
|
|
117
|
-
border-radius:
|
|
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(
|
|
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
|
}
|
|
@@ -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({
|
|
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
|
}
|