@stacksjs/error-handling 0.70.57 → 0.70.59

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.
@@ -0,0 +1,4 @@
1
+ export declare function languageForFile(file: string): string;
2
+ export declare function highlightSnippet(code: string, file: string, highlightLine: number, startingLine: number): Promise<{ html: string, css: string }>;
3
+ export declare function getSharedHighlighterCss(): string;
4
+ export declare function resetHighlighterCache(): void;
@@ -0,0 +1,39 @@
1
+ import { createHighlighter, detectLanguageFromExtension, getLanguageByExtension } from "ts-syntax-highlighter";
2
+ let sharedHighlighter = null, sharedCss = null;
3
+ async function getHighlighter() {
4
+ if (!sharedHighlighter)
5
+ sharedHighlighter = await createHighlighter({
6
+ theme: "github-light",
7
+ cache: !0
8
+ });
9
+ return sharedHighlighter;
10
+ }
11
+ export function languageForFile(file) {
12
+ const ext = file.includes(".") ? `.${file.split(".").pop()}` : file, byExt = detectLanguageFromExtension(file) ?? getLanguageByExtension(ext);
13
+ if (byExt)
14
+ return byExt.id;
15
+ if (ext === ".php")
16
+ return "php";
17
+ if (ext === ".sql")
18
+ return "sql";
19
+ return "text";
20
+ }
21
+ export async function highlightSnippet(code, file, highlightLine, startingLine) {
22
+ const highlighter = await getHighlighter(), lang = languageForFile(file), relativeHighlight = Math.max(1, highlightLine - startingLine + 1), result = await highlighter.highlight(code, lang, {
23
+ lineNumbers: !0,
24
+ highlightLines: [relativeHighlight]
25
+ });
26
+ if (result.css && !sharedCss)
27
+ sharedCss = result.css;
28
+ return {
29
+ html: result.html,
30
+ css: sharedCss ?? result.css ?? ""
31
+ };
32
+ }
33
+ export function getSharedHighlighterCss() {
34
+ return sharedCss ?? "";
35
+ }
36
+ export function resetHighlighterCache() {
37
+ sharedHighlighter = null;
38
+ sharedCss = null;
39
+ }
@@ -0,0 +1,11 @@
1
+ import type { ErrorPageConfig, QueryInfo, RequestContext, RoutingContext, UserContext } from './error-page';
2
+ export declare function renderDevErrorPage(opts: {
3
+ error: Error
4
+ status: number
5
+ config: ErrorPageConfig
6
+ framework?: { name: string, version?: string }
7
+ request?: RequestContext
8
+ routing?: RoutingContext
9
+ user?: UserContext
10
+ queries?: QueryInfo[]
11
+ }): Promise<string>;
@@ -0,0 +1,21 @@
1
+ import { dirname, join } from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ import { buildErrorPageViewModel } from "./error-page-view-model";
4
+ const VIEWS_DIR = join(dirname(fileURLToPath(import.meta.url)), "views/errors");
5
+ let renderTemplateFn = null;
6
+ async function getRenderTemplate() {
7
+ if (!renderTemplateFn)
8
+ renderTemplateFn = (await import("@stacksjs/stx")).renderTemplate;
9
+ return renderTemplateFn;
10
+ }
11
+ export async function renderDevErrorPage(opts) {
12
+ const viewModel = await buildErrorPageViewModel(opts);
13
+ return (await getRenderTemplate())(join(VIEWS_DIR, "show.stx"), {
14
+ context: viewModel,
15
+ layout: join(VIEWS_DIR, "layout.stx"),
16
+ injectCSS: !0,
17
+ templateOnly: !0,
18
+ processClientScripts: !1,
19
+ title: viewModel.pageTitle
20
+ });
21
+ }
@@ -0,0 +1,620 @@
1
+ export const ERROR_PAGE_CSS = `
2
+ :root {
3
+ --font-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
4
+ --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
5
+ --neutral-50: #fafafa;
6
+ --neutral-100: #f5f5f5;
7
+ --neutral-200: #e5e5e5;
8
+ --neutral-300: #d4d4d4;
9
+ --neutral-400: #a3a3a3;
10
+ --neutral-500: #737373;
11
+ --neutral-600: #525252;
12
+ --neutral-700: #404040;
13
+ --neutral-800: #262626;
14
+ --neutral-900: #171717;
15
+ --neutral-950: #0a0a0a;
16
+ --rose-100: #ffe4e6;
17
+ --rose-200: #fecdd3;
18
+ --rose-500: #f43f5e;
19
+ --rose-600: #e11d48;
20
+ --rose-900: #881337;
21
+ --rose-950: #4c0519;
22
+ --blue-100: #dbeafe;
23
+ --blue-500: #3b82f6;
24
+ --blue-600: #2563eb;
25
+ --blue-700: #1d4ed8;
26
+ --blue-900: #1e3a8a;
27
+ --emerald-500: #10b981;
28
+ --amber-200: #fde68a;
29
+ --amber-600: #d97706;
30
+ --amber-900: #78350f;
31
+ --white: #fff;
32
+ --radius-md: 0.375rem;
33
+ --radius-lg: 0.5rem;
34
+ --radius-xl: 0.75rem;
35
+ }
36
+
37
+ * { box-sizing: border-box; margin: 0; padding: 0; }
38
+
39
+ html {
40
+ -webkit-text-size-adjust: 100%;
41
+ color-scheme: light dark;
42
+ }
43
+
44
+ body {
45
+ font-family: var(--font-sans);
46
+ -webkit-font-smoothing: antialiased;
47
+ -moz-osx-font-smoothing: grayscale;
48
+ background: var(--neutral-50);
49
+ color: var(--neutral-950);
50
+ line-height: 1.5;
51
+ min-height: 100dvh;
52
+ }
53
+
54
+ @media (prefers-color-scheme: dark) {
55
+ body {
56
+ background: var(--neutral-900);
57
+ color: var(--white);
58
+ }
59
+ }
60
+
61
+ .page {
62
+ max-width: 80rem;
63
+ margin: 0 auto;
64
+ padding: 3.5rem 1.5rem 2rem;
65
+ }
66
+
67
+ @media (min-width: 640px) {
68
+ .page { padding: 4rem 3.5rem 2rem; }
69
+ }
70
+
71
+ /* \u2500\u2500 Header \u2500\u2500 */
72
+ .header {
73
+ display: flex;
74
+ align-items: center;
75
+ justify-content: space-between;
76
+ gap: 1rem;
77
+ margin-bottom: 2rem;
78
+ flex-wrap: wrap;
79
+ }
80
+
81
+ .header-title {
82
+ display: flex;
83
+ align-items: center;
84
+ gap: 0.625rem;
85
+ font-size: 0.875rem;
86
+ font-weight: 500;
87
+ color: var(--neutral-600);
88
+ }
89
+
90
+ @media (prefers-color-scheme: dark) {
91
+ .header-title { color: var(--neutral-400); }
92
+ }
93
+
94
+ .header-dot {
95
+ width: 0.625rem;
96
+ height: 0.625rem;
97
+ border-radius: 9999px;
98
+ background: var(--rose-500);
99
+ flex-shrink: 0;
100
+ }
101
+
102
+ .btn-copy {
103
+ display: inline-flex;
104
+ align-items: center;
105
+ gap: 0.375rem;
106
+ padding: 0.375rem 0.75rem;
107
+ font-size: 0.75rem;
108
+ font-weight: 500;
109
+ border: 1px solid var(--neutral-200);
110
+ border-radius: var(--radius-md);
111
+ background: var(--white);
112
+ color: var(--neutral-700);
113
+ cursor: pointer;
114
+ transition: background 0.15s, border-color 0.15s;
115
+ }
116
+
117
+ .btn-copy:hover { background: var(--neutral-100); }
118
+ .btn-copy.copied { border-color: var(--emerald-500); color: var(--emerald-500); }
119
+
120
+ @media (prefers-color-scheme: dark) {
121
+ .btn-copy {
122
+ background: var(--neutral-800);
123
+ border-color: rgba(255,255,255,0.1);
124
+ color: var(--neutral-200);
125
+ }
126
+ .btn-copy:hover { background: rgba(255,255,255,0.05); }
127
+ }
128
+
129
+ /* \u2500\u2500 Error summary card \u2500\u2500 */
130
+ .summary {
131
+ margin-bottom: 2rem;
132
+ }
133
+
134
+ .summary h1 {
135
+ font-size: 1.875rem;
136
+ font-weight: 600;
137
+ line-height: 1.2;
138
+ margin-bottom: 0.75rem;
139
+ }
140
+
141
+ .summary-file {
142
+ font-family: var(--font-mono);
143
+ font-size: 0.875rem;
144
+ color: var(--rose-600);
145
+ margin-bottom: 0.75rem;
146
+ word-break: break-all;
147
+ }
148
+
149
+ @media (prefers-color-scheme: dark) {
150
+ .summary-file { color: var(--rose-500); }
151
+ }
152
+
153
+ .summary-message {
154
+ font-size: 1.125rem;
155
+ color: var(--neutral-800);
156
+ margin-bottom: 1.25rem;
157
+ word-break: break-word;
158
+ }
159
+
160
+ @media (prefers-color-scheme: dark) {
161
+ .summary-message { color: var(--neutral-200); }
162
+ }
163
+
164
+ .badges {
165
+ display: flex;
166
+ flex-wrap: wrap;
167
+ gap: 0.5rem;
168
+ margin-bottom: 1.25rem;
169
+ }
170
+
171
+ .badge {
172
+ display: inline-flex;
173
+ align-items: center;
174
+ padding: 0.25rem 0.5rem;
175
+ font-size: 0.6875rem;
176
+ font-weight: 600;
177
+ letter-spacing: 0.04em;
178
+ text-transform: uppercase;
179
+ border-radius: var(--radius-md);
180
+ border: 1px solid var(--neutral-200);
181
+ background: var(--neutral-100);
182
+ color: var(--neutral-600);
183
+ }
184
+
185
+ @media (prefers-color-scheme: dark) {
186
+ .badge {
187
+ border-color: rgba(255,255,255,0.1);
188
+ background: rgba(255,255,255,0.05);
189
+ color: var(--neutral-400);
190
+ }
191
+ }
192
+
193
+ .badge-status {
194
+ background: var(--rose-100);
195
+ border-color: var(--rose-200);
196
+ color: var(--rose-900);
197
+ }
198
+
199
+ @media (prefers-color-scheme: dark) {
200
+ .badge-status {
201
+ background: var(--rose-950);
202
+ border-color: var(--rose-900);
203
+ color: var(--rose-100);
204
+ }
205
+ }
206
+
207
+ .request-row {
208
+ display: flex;
209
+ align-items: center;
210
+ gap: 0.75rem;
211
+ flex-wrap: wrap;
212
+ font-size: 0.875rem;
213
+ }
214
+
215
+ .badge-method {
216
+ background: var(--blue-100);
217
+ border-color: transparent;
218
+ color: var(--blue-900);
219
+ font-family: var(--font-mono);
220
+ font-size: 0.6875rem;
221
+ }
222
+
223
+ @media (prefers-color-scheme: dark) {
224
+ .badge-method {
225
+ background: var(--blue-700);
226
+ color: var(--white);
227
+ }
228
+ }
229
+
230
+ .request-url {
231
+ font-family: var(--font-mono);
232
+ font-size: 0.8125rem;
233
+ color: var(--neutral-500);
234
+ word-break: break-all;
235
+ }
236
+
237
+ @media (prefers-color-scheme: dark) {
238
+ .request-url { color: var(--neutral-400); }
239
+ }
240
+
241
+ /* \u2500\u2500 Hints \u2500\u2500 */
242
+ .hints {
243
+ border: 1px solid var(--amber-200);
244
+ border-radius: var(--radius-lg);
245
+ background: #fffbeb;
246
+ margin-bottom: 2rem;
247
+ overflow: hidden;
248
+ }
249
+
250
+ @media (prefers-color-scheme: dark) {
251
+ .hints {
252
+ border-color: var(--amber-600);
253
+ background: rgba(120, 53, 15, 0.3);
254
+ }
255
+ }
256
+
257
+ .hints-header {
258
+ padding: 0.75rem 1rem;
259
+ font-size: 0.8125rem;
260
+ font-weight: 600;
261
+ color: var(--amber-900);
262
+ border-bottom: 1px solid var(--amber-200);
263
+ }
264
+
265
+ @media (prefers-color-scheme: dark) {
266
+ .hints-header { color: var(--amber-200); border-bottom-color: rgba(217,119,6,0.3); }
267
+ }
268
+
269
+ .hints-body {
270
+ padding: 1rem;
271
+ font-size: 0.875rem;
272
+ color: var(--neutral-700);
273
+ }
274
+
275
+ @media (prefers-color-scheme: dark) {
276
+ .hints-body { color: var(--neutral-300); }
277
+ }
278
+
279
+ .hints-body ul { margin: 0 0 0.75rem 1.25rem; }
280
+ .hints-body li { margin: 0.2rem 0; }
281
+ .hints-suggestion { font-weight: 500; margin-bottom: 0.5rem; }
282
+ .hints-doc { color: var(--blue-600); text-decoration: none; font-size: 0.8125rem; }
283
+ .hints-doc:hover { text-decoration: underline; }
284
+
285
+ /* \u2500\u2500 Exception trace \u2500\u2500 */
286
+ .trace {
287
+ margin-bottom: 2rem;
288
+ }
289
+
290
+ .trace-title {
291
+ font-size: 0.875rem;
292
+ font-weight: 600;
293
+ color: var(--neutral-500);
294
+ text-transform: uppercase;
295
+ letter-spacing: 0.05em;
296
+ margin-bottom: 1rem;
297
+ }
298
+
299
+ @media (prefers-color-scheme: dark) {
300
+ .trace-title { color: var(--neutral-400); }
301
+ }
302
+
303
+ .trace-frame {
304
+ border: 1px solid var(--neutral-200);
305
+ border-radius: var(--radius-lg);
306
+ margin-bottom: 0.5rem;
307
+ overflow: hidden;
308
+ background: var(--white);
309
+ }
310
+
311
+ @media (prefers-color-scheme: dark) {
312
+ .trace-frame {
313
+ border-color: rgba(255,255,255,0.1);
314
+ background: rgba(255,255,255,0.02);
315
+ }
316
+ }
317
+
318
+ .trace-frame-header {
319
+ display: flex;
320
+ align-items: baseline;
321
+ gap: 0.5rem;
322
+ padding: 0.75rem 1rem;
323
+ cursor: pointer;
324
+ font-size: 0.8125rem;
325
+ transition: background 0.15s;
326
+ }
327
+
328
+ .trace-frame-header:hover { background: var(--neutral-50); }
329
+
330
+ @media (prefers-color-scheme: dark) {
331
+ .trace-frame-header:hover { background: rgba(255,255,255,0.03); }
332
+ }
333
+
334
+ .trace-frame-fn {
335
+ font-family: var(--font-mono);
336
+ color: var(--neutral-800);
337
+ flex: 1;
338
+ min-width: 0;
339
+ overflow: hidden;
340
+ text-overflow: ellipsis;
341
+ white-space: nowrap;
342
+ }
343
+
344
+ @media (prefers-color-scheme: dark) {
345
+ .trace-frame-fn { color: var(--neutral-200); }
346
+ }
347
+
348
+ .trace-frame-file {
349
+ font-family: var(--font-mono);
350
+ font-size: 0.75rem;
351
+ color: var(--neutral-400);
352
+ flex-shrink: 0;
353
+ }
354
+
355
+ .trace-frame-body {
356
+ display: none;
357
+ border-top: 1px solid var(--neutral-200);
358
+ }
359
+
360
+ .trace-frame.expanded .trace-frame-body { display: block; }
361
+
362
+ @media (prefers-color-scheme: dark) {
363
+ .trace-frame-body { border-top-color: rgba(255,255,255,0.08); }
364
+ }
365
+
366
+ .trace-vendor {
367
+ border: 1px dashed var(--neutral-300);
368
+ border-radius: var(--radius-lg);
369
+ margin-bottom: 0.5rem;
370
+ overflow: hidden;
371
+ }
372
+
373
+ @media (prefers-color-scheme: dark) {
374
+ .trace-vendor { border-color: rgba(255,255,255,0.15); }
375
+ }
376
+
377
+ .trace-vendor-toggle {
378
+ display: flex;
379
+ align-items: center;
380
+ gap: 0.5rem;
381
+ padding: 0.75rem 1rem;
382
+ font-size: 0.8125rem;
383
+ font-weight: 500;
384
+ color: var(--neutral-500);
385
+ cursor: pointer;
386
+ background: var(--neutral-50);
387
+ transition: background 0.15s;
388
+ }
389
+
390
+ .trace-vendor-toggle:hover { background: var(--neutral-100); }
391
+
392
+ @media (prefers-color-scheme: dark) {
393
+ .trace-vendor-toggle {
394
+ background: rgba(255,255,255,0.03);
395
+ color: var(--neutral-400);
396
+ }
397
+ .trace-vendor-toggle:hover { background: rgba(255,255,255,0.05); }
398
+ }
399
+
400
+ .trace-vendor-frames { display: none; padding: 0.25rem 0; }
401
+ .trace-vendor.expanded .trace-vendor-frames { display: block; }
402
+
403
+ /* \u2500\u2500 Code snippet \u2500\u2500 */
404
+ .code-block {
405
+ overflow-x: auto;
406
+ font-family: var(--font-mono);
407
+ font-size: 0.8125rem;
408
+ line-height: 1.6;
409
+ background: var(--neutral-950);
410
+ color: #abb2bf;
411
+ }
412
+
413
+ .code-line {
414
+ display: flex;
415
+ min-height: 1.5rem;
416
+ }
417
+
418
+ .code-line:nth-child(odd) { background: rgba(255,255,255,0.02); }
419
+ .code-line:nth-child(even) { background: rgba(255,255,255,0.04); }
420
+
421
+ .code-line.highlight {
422
+ background: rgba(244, 63, 94, 0.25) !important;
423
+ }
424
+
425
+ .code-ln {
426
+ width: 3.5rem;
427
+ text-align: right;
428
+ padding: 0 1rem 0 0.5rem;
429
+ color: var(--neutral-500);
430
+ user-select: none;
431
+ flex-shrink: 0;
432
+ }
433
+
434
+ .code-line.highlight .code-ln { color: var(--rose-500); }
435
+
436
+ .code-txt {
437
+ flex: 1;
438
+ padding-right: 1rem;
439
+ white-space: pre;
440
+ }
441
+
442
+ /* \u2500\u2500 Context panels \u2500\u2500 */
443
+ .context {
444
+ margin-bottom: 2rem;
445
+ }
446
+
447
+ .context-tabs {
448
+ display: flex;
449
+ gap: 0;
450
+ border-bottom: 2px solid var(--neutral-200);
451
+ overflow-x: auto;
452
+ }
453
+
454
+ @media (prefers-color-scheme: dark) {
455
+ .context-tabs { border-bottom-color: rgba(255,255,255,0.1); }
456
+ }
457
+
458
+ .context-tab {
459
+ padding: 0.625rem 1rem;
460
+ font-size: 0.8125rem;
461
+ font-weight: 500;
462
+ color: var(--neutral-500);
463
+ background: none;
464
+ border: none;
465
+ border-bottom: 2px solid transparent;
466
+ margin-bottom: -2px;
467
+ cursor: pointer;
468
+ white-space: nowrap;
469
+ transition: color 0.15s;
470
+ }
471
+
472
+ .context-tab:hover { color: var(--neutral-800); }
473
+ .context-tab.active {
474
+ color: var(--rose-600);
475
+ border-bottom-color: var(--rose-500);
476
+ }
477
+
478
+ @media (prefers-color-scheme: dark) {
479
+ .context-tab:hover { color: var(--neutral-200); }
480
+ .context-tab.active { color: var(--rose-500); }
481
+ }
482
+
483
+ .context-panel {
484
+ display: none;
485
+ padding: 1.25rem 0;
486
+ }
487
+
488
+ .context-panel.active { display: block; }
489
+
490
+ .kv-table {
491
+ width: 100%;
492
+ border-collapse: collapse;
493
+ font-size: 0.8125rem;
494
+ }
495
+
496
+ .kv-table tr {
497
+ border-bottom: 1px solid var(--neutral-100);
498
+ }
499
+
500
+ @media (prefers-color-scheme: dark) {
501
+ .kv-table tr { border-bottom-color: rgba(255,255,255,0.05); }
502
+ }
503
+
504
+ .kv-table td {
505
+ padding: 0.5rem 0.75rem;
506
+ vertical-align: top;
507
+ }
508
+
509
+ .kv-table td:first-child {
510
+ font-weight: 500;
511
+ color: var(--neutral-500);
512
+ width: 30%;
513
+ font-family: var(--font-mono);
514
+ font-size: 0.75rem;
515
+ word-break: break-all;
516
+ }
517
+
518
+ @media (prefers-color-scheme: dark) {
519
+ .kv-table td:first-child { color: var(--neutral-400); }
520
+ }
521
+
522
+ .kv-table td:last-child {
523
+ font-family: var(--font-mono);
524
+ font-size: 0.75rem;
525
+ word-break: break-all;
526
+ color: var(--neutral-800);
527
+ }
528
+
529
+ @media (prefers-color-scheme: dark) {
530
+ .kv-table td:last-child { color: var(--neutral-200); }
531
+ }
532
+
533
+ .query-item {
534
+ padding: 0.75rem 1rem;
535
+ margin-bottom: 0.5rem;
536
+ border: 1px solid var(--neutral-200);
537
+ border-radius: var(--radius-md);
538
+ font-family: var(--font-mono);
539
+ font-size: 0.75rem;
540
+ background: var(--neutral-50);
541
+ }
542
+
543
+ @media (prefers-color-scheme: dark) {
544
+ .query-item {
545
+ border-color: rgba(255,255,255,0.08);
546
+ background: rgba(255,255,255,0.03);
547
+ }
548
+ }
549
+
550
+ .query-time {
551
+ margin-top: 0.375rem;
552
+ font-size: 0.6875rem;
553
+ color: var(--neutral-400);
554
+ }
555
+
556
+ .json-block {
557
+ font-family: var(--font-mono);
558
+ font-size: 0.75rem;
559
+ background: var(--neutral-100);
560
+ border-radius: var(--radius-md);
561
+ padding: 1rem;
562
+ overflow-x: auto;
563
+ white-space: pre-wrap;
564
+ word-break: break-all;
565
+ }
566
+
567
+ @media (prefers-color-scheme: dark) {
568
+ .json-block {
569
+ background: rgba(255,255,255,0.05);
570
+ color: var(--neutral-200);
571
+ }
572
+ }
573
+
574
+ /* \u2500\u2500 Production page \u2500\u2500 */
575
+ .production-page {
576
+ display: flex;
577
+ flex-direction: column;
578
+ align-items: center;
579
+ justify-content: center;
580
+ min-height: 100dvh;
581
+ text-align: center;
582
+ padding: 2rem;
583
+ }
584
+
585
+ .production-status {
586
+ font-size: 6rem;
587
+ font-weight: 700;
588
+ color: var(--neutral-200);
589
+ margin-bottom: 1rem;
590
+ line-height: 1;
591
+ }
592
+
593
+ @media (prefers-color-scheme: dark) {
594
+ .production-status { color: var(--neutral-700); }
595
+ }
596
+
597
+ .production-title {
598
+ font-size: 1.5rem;
599
+ font-weight: 600;
600
+ margin-bottom: 0.5rem;
601
+ }
602
+
603
+ .production-message {
604
+ color: var(--neutral-500);
605
+ margin-bottom: 2rem;
606
+ max-width: 28rem;
607
+ }
608
+
609
+ .production-link {
610
+ color: var(--blue-600);
611
+ text-decoration: none;
612
+ font-size: 0.875rem;
613
+ }
614
+
615
+ .production-link:hover { text-decoration: underline; }
616
+
617
+ @media (prefers-color-scheme: dark) {
618
+ .production-link { color: var(--blue-500); }
619
+ }
620
+ `;