@textjs/app 0.0.0-stage → 1.0.2
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/README.md +34 -2
- package/bin/text.js +29 -0
- package/bin/text.ts +13 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/cli/build.d.ts +39 -0
- package/dist/cli/build.d.ts.map +1 -0
- package/dist/cli/build.js +157 -0
- package/dist/cli/build.js.map +1 -0
- package/dist/cli/config.d.ts +22 -0
- package/dist/cli/config.d.ts.map +1 -0
- package/dist/cli/config.js +73 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/dev.d.ts +12 -0
- package/dist/cli/dev.d.ts.map +1 -0
- package/dist/cli/dev.js +49 -0
- package/dist/cli/dev.js.map +1 -0
- package/dist/cli/index.d.ts +26 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +192 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/run.d.ts +31 -0
- package/dist/cli/run.d.ts.map +1 -0
- package/dist/cli/run.js +111 -0
- package/dist/cli/run.js.map +1 -0
- package/dist/cli/start.d.ts +14 -0
- package/dist/cli/start.d.ts.map +1 -0
- package/dist/cli/start.js +183 -0
- package/dist/cli/start.js.map +1 -0
- package/dist/cli/ui.d.ts +35 -0
- package/dist/cli/ui.d.ts.map +1 -0
- package/dist/cli/ui.js +73 -0
- package/dist/cli/ui.js.map +1 -0
- package/dist/client.d.ts +34 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +93 -0
- package/dist/client.js.map +1 -0
- package/dist/config.d.ts +143 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +28 -0
- package/dist/config.js.map +1 -0
- package/dist/context.d.ts +44 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +26 -0
- package/dist/context.js.map +1 -0
- package/dist/dev-errors.d.ts +68 -0
- package/dist/dev-errors.d.ts.map +1 -0
- package/dist/dev-errors.js +121 -0
- package/dist/dev-errors.js.map +1 -0
- package/dist/devtools.d.ts +64 -0
- package/dist/devtools.d.ts.map +1 -0
- package/dist/devtools.js +186 -0
- package/dist/devtools.js.map +1 -0
- package/dist/frame-mirror.d.ts +19 -0
- package/dist/frame-mirror.d.ts.map +1 -0
- package/dist/frame-mirror.js +141 -0
- package/dist/frame-mirror.js.map +1 -0
- package/dist/frame-protocol.d.ts +175 -0
- package/dist/frame-protocol.d.ts.map +1 -0
- package/dist/frame-protocol.js +369 -0
- package/dist/frame-protocol.js.map +1 -0
- package/dist/history.d.ts +54 -0
- package/dist/history.d.ts.map +1 -0
- package/dist/history.js +152 -0
- package/dist/history.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/link.d.ts +32 -0
- package/dist/link.d.ts.map +1 -0
- package/dist/link.js +54 -0
- package/dist/link.js.map +1 -0
- package/dist/live-player.d.ts +83 -0
- package/dist/live-player.d.ts.map +1 -0
- package/dist/live-player.js +413 -0
- package/dist/live-player.js.map +1 -0
- package/dist/live-protocol.d.ts +132 -0
- package/dist/live-protocol.d.ts.map +1 -0
- package/dist/live-protocol.js +278 -0
- package/dist/live-protocol.js.map +1 -0
- package/dist/live-stub.d.ts +10 -0
- package/dist/live-stub.d.ts.map +1 -0
- package/dist/live-stub.js +32 -0
- package/dist/live-stub.js.map +1 -0
- package/dist/mcp/cli.d.ts +22 -0
- package/dist/mcp/cli.d.ts.map +1 -0
- package/dist/mcp/cli.js +177 -0
- package/dist/mcp/cli.js.map +1 -0
- package/dist/mcp/frame.d.ts +58 -0
- package/dist/mcp/frame.d.ts.map +1 -0
- package/dist/mcp/frame.js +169 -0
- package/dist/mcp/frame.js.map +1 -0
- package/dist/mcp/index.d.ts +15 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +15 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/inspect.d.ts +95 -0
- package/dist/mcp/inspect.d.ts.map +1 -0
- package/dist/mcp/inspect.js +386 -0
- package/dist/mcp/inspect.js.map +1 -0
- package/dist/mcp/keys.d.ts +20 -0
- package/dist/mcp/keys.d.ts.map +1 -0
- package/dist/mcp/keys.js +87 -0
- package/dist/mcp/keys.js.map +1 -0
- package/dist/mcp/logs.d.ts +63 -0
- package/dist/mcp/logs.d.ts.map +1 -0
- package/dist/mcp/logs.js +217 -0
- package/dist/mcp/logs.js.map +1 -0
- package/dist/mcp/server.d.ts +64 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +485 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/session.d.ts +230 -0
- package/dist/mcp/session.d.ts.map +1 -0
- package/dist/mcp/session.js +742 -0
- package/dist/mcp/session.js.map +1 -0
- package/dist/navigation-errors.d.ts +28 -0
- package/dist/navigation-errors.d.ts.map +1 -0
- package/dist/navigation-errors.js +43 -0
- package/dist/navigation-errors.js.map +1 -0
- package/dist/navigation.d.ts +21 -0
- package/dist/navigation.d.ts.map +1 -0
- package/dist/navigation.js +33 -0
- package/dist/navigation.js.map +1 -0
- package/dist/router.d.ts +76 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +296 -0
- package/dist/router.js.map +1 -0
- package/dist/routing.d.ts +101 -0
- package/dist/routing.d.ts.map +1 -0
- package/dist/routing.js +286 -0
- package/dist/routing.js.map +1 -0
- package/dist/server/adapters/build-step.d.ts +34 -0
- package/dist/server/adapters/build-step.d.ts.map +1 -0
- package/dist/server/adapters/build-step.js +112 -0
- package/dist/server/adapters/build-step.js.map +1 -0
- package/dist/server/adapters/bun.d.ts +8 -0
- package/dist/server/adapters/bun.d.ts.map +1 -0
- package/dist/server/adapters/bun.js +89 -0
- package/dist/server/adapters/bun.js.map +1 -0
- package/dist/server/adapters/bundle.d.ts +21 -0
- package/dist/server/adapters/bundle.d.ts.map +1 -0
- package/dist/server/adapters/bundle.js +86 -0
- package/dist/server/adapters/bundle.js.map +1 -0
- package/dist/server/adapters/cloudflare.d.ts +29 -0
- package/dist/server/adapters/cloudflare.d.ts.map +1 -0
- package/dist/server/adapters/cloudflare.js +105 -0
- package/dist/server/adapters/cloudflare.js.map +1 -0
- package/dist/server/adapters/index.d.ts +10 -0
- package/dist/server/adapters/index.d.ts.map +1 -0
- package/dist/server/adapters/index.js +24 -0
- package/dist/server/adapters/index.js.map +1 -0
- package/dist/server/adapters/runtime-node.d.ts +16 -0
- package/dist/server/adapters/runtime-node.d.ts.map +1 -0
- package/dist/server/adapters/runtime-node.js +29 -0
- package/dist/server/adapters/runtime-node.js.map +1 -0
- package/dist/server/adapters/runtime.d.ts +42 -0
- package/dist/server/adapters/runtime.d.ts.map +1 -0
- package/dist/server/adapters/runtime.js +115 -0
- package/dist/server/adapters/runtime.js.map +1 -0
- package/dist/server/adapters/shared.d.ts +66 -0
- package/dist/server/adapters/shared.d.ts.map +1 -0
- package/dist/server/adapters/shared.js +124 -0
- package/dist/server/adapters/shared.js.map +1 -0
- package/dist/server/adapters/static.d.ts +9 -0
- package/dist/server/adapters/static.d.ts.map +1 -0
- package/dist/server/adapters/static.js +70 -0
- package/dist/server/adapters/static.js.map +1 -0
- package/dist/server/adapters/vercel.d.ts +43 -0
- package/dist/server/adapters/vercel.d.ts.map +1 -0
- package/dist/server/adapters/vercel.js +119 -0
- package/dist/server/adapters/vercel.js.map +1 -0
- package/dist/server/bun-jsx.d.ts +18 -0
- package/dist/server/bun-jsx.d.ts.map +1 -0
- package/dist/server/bun-jsx.js +56 -0
- package/dist/server/bun-jsx.js.map +1 -0
- package/dist/server/cache-route.d.ts +38 -0
- package/dist/server/cache-route.d.ts.map +1 -0
- package/dist/server/cache-route.js +109 -0
- package/dist/server/cache-route.js.map +1 -0
- package/dist/server/cache.d.ts +131 -0
- package/dist/server/cache.d.ts.map +1 -0
- package/dist/server/cache.js +409 -0
- package/dist/server/cache.js.map +1 -0
- package/dist/server/frames.d.ts +45 -0
- package/dist/server/frames.d.ts.map +1 -0
- package/dist/server/frames.js +294 -0
- package/dist/server/frames.js.map +1 -0
- package/dist/server/http.d.ts +80 -0
- package/dist/server/http.d.ts.map +1 -0
- package/dist/server/http.js +453 -0
- package/dist/server/http.js.map +1 -0
- package/dist/server/jsx-dev-runtime.d.ts +5 -0
- package/dist/server/jsx-dev-runtime.d.ts.map +1 -0
- package/dist/server/jsx-dev-runtime.js +6 -0
- package/dist/server/jsx-dev-runtime.js.map +1 -0
- package/dist/server/jsx-runtime.d.ts +10 -0
- package/dist/server/jsx-runtime.d.ts.map +1 -0
- package/dist/server/jsx-runtime.js +11 -0
- package/dist/server/jsx-runtime.js.map +1 -0
- package/dist/server/live-app.d.ts +87 -0
- package/dist/server/live-app.d.ts.map +1 -0
- package/dist/server/live-app.js +307 -0
- package/dist/server/live-app.js.map +1 -0
- package/dist/server/live-build.d.ts +70 -0
- package/dist/server/live-build.d.ts.map +1 -0
- package/dist/server/live-build.js +222 -0
- package/dist/server/live-build.js.map +1 -0
- package/dist/server/live-dev.d.ts +62 -0
- package/dist/server/live-dev.d.ts.map +1 -0
- package/dist/server/live-dev.js +313 -0
- package/dist/server/live-dev.js.map +1 -0
- package/dist/server/live-manifest.d.ts +23 -0
- package/dist/server/live-manifest.d.ts.map +1 -0
- package/dist/server/live-manifest.js +7 -0
- package/dist/server/live-manifest.js.map +1 -0
- package/dist/server/live-prod.d.ts +30 -0
- package/dist/server/live-prod.d.ts.map +1 -0
- package/dist/server/live-prod.js +97 -0
- package/dist/server/live-prod.js.map +1 -0
- package/dist/server/live-routes.d.ts +72 -0
- package/dist/server/live-routes.d.ts.map +1 -0
- package/dist/server/live-routes.js +274 -0
- package/dist/server/live-routes.js.map +1 -0
- package/dist/server/live-web.d.ts +16 -0
- package/dist/server/live-web.d.ts.map +1 -0
- package/dist/server/live-web.js +159 -0
- package/dist/server/live-web.js.map +1 -0
- package/dist/server/live.d.ts +138 -0
- package/dist/server/live.d.ts.map +1 -0
- package/dist/server/live.js +542 -0
- package/dist/server/live.js.map +1 -0
- package/dist/server/middleware-node.d.ts +48 -0
- package/dist/server/middleware-node.d.ts.map +1 -0
- package/dist/server/middleware-node.js +167 -0
- package/dist/server/middleware-node.js.map +1 -0
- package/dist/server/middleware-runtime.d.ts +97 -0
- package/dist/server/middleware-runtime.d.ts.map +1 -0
- package/dist/server/middleware-runtime.js +447 -0
- package/dist/server/middleware-runtime.js.map +1 -0
- package/dist/server/middleware.d.ts +145 -0
- package/dist/server/middleware.d.ts.map +1 -0
- package/dist/server/middleware.js +365 -0
- package/dist/server/middleware.js.map +1 -0
- package/dist/server/ppr.d.ts +22 -0
- package/dist/server/ppr.d.ts.map +1 -0
- package/dist/server/ppr.js +58 -0
- package/dist/server/ppr.js.map +1 -0
- package/dist/server/scan.d.ts +48 -0
- package/dist/server/scan.d.ts.map +1 -0
- package/dist/server/scan.js +161 -0
- package/dist/server/scan.js.map +1 -0
- package/dist/server/server-components.d.ts +65 -0
- package/dist/server/server-components.d.ts.map +1 -0
- package/dist/server/server-components.js +215 -0
- package/dist/server/server-components.js.map +1 -0
- package/dist/terminal.d.ts +5 -0
- package/dist/terminal.d.ts.map +1 -0
- package/dist/terminal.js +5 -0
- package/dist/terminal.js.map +1 -0
- package/dist/testing.d.ts +5 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +5 -0
- package/dist/testing.js.map +1 -0
- package/dist/vite-plugin.d.ts +51 -0
- package/dist/vite-plugin.d.ts.map +1 -0
- package/dist/vite-plugin.js +403 -0
- package/dist/vite-plugin.js.map +1 -0
- package/package.json +130 -4
- package/src/cli/build.ts +182 -0
- package/src/cli/config.ts +72 -0
- package/src/cli/dev.ts +54 -0
- package/src/cli/index.ts +185 -0
- package/src/cli/run.ts +124 -0
- package/src/cli/start.ts +194 -0
- package/src/cli/ui.ts +93 -0
- package/src/client.tsx +122 -0
- package/src/config.ts +155 -0
- package/src/context.ts +69 -0
- package/src/dev-errors.ts +167 -0
- package/src/devtools.tsx +419 -0
- package/src/frame-mirror.ts +145 -0
- package/src/frame-protocol.ts +435 -0
- package/src/history.ts +201 -0
- package/src/index.ts +9 -0
- package/src/link.tsx +108 -0
- package/src/live-player.ts +449 -0
- package/src/live-protocol.ts +317 -0
- package/src/live-stub.tsx +32 -0
- package/src/mcp/cli.ts +188 -0
- package/src/mcp/frame.ts +224 -0
- package/src/mcp/index.ts +15 -0
- package/src/mcp/inspect.ts +445 -0
- package/src/mcp/keys.ts +86 -0
- package/src/mcp/logs.ts +225 -0
- package/src/mcp/server.ts +595 -0
- package/src/mcp/session.ts +821 -0
- package/src/navigation-errors.ts +60 -0
- package/src/navigation.ts +44 -0
- package/src/router.tsx +497 -0
- package/src/routing.ts +344 -0
- package/src/server/adapters/build-step.ts +135 -0
- package/src/server/adapters/bun.ts +92 -0
- package/src/server/adapters/bundle.ts +97 -0
- package/src/server/adapters/cloudflare.ts +111 -0
- package/src/server/adapters/index.ts +29 -0
- package/src/server/adapters/runtime-node.ts +34 -0
- package/src/server/adapters/runtime.ts +145 -0
- package/src/server/adapters/shared.ts +159 -0
- package/src/server/adapters/static.ts +68 -0
- package/src/server/adapters/vercel.ts +149 -0
- package/src/server/bun-jsx.ts +65 -0
- package/src/server/cache-route.ts +139 -0
- package/src/server/cache.ts +498 -0
- package/src/server/frames.ts +326 -0
- package/src/server/http.ts +467 -0
- package/src/server/jsx-dev-runtime.ts +9 -0
- package/src/server/jsx-runtime.ts +13 -0
- package/src/server/live-app.tsx +374 -0
- package/src/server/live-build.ts +258 -0
- package/src/server/live-dev.ts +357 -0
- package/src/server/live-manifest.ts +25 -0
- package/src/server/live-prod.ts +120 -0
- package/src/server/live-routes.ts +298 -0
- package/src/server/live-web.ts +170 -0
- package/src/server/live.ts +551 -0
- package/src/server/middleware-node.ts +168 -0
- package/src/server/middleware-runtime.ts +478 -0
- package/src/server/middleware.ts +451 -0
- package/src/server/ppr.ts +62 -0
- package/src/server/scan.ts +190 -0
- package/src/server/server-components.ts +224 -0
- package/src/terminal.ts +5 -0
- package/src/testing.ts +5 -0
- package/src/vite-plugin.ts +413 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Development error store feeding the in-console Redbox and the `[▼ !]` pill.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export type DevErrorKind = "runtime" | "build" | "unhandled";
|
|
6
|
+
|
|
7
|
+
export interface DevErrorLocation {
|
|
8
|
+
file: string;
|
|
9
|
+
line?: number;
|
|
10
|
+
column?: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface DevError {
|
|
14
|
+
id: number;
|
|
15
|
+
kind: DevErrorKind;
|
|
16
|
+
/** e.g. `TypeError`, `Build Error`. */
|
|
17
|
+
name: string;
|
|
18
|
+
message: string;
|
|
19
|
+
stack: string[];
|
|
20
|
+
location?: DevErrorLocation;
|
|
21
|
+
/** Code frame snippet (Vite / esbuild / Babel format). */
|
|
22
|
+
frame?: string;
|
|
23
|
+
plugin?: string;
|
|
24
|
+
componentStack?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface DevErrorSnapshot {
|
|
28
|
+
errors: readonly DevError[];
|
|
29
|
+
/** Whether the Redbox is showing (Esc hides it until the next error). */
|
|
30
|
+
visible: boolean;
|
|
31
|
+
/** Bumped whenever errors are cleared (lets boundaries recover after a fix). */
|
|
32
|
+
generation: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Minimal shape of Vite's `ErrorPayload["err"]`. */
|
|
36
|
+
export interface ViteErrorLike {
|
|
37
|
+
message: string;
|
|
38
|
+
stack?: string;
|
|
39
|
+
id?: string;
|
|
40
|
+
frame?: string;
|
|
41
|
+
plugin?: string;
|
|
42
|
+
loc?: { file?: string; line: number; column: number };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const STACK_LOCATION = /\(?((?:https?:\/\/[^/\s]+)?(\/[^\s()]*?|[A-Za-z]:\\[^\s()]*?)):(\d+):(\d+)\)?\s*$/;
|
|
46
|
+
|
|
47
|
+
/** Strip the dev server origin, `/@fs` and cache-busting queries from a stack URL. */
|
|
48
|
+
export function cleanSourcePath(file: string): string {
|
|
49
|
+
let f = file.replace(/^https?:\/\/[^/]+/, "");
|
|
50
|
+
f = f.replace(/^\/@fs/, "");
|
|
51
|
+
f = f.replace(/\?[^:]*$/, "");
|
|
52
|
+
const proc = (globalThis as { process?: { cwd?: () => string } }).process;
|
|
53
|
+
const cwd = typeof proc?.cwd === "function" ? proc.cwd() : "";
|
|
54
|
+
if (cwd && f.startsWith(`${cwd}/`)) f = f.slice(cwd.length + 1);
|
|
55
|
+
return f.replace(/^\//, "");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function parseStack(stack: string | undefined): string[] {
|
|
59
|
+
if (!stack) return [];
|
|
60
|
+
return stack
|
|
61
|
+
.split("\n")
|
|
62
|
+
.map((l) => l.trim())
|
|
63
|
+
.filter((l) => l.startsWith("at ") || /@.+:\d+:\d+$/.test(l));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function locationFromStack(lines: readonly string[]): DevErrorLocation | undefined {
|
|
67
|
+
for (const line of lines) {
|
|
68
|
+
if (/node_modules|react-dom|react-reconciler|scheduler|\/@textjs\/|packages\/(react|core)\/src/.test(line)) continue;
|
|
69
|
+
const m = STACK_LOCATION.exec(line);
|
|
70
|
+
if (m) return { file: cleanSourcePath(m[1] as string), line: Number(m[3]), column: Number(m[4]) };
|
|
71
|
+
}
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function toDevError(error: unknown, kind: DevErrorKind, id: number, componentStack?: string | null): DevError {
|
|
76
|
+
if (error instanceof Error) {
|
|
77
|
+
const stack = parseStack(error.stack);
|
|
78
|
+
return {
|
|
79
|
+
id,
|
|
80
|
+
kind,
|
|
81
|
+
name: error.name || "Error",
|
|
82
|
+
message: error.message,
|
|
83
|
+
stack,
|
|
84
|
+
location: locationFromStack(stack),
|
|
85
|
+
componentStack: componentStack ?? undefined,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
return { id, kind, name: "Error", message: typeof error === "string" ? error : safeString(error), stack: [], componentStack: componentStack ?? undefined };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function safeString(v: unknown): string {
|
|
92
|
+
try {
|
|
93
|
+
return JSON.stringify(v) ?? String(v);
|
|
94
|
+
} catch {
|
|
95
|
+
return String(v);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export class DevErrorStore {
|
|
100
|
+
private snapshot: DevErrorSnapshot = { errors: [], visible: false, generation: 0 };
|
|
101
|
+
private readonly listeners = new Set<() => void>();
|
|
102
|
+
private nextId = 1;
|
|
103
|
+
|
|
104
|
+
readonly subscribe = (listener: () => void): (() => void) => {
|
|
105
|
+
this.listeners.add(listener);
|
|
106
|
+
return () => this.listeners.delete(listener);
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
readonly getSnapshot = (): DevErrorSnapshot => this.snapshot;
|
|
110
|
+
|
|
111
|
+
private set(next: DevErrorSnapshot): void {
|
|
112
|
+
this.snapshot = next;
|
|
113
|
+
for (const l of [...this.listeners]) l();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private push(entry: DevError): DevError {
|
|
117
|
+
const errors = this.snapshot.errors;
|
|
118
|
+
const dup = errors.find((e) => e.kind === entry.kind && e.message === entry.message && e.location?.file === entry.location?.file);
|
|
119
|
+
if (dup) {
|
|
120
|
+
if (!this.snapshot.visible) this.set({ ...this.snapshot, visible: true });
|
|
121
|
+
return dup;
|
|
122
|
+
}
|
|
123
|
+
this.set({ ...this.snapshot, errors: [...errors, entry].slice(-20), visible: true });
|
|
124
|
+
return entry;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Report a runtime render / event error. */
|
|
128
|
+
reportRuntime(error: unknown, componentStack?: string | null, kind: DevErrorKind = "runtime"): DevError {
|
|
129
|
+
return this.push(toDevError(error, kind, this.nextId++, componentStack));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Report a Vite build / transform error (`vite:error`). */
|
|
133
|
+
reportBuild(err: ViteErrorLike): DevError {
|
|
134
|
+
const stack = parseStack(err.stack);
|
|
135
|
+
const file = err.loc?.file ?? err.id;
|
|
136
|
+
const message = err.message.replace(/\x1b\[[0-9;]*m/g, "");
|
|
137
|
+
return this.push({
|
|
138
|
+
id: this.nextId++,
|
|
139
|
+
kind: "build",
|
|
140
|
+
name: "Build Error",
|
|
141
|
+
message,
|
|
142
|
+
stack,
|
|
143
|
+
location: file ? { file: cleanSourcePath(file), line: err.loc?.line, column: err.loc?.column } : undefined,
|
|
144
|
+
frame: err.frame?.replace(/\x1b\[[0-9;]*m/g, ""),
|
|
145
|
+
plugin: err.plugin,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Hide the Redbox (errors stay listed in DevTools until cleared). */
|
|
150
|
+
dismiss(): void {
|
|
151
|
+
if (this.snapshot.visible) this.set({ ...this.snapshot, visible: false });
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
show(): void {
|
|
155
|
+
if (!this.snapshot.visible && this.snapshot.errors.length > 0) this.set({ ...this.snapshot, visible: true });
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Clear errors (all, or one kind) — e.g. after a successful HMR update. */
|
|
159
|
+
clear(kind?: DevErrorKind): void {
|
|
160
|
+
const errors = kind ? this.snapshot.errors.filter((e) => e.kind !== kind) : [];
|
|
161
|
+
if (errors.length === this.snapshot.errors.length) return;
|
|
162
|
+
this.set({ errors, visible: this.snapshot.visible && errors.length > 0, generation: this.snapshot.generation + 1 });
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** The process-wide store used by `text dev`. */
|
|
167
|
+
export const devErrors = new DevErrorStore();
|
package/src/devtools.tsx
ADDED
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@textjs/app/devtools` — the in-console Dev Overlay (`[▼]` pill and inspector), and the
|
|
3
|
+
* Next.js-style Redbox error overlay, all rendered inside `console.log`.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useContext, useLayoutEffect, useMemo, useState, useSyncExternalStore, type ReactNode } from "react";
|
|
7
|
+
import { BRAND_MARK, ENGINE_NAME, VERSION } from "@textjs/core";
|
|
8
|
+
import {
|
|
9
|
+
Badge,
|
|
10
|
+
Box,
|
|
11
|
+
createScope,
|
|
12
|
+
Divider,
|
|
13
|
+
ScopeContext,
|
|
14
|
+
ScrollBox,
|
|
15
|
+
Text,
|
|
16
|
+
useApp,
|
|
17
|
+
useConsoleStats,
|
|
18
|
+
useInput,
|
|
19
|
+
useRuntime,
|
|
20
|
+
useTheme,
|
|
21
|
+
useViewport,
|
|
22
|
+
type KeyEvent,
|
|
23
|
+
type Scope,
|
|
24
|
+
} from "@textjs/react";
|
|
25
|
+
import { useOptionalAppShell, useOptionalRouterState, useRouterState, type AppShellState } from "./context";
|
|
26
|
+
import { devErrors, type DevError, type DevErrorStore, type ViteErrorLike } from "./dev-errors";
|
|
27
|
+
import { isNavigationError } from "./navigation-errors";
|
|
28
|
+
import { formatParams, type RouteMatch } from "./routing";
|
|
29
|
+
|
|
30
|
+
export { devErrors, DevErrorStore, type DevError, type DevErrorKind, type DevErrorSnapshot, type ViteErrorLike } from "./dev-errors";
|
|
31
|
+
|
|
32
|
+
/** `F2` or `` Ctrl+` `` toggles the Dev Overlay inspector. */
|
|
33
|
+
export function isDevToolsShortcut(e: { key: string; code?: string; ctrl: boolean; meta: boolean; alt: boolean }): boolean {
|
|
34
|
+
if (e.key === "f2" && !e.ctrl && !e.meta && !e.alt) return true;
|
|
35
|
+
return e.ctrl && !e.meta && (e.key === "`" || e.code === "Backquote");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Current route details (pattern, params, `○ Static` / `ƒ Dynamic`) for inspectors. */
|
|
39
|
+
export function useRouteInfo(): { match: RouteMatch; title: string | undefined; pathname: string } {
|
|
40
|
+
const s = useRouterState();
|
|
41
|
+
return { match: s.match, title: s.metadata.title, pathname: s.location.pathname };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** A focus + input trap for an overlay layer (like `<Modal>`, without the panel chrome). */
|
|
45
|
+
function useOverlayScope(): Scope {
|
|
46
|
+
const rt = useRuntime();
|
|
47
|
+
const parent = useContext(ScopeContext);
|
|
48
|
+
const scope = useMemo(() => createScope(parent, true), [parent]);
|
|
49
|
+
useLayoutEffect(() => rt.focus.pushTrap(scope), [rt, scope]);
|
|
50
|
+
return scope;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function useErrors(store: DevErrorStore) {
|
|
54
|
+
return useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
// <TextDevOverlay />
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
|
|
61
|
+
export interface TextDevOverlayProps {
|
|
62
|
+
/** Start with the inspector open. */
|
|
63
|
+
initialOpen?: boolean;
|
|
64
|
+
/** Also mount `<RedboxOverlay />` (default `true`). */
|
|
65
|
+
redbox?: boolean;
|
|
66
|
+
store?: DevErrorStore;
|
|
67
|
+
/** Draw the `[▼]` pill (default `true`). Without it, the shortcut still opens the inspector. */
|
|
68
|
+
pill?: boolean;
|
|
69
|
+
/** Keys that toggle the inspector (default `isDevToolsShortcut`: `F2` or `` Ctrl+` ``). */
|
|
70
|
+
shortcut?: (e: KeyEvent) => boolean;
|
|
71
|
+
/** Theme and wireframe controls for apps without the App Router, which otherwise provides them. */
|
|
72
|
+
shell?: AppShellState;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The `[▼]` pill in the bottom-left corner of the console frame. `F2` or
|
|
77
|
+
* `` Ctrl+` `` opens the Dev Overlay inspector.
|
|
78
|
+
*/
|
|
79
|
+
export function TextDevOverlay({ initialOpen = false, redbox = true, store = devErrors, pill = true, shortcut = isDevToolsShortcut, shell }: TextDevOverlayProps): ReactNode {
|
|
80
|
+
const theme = useTheme();
|
|
81
|
+
const [open, setOpen] = useState(initialOpen);
|
|
82
|
+
const errors = useErrors(store);
|
|
83
|
+
const hasErrors = errors.errors.length > 0;
|
|
84
|
+
|
|
85
|
+
useInput(
|
|
86
|
+
(e) => {
|
|
87
|
+
if (!shortcut(e)) return;
|
|
88
|
+
e.stopPropagation();
|
|
89
|
+
setOpen((v) => !v);
|
|
90
|
+
},
|
|
91
|
+
{ global: true, priority: 1000 },
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<>
|
|
96
|
+
{pill ? (
|
|
97
|
+
<Box position="absolute" left={0} bottom={0} zIndex={40} height={1} flexDirection="row" wireframeLabel="devtools-pill">
|
|
98
|
+
<Text bg={hasErrors ? theme.danger : theme.elevated} fg={hasErrors ? "#ffffff" : theme.muted} bold={hasErrors} wrap="nowrap">
|
|
99
|
+
{hasErrors ? `[${BRAND_MARK} !]` : `[${BRAND_MARK}]`}
|
|
100
|
+
</Text>
|
|
101
|
+
</Box>
|
|
102
|
+
) : null}
|
|
103
|
+
{open ? <DevToolsPanel store={store} shell={shell} onClose={() => setOpen(false)} /> : null}
|
|
104
|
+
{redbox && !open ? <RedboxOverlay store={store} /> : null}
|
|
105
|
+
</>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function Row({ label, children, color }: { label: string; children: ReactNode; color?: string }): ReactNode {
|
|
110
|
+
const theme = useTheme();
|
|
111
|
+
return (
|
|
112
|
+
<Box height={1} flexDirection="row" flexShrink={0}>
|
|
113
|
+
<Text color={theme.subtle} wrap="nowrap" width={10} flexShrink={0}>
|
|
114
|
+
{label}
|
|
115
|
+
</Text>
|
|
116
|
+
<Text color={color ?? theme.fg} wrap="truncate-end" flexShrink={1}>
|
|
117
|
+
{children}
|
|
118
|
+
</Text>
|
|
119
|
+
</Box>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function Section({ title, children }: { title: string; children: ReactNode }): ReactNode {
|
|
124
|
+
const theme = useTheme();
|
|
125
|
+
return (
|
|
126
|
+
<Box flexDirection="column" flexGrow={1} flexBasis={0} minWidth={24}>
|
|
127
|
+
<Text color={theme.muted} bold wrap="nowrap">
|
|
128
|
+
{title}
|
|
129
|
+
</Text>
|
|
130
|
+
{children}
|
|
131
|
+
</Box>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function ms(n: number): string {
|
|
136
|
+
return `${n.toFixed(2)}ms`;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
interface DevToolsPanelProps {
|
|
140
|
+
store: DevErrorStore;
|
|
141
|
+
shell: AppShellState | undefined;
|
|
142
|
+
onClose: () => void;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function DevToolsPanel(props: DevToolsPanelProps): ReactNode {
|
|
146
|
+
const scope = useOverlayScope();
|
|
147
|
+
return (
|
|
148
|
+
<ScopeContext value={scope}>
|
|
149
|
+
<DevToolsPanelBody {...props} />
|
|
150
|
+
</ScopeContext>
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function DevToolsPanelBody({ store, shell: shellProp, onClose }: DevToolsPanelProps): ReactNode {
|
|
155
|
+
const theme = useTheme();
|
|
156
|
+
const app = useApp();
|
|
157
|
+
const viewport = useViewport();
|
|
158
|
+
const stats = useConsoleStats({ interval: 250 });
|
|
159
|
+
const contextShell = useOptionalAppShell();
|
|
160
|
+
const shell = shellProp ?? contextShell;
|
|
161
|
+
const router = useOptionalRouterState();
|
|
162
|
+
const errors = useErrors(store);
|
|
163
|
+
const [localWireframe, setLocalWireframe] = useState(false);
|
|
164
|
+
const wireframe = shell ? shell.wireframe : localWireframe;
|
|
165
|
+
|
|
166
|
+
useInput((e) => {
|
|
167
|
+
if (e.ctrl || e.meta || e.alt) return;
|
|
168
|
+
if (e.key === "escape") onClose();
|
|
169
|
+
else if (e.key === "w") {
|
|
170
|
+
if (shell) shell.setWireframe(!wireframe);
|
|
171
|
+
else {
|
|
172
|
+
app.renderer.setWireframe(!localWireframe);
|
|
173
|
+
setLocalWireframe(!localWireframe);
|
|
174
|
+
}
|
|
175
|
+
} else if (e.key === "t") shell?.cycleTheme();
|
|
176
|
+
else if (e.key === "r") viewport.setResponsive(!viewport.responsive);
|
|
177
|
+
else if (e.key === "return" && errors.errors.length > 0) {
|
|
178
|
+
store.show();
|
|
179
|
+
onClose();
|
|
180
|
+
} else return;
|
|
181
|
+
e.stopPropagation();
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
const match = router?.match;
|
|
185
|
+
const typeLabel = match ? (match.notFound ? "⚠ Not Found" : match.dynamic ? "ƒ Dynamic" : "○ Static") : "—";
|
|
186
|
+
|
|
187
|
+
return (
|
|
188
|
+
<Box position="absolute" top={0} left={0} right={0} bottom={0} zIndex={45} backdropDim={0.45} flexDirection="column" justifyContent="flex-end" wireframeLabel="devtools">
|
|
189
|
+
<Box
|
|
190
|
+
flexDirection="column"
|
|
191
|
+
flexShrink={0}
|
|
192
|
+
border="rounded"
|
|
193
|
+
borderColor={theme.borderFocus}
|
|
194
|
+
bg={theme.elevated}
|
|
195
|
+
fg={theme.fg}
|
|
196
|
+
paddingX={1}
|
|
197
|
+
title={` ${BRAND_MARK} Dev Overlay · ${ENGINE_NAME} `}
|
|
198
|
+
titleColor={theme.fg}
|
|
199
|
+
bottomTitle=" W wireframe · T theme · R viewport · esc close "
|
|
200
|
+
bottomTitleColor={theme.subtle}
|
|
201
|
+
bottomTitleAlign="right"
|
|
202
|
+
>
|
|
203
|
+
<Box flexDirection="row" flexWrap="wrap" columnGap={2}>
|
|
204
|
+
<Section title="ROUTE">
|
|
205
|
+
<Row label="type" color={match?.dynamic ? theme.warning : theme.success}>
|
|
206
|
+
{typeLabel}
|
|
207
|
+
</Row>
|
|
208
|
+
<Row label="pattern">{match?.pattern ?? "—"}</Row>
|
|
209
|
+
<Row label="pathname">{router?.location.pathname ?? (typeof location !== "undefined" ? location.pathname : "—")}</Row>
|
|
210
|
+
<Row label="params">{match ? formatParams(match.params) : "{}"}</Row>
|
|
211
|
+
<Row label="title">{router?.metadata.title ?? "—"}</Row>
|
|
212
|
+
<Row label="segments">{match ? String(match.chain.length) : "0"}</Row>
|
|
213
|
+
</Section>
|
|
214
|
+
<Section title="FRAME">
|
|
215
|
+
<Row label="grid">{`${stats.cols}×${stats.rows} cells`}</Row>
|
|
216
|
+
<Row label="spans">{`${stats.spanCount} %c spans`}</Row>
|
|
217
|
+
<Row label="total">{ms(stats.totalMs)}</Row>
|
|
218
|
+
<Row label="phases">{`${stats.layoutMs.toFixed(2)} / ${stats.rasterMs.toFixed(2)} / ${stats.encodeMs.toFixed(2)}`}</Row>
|
|
219
|
+
<Row label="yoga">{`${stats.yogaNodes} nodes`}</Row>
|
|
220
|
+
<Row label="gated" color={theme.success}>{`${stats.skippedFrames} noop frames`}</Row>
|
|
221
|
+
</Section>
|
|
222
|
+
<Section title="TOGGLES">
|
|
223
|
+
<Row label="[W] wire" color={wireframe ? theme.success : theme.muted}>
|
|
224
|
+
{wireframe ? "on" : "off"}
|
|
225
|
+
</Row>
|
|
226
|
+
<Row label="[T] theme" color={theme.accent}>
|
|
227
|
+
{shell ? shell.theme.name : theme.name}
|
|
228
|
+
</Row>
|
|
229
|
+
<Row label="[R] view">{viewport.responsive ? "responsive" : `fixed ${viewport.cols}×${viewport.rows}`}</Row>
|
|
230
|
+
<Row label="issues" color={errors.errors.length > 0 ? theme.danger : theme.success}>
|
|
231
|
+
{errors.errors.length > 0 ? `${errors.errors.length} · enter to open` : "0 · all clear"}
|
|
232
|
+
</Row>
|
|
233
|
+
<Row label="engine">{`${ENGINE_NAME} ${VERSION}`}</Row>
|
|
234
|
+
</Section>
|
|
235
|
+
</Box>
|
|
236
|
+
</Box>
|
|
237
|
+
</Box>
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// ---------------------------------------------------------------------------
|
|
242
|
+
// <RedboxOverlay />
|
|
243
|
+
// ---------------------------------------------------------------------------
|
|
244
|
+
|
|
245
|
+
export interface RedboxOverlayProps {
|
|
246
|
+
store?: DevErrorStore;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** The Next.js-style Redbox, rendered inside `console.log`. `Esc` dismisses. */
|
|
250
|
+
export function RedboxOverlay({ store = devErrors }: RedboxOverlayProps): ReactNode {
|
|
251
|
+
const snap = useErrors(store);
|
|
252
|
+
if (!snap.visible || snap.errors.length === 0) return null;
|
|
253
|
+
return <RedboxLayer store={store} errors={snap.errors} />;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function RedboxLayer({ store, errors }: { store: DevErrorStore; errors: readonly DevError[] }): ReactNode {
|
|
257
|
+
const scope = useOverlayScope();
|
|
258
|
+
return (
|
|
259
|
+
<ScopeContext value={scope}>
|
|
260
|
+
<RedboxBody store={store} errors={errors} />
|
|
261
|
+
</ScopeContext>
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function RedboxBody({ store, errors }: { store: DevErrorStore; errors: readonly DevError[] }): ReactNode {
|
|
266
|
+
const theme = useTheme();
|
|
267
|
+
const [index, setIndex] = useState(errors.length - 1);
|
|
268
|
+
const i = Math.max(0, Math.min(errors.length - 1, index));
|
|
269
|
+
const err = errors[i] as DevError;
|
|
270
|
+
|
|
271
|
+
useInput((e) => {
|
|
272
|
+
if (e.key === "escape") store.dismiss();
|
|
273
|
+
else if (e.key === "left") setIndex(Math.max(0, i - 1));
|
|
274
|
+
else if (e.key === "right") setIndex(Math.min(errors.length - 1, i + 1));
|
|
275
|
+
else return;
|
|
276
|
+
e.stopPropagation();
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
const heading = err.kind === "build" ? "Build Error" : err.kind === "unhandled" ? "Unhandled Runtime Error" : "Unhandled Runtime Error";
|
|
280
|
+
const loc = err.location ? `${err.location.file}${err.location.line !== undefined ? `:${err.location.line}` : ""}${err.location.column !== undefined ? `:${err.location.column}` : ""}` : null;
|
|
281
|
+
const frame = err.frame ? err.frame.replace(/\n+$/, "").split("\n") : [];
|
|
282
|
+
const componentStack = err.componentStack
|
|
283
|
+
? err.componentStack
|
|
284
|
+
.split("\n")
|
|
285
|
+
.map((l) => l.trim())
|
|
286
|
+
.filter(Boolean)
|
|
287
|
+
: [];
|
|
288
|
+
|
|
289
|
+
return (
|
|
290
|
+
<Box position="absolute" top={0} left={0} right={0} bottom={0} zIndex={60} backdropDim={0.75} alignItems="center" justifyContent="center" flexDirection="column" wireframeLabel="redbox">
|
|
291
|
+
<Box
|
|
292
|
+
flexDirection="column"
|
|
293
|
+
width="94%"
|
|
294
|
+
height="92%"
|
|
295
|
+
border="rounded"
|
|
296
|
+
borderColor={theme.danger}
|
|
297
|
+
bg={theme.elevated}
|
|
298
|
+
fg={theme.fg}
|
|
299
|
+
paddingX={2}
|
|
300
|
+
paddingY={1}
|
|
301
|
+
overflow="hidden"
|
|
302
|
+
title={` ✕ ${heading} `}
|
|
303
|
+
titleColor={theme.danger}
|
|
304
|
+
bottomTitle=" esc dismiss · ←/→ errors · ↑/↓ scroll "
|
|
305
|
+
bottomTitleColor={theme.subtle}
|
|
306
|
+
bottomTitleAlign="right"
|
|
307
|
+
>
|
|
308
|
+
<Box height={1} flexDirection="row" flexShrink={0}>
|
|
309
|
+
<Badge variant="danger">{`${i + 1} of ${errors.length} ${errors.length === 1 ? "error" : "errors"}`}</Badge>
|
|
310
|
+
<Box flexGrow={1} />
|
|
311
|
+
<Text color={theme.subtle} wrap="nowrap">
|
|
312
|
+
{`${BRAND_MARK} Text.js ${VERSION} (${ENGINE_NAME})`}
|
|
313
|
+
</Text>
|
|
314
|
+
</Box>
|
|
315
|
+
<Box height={1} flexShrink={0} />
|
|
316
|
+
<Text color={theme.danger} bold flexShrink={0}>
|
|
317
|
+
{err.kind === "build" ? err.message : `${err.name}: ${err.message}`}
|
|
318
|
+
</Text>
|
|
319
|
+
{loc ? (
|
|
320
|
+
<Text color={theme.accent} underline wrap="truncate-start" flexShrink={0}>
|
|
321
|
+
{loc}
|
|
322
|
+
</Text>
|
|
323
|
+
) : null}
|
|
324
|
+
{err.plugin ? (
|
|
325
|
+
<Text color={theme.subtle} flexShrink={0}>
|
|
326
|
+
{`plugin: ${err.plugin}`}
|
|
327
|
+
</Text>
|
|
328
|
+
) : null}
|
|
329
|
+
{frame.length > 0 ? (
|
|
330
|
+
<Box flexDirection="column" flexShrink={0} marginTop={1} bg={theme.codeBg} paddingX={1} maxHeight={10} overflow="hidden">
|
|
331
|
+
{frame.map((line, n) => (
|
|
332
|
+
<Text key={n} wrap="nowrap" color={line.trimStart().startsWith(">") ? theme.danger : /^\s*\|\s*\^/.test(line) ? theme.danger : theme.muted} bold={line.trimStart().startsWith(">")}>
|
|
333
|
+
{line || " "}
|
|
334
|
+
</Text>
|
|
335
|
+
))}
|
|
336
|
+
</Box>
|
|
337
|
+
) : null}
|
|
338
|
+
<Box height={1} flexShrink={0} marginTop={1}>
|
|
339
|
+
<Divider title="Call Stack" />
|
|
340
|
+
</Box>
|
|
341
|
+
<ScrollBox flexGrow={1} focusable autoFocus>
|
|
342
|
+
{err.stack.length === 0 && componentStack.length === 0 ? (
|
|
343
|
+
<Text color={theme.subtle} italic>
|
|
344
|
+
No stack trace available.
|
|
345
|
+
</Text>
|
|
346
|
+
) : null}
|
|
347
|
+
{err.stack.map((line, n) => (
|
|
348
|
+
<Text key={`s${n}`} color={theme.muted} wrap="truncate-middle">
|
|
349
|
+
{line}
|
|
350
|
+
</Text>
|
|
351
|
+
))}
|
|
352
|
+
{componentStack.length > 0 ? (
|
|
353
|
+
<Text color={theme.subtle} bold>
|
|
354
|
+
{"Component Stack"}
|
|
355
|
+
</Text>
|
|
356
|
+
) : null}
|
|
357
|
+
{componentStack.map((line, n) => (
|
|
358
|
+
<Text key={`c${n}`} color={theme.subtle} wrap="truncate-middle">
|
|
359
|
+
{line}
|
|
360
|
+
</Text>
|
|
361
|
+
))}
|
|
362
|
+
</ScrollBox>
|
|
363
|
+
</Box>
|
|
364
|
+
</Box>
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// ---------------------------------------------------------------------------
|
|
369
|
+
// Vite HMR + global error wiring
|
|
370
|
+
// ---------------------------------------------------------------------------
|
|
371
|
+
|
|
372
|
+
/** The subset of Vite's `import.meta.hot` used by the Redbox. */
|
|
373
|
+
export interface HotContextLike {
|
|
374
|
+
on(event: string, callback: (payload: any) => void): void;
|
|
375
|
+
off?(event: string, callback: (payload: any) => void): void;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Show Vite build / syntax errors in the in-console Redbox and clear them
|
|
380
|
+
* (recovering crashed routes) once an HMR update applies cleanly.
|
|
381
|
+
*/
|
|
382
|
+
export function connectViteHmr(hot: HotContextLike, store: DevErrorStore = devErrors): () => void {
|
|
383
|
+
const onError = (payload: { err: ViteErrorLike }): void => {
|
|
384
|
+
if (payload?.err) store.reportBuild(payload.err);
|
|
385
|
+
};
|
|
386
|
+
const onBeforeUpdate = (): void => store.clear("build");
|
|
387
|
+
const onAfterUpdate = (): void => store.clear();
|
|
388
|
+
hot.on("vite:error", onError);
|
|
389
|
+
hot.on("vite:beforeUpdate", onBeforeUpdate);
|
|
390
|
+
hot.on("vite:afterUpdate", onAfterUpdate);
|
|
391
|
+
return () => {
|
|
392
|
+
hot.off?.("vite:error", onError);
|
|
393
|
+
hot.off?.("vite:beforeUpdate", onBeforeUpdate);
|
|
394
|
+
hot.off?.("vite:afterUpdate", onAfterUpdate);
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
interface ErrorTarget {
|
|
399
|
+
addEventListener(type: string, listener: (event: any) => void): void;
|
|
400
|
+
removeEventListener(type: string, listener: (event: any) => void): void;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/** Report uncaught exceptions and unhandled rejections to the Redbox. */
|
|
404
|
+
export function installGlobalErrorHandlers(target: ErrorTarget | undefined = typeof window !== "undefined" ? window : undefined, store: DevErrorStore = devErrors): () => void {
|
|
405
|
+
if (!target) return () => undefined;
|
|
406
|
+
const onError = (e: { error?: unknown; message?: string }): void => {
|
|
407
|
+
const error = e.error ?? e.message;
|
|
408
|
+
if (error !== undefined && !isNavigationError(error)) store.reportRuntime(error, null, "unhandled");
|
|
409
|
+
};
|
|
410
|
+
const onRejection = (e: { reason?: unknown }): void => {
|
|
411
|
+
if (!isNavigationError(e.reason)) store.reportRuntime(e.reason, null, "unhandled");
|
|
412
|
+
};
|
|
413
|
+
target.addEventListener("error", onError);
|
|
414
|
+
target.addEventListener("unhandledrejection", onRejection);
|
|
415
|
+
return () => {
|
|
416
|
+
target.removeEventListener("error", onError);
|
|
417
|
+
target.removeEventListener("unhandledrejection", onRejection);
|
|
418
|
+
};
|
|
419
|
+
}
|