@timber-js/app 0.2.0-alpha.157 → 0.2.0-alpha.159
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/dist/_chunks/{actions-CqE7SW7E.js → actions-CBbP1hHO.js} +22 -6
- package/dist/_chunks/actions-CBbP1hHO.js.map +1 -0
- package/dist/_chunks/{build-output-helper-C8q4Zpf5.js → build-output-helper-DF5uScqS.js} +4 -1
- package/dist/_chunks/{build-output-helper-C8q4Zpf5.js.map → build-output-helper-DF5uScqS.js.map} +1 -1
- package/dist/_chunks/cache-api-DsWEYwVZ.js +1120 -0
- package/dist/_chunks/cache-api-DsWEYwVZ.js.map +1 -0
- package/dist/_chunks/{cli-schema-sync-DcmQrzJG.js → cli-schema-sync-Bivx4Rn7.js} +4 -3
- package/dist/_chunks/{cli-schema-sync-DcmQrzJG.js.map → cli-schema-sync-Bivx4Rn7.js.map} +1 -1
- package/dist/_chunks/dist-BA3u1z3W.js +423 -0
- package/dist/_chunks/dist-BA3u1z3W.js.map +1 -0
- package/dist/_chunks/{logger-C5v1IMuh.js → logger-CrRmZMlo.js} +81 -56
- package/dist/_chunks/logger-CrRmZMlo.js.map +1 -0
- package/dist/_chunks/{plugin-context-DUl3dnCF.js → plugin-context-DeAxFRMq.js} +3 -36
- package/dist/_chunks/plugin-context-DeAxFRMq.js.map +1 -0
- package/dist/_chunks/sizeof-lXffQ1Vr.js +35 -0
- package/dist/_chunks/sizeof-lXffQ1Vr.js.map +1 -0
- package/dist/_chunks/{walkers-D5jCGPum.js → walkers-DXbJkeCY.js} +217 -19
- package/dist/_chunks/walkers-DXbJkeCY.js.map +1 -0
- package/dist/adapters/build-output-helper.d.ts.map +1 -1
- package/dist/adapters/cloudflare.d.ts +28 -0
- package/dist/adapters/cloudflare.d.ts.map +1 -1
- package/dist/adapters/cloudflare.js +143 -9
- package/dist/adapters/cloudflare.js.map +1 -1
- package/dist/adapters/nitro.js +1 -1
- package/dist/cache/cache-api.d.ts +2 -0
- package/dist/cache/cache-api.d.ts.map +1 -1
- package/dist/cache/handler-store.d.ts +2 -0
- package/dist/cache/handler-store.d.ts.map +1 -1
- package/dist/cache/index.d.ts +2 -0
- package/dist/cache/index.d.ts.map +1 -1
- package/dist/cache/index.js +3 -186
- package/dist/cache/redis-handler.d.ts +17 -5
- package/dist/cache/redis-handler.d.ts.map +1 -1
- package/dist/cache/singleflight.d.ts +1 -1
- package/dist/cache/singleflight.d.ts.map +1 -1
- package/dist/cache/store.d.ts +53 -0
- package/dist/cache/store.d.ts.map +1 -0
- package/dist/cache/stores/cloudflare-kv.d.ts +38 -0
- package/dist/cache/stores/cloudflare-kv.d.ts.map +1 -0
- package/dist/cache/stores/cloudflare-kv.js +29 -0
- package/dist/cache/stores/cloudflare-kv.js.map +1 -0
- package/dist/cache/stores/memory.d.ts +42 -0
- package/dist/cache/stores/memory.d.ts.map +1 -0
- package/dist/cache/stores/memory.js +90 -0
- package/dist/cache/stores/memory.js.map +1 -0
- package/dist/cache/stores/redis.d.ts +69 -0
- package/dist/cache/stores/redis.d.ts.map +1 -0
- package/dist/cache/stores/redis.js +35 -0
- package/dist/cache/stores/redis.js.map +1 -0
- package/dist/cache/stores/vercel.d.ts +36 -0
- package/dist/cache/stores/vercel.d.ts.map +1 -0
- package/dist/cache/stores/vercel.js +33 -0
- package/dist/cache/stores/vercel.js.map +1 -0
- package/dist/cache/tag-aware-handler.d.ts +51 -0
- package/dist/cache/tag-aware-handler.d.ts.map +1 -0
- package/dist/cache/timber-cache.d.ts.map +1 -1
- package/dist/cdn/cloudflare-purge.d.ts +23 -0
- package/dist/cdn/cloudflare-purge.d.ts.map +1 -0
- package/dist/cdn/fastly-purge.d.ts +25 -0
- package/dist/cdn/fastly-purge.d.ts.map +1 -0
- package/dist/cdn/headers.d.ts +82 -0
- package/dist/cdn/headers.d.ts.map +1 -0
- package/dist/cdn/index.d.ts +5 -0
- package/dist/cdn/index.d.ts.map +1 -0
- package/dist/cdn/purge-store.d.ts +23 -0
- package/dist/cdn/purge-store.d.ts.map +1 -0
- package/dist/cli.js +2 -2
- package/dist/client/browser-entry/action-dispatch.d.ts.map +1 -1
- package/dist/client/browser-entry/post-hydration.d.ts.map +1 -1
- package/dist/client/browser-entry/router-init.d.ts.map +1 -1
- package/dist/client/history.d.ts +0 -3
- package/dist/client/history.d.ts.map +1 -1
- package/dist/client/index.js +0 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/internal.js +11 -86
- package/dist/client/internal.js.map +1 -1
- package/dist/client/navigation-api.d.ts +1 -3
- package/dist/client/navigation-api.d.ts.map +1 -1
- package/dist/client/router.d.ts +1 -4
- package/dist/client/router.d.ts.map +1 -1
- package/dist/client/rsc-fetch.d.ts +1 -11
- package/dist/client/rsc-fetch.d.ts.map +1 -1
- package/dist/client/segment-cache.d.ts +1 -3
- package/dist/client/segment-cache.d.ts.map +1 -1
- package/dist/client/use-cookie.d.ts.map +1 -1
- package/dist/config-types.d.ts +3 -1
- package/dist/config-types.d.ts.map +1 -1
- package/dist/config-validation.d.ts.map +1 -1
- package/dist/cookies/index.js +15 -11
- package/dist/cookies/index.js.map +1 -1
- package/dist/dev-tools/browser-logs.d.ts +28 -72
- package/dist/dev-tools/browser-logs.d.ts.map +1 -1
- package/dist/dev-tools/index.d.ts +1 -1
- package/dist/dev-tools/index.d.ts.map +1 -1
- package/dist/dev-tools/logger.d.ts.map +1 -1
- package/dist/dev-tools/terminal.d.ts.map +1 -1
- package/dist/fonts/transform.d.ts +4 -0
- package/dist/fonts/transform.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1269 -359
- package/dist/index.js.map +1 -1
- package/dist/plugin-context.d.ts +36 -0
- package/dist/plugin-context.d.ts.map +1 -1
- package/dist/plugins/build-report.d.ts +21 -0
- package/dist/plugins/build-report.d.ts.map +1 -1
- package/dist/plugins/cache.d.ts +5 -14
- package/dist/plugins/cache.d.ts.map +1 -1
- package/dist/plugins/callsite-ast.d.ts +95 -0
- package/dist/plugins/callsite-ast.d.ts.map +1 -0
- package/dist/plugins/callsite-jsx.d.ts +35 -0
- package/dist/plugins/callsite-jsx.d.ts.map +1 -0
- package/dist/plugins/entries.d.ts.map +1 -1
- package/dist/plugins/fonts.d.ts.map +1 -1
- package/dist/plugins/prebuilt-capture.d.ts +60 -0
- package/dist/plugins/prebuilt-capture.d.ts.map +1 -0
- package/dist/plugins/prebuilt.d.ts +82 -0
- package/dist/plugins/prebuilt.d.ts.map +1 -0
- package/dist/plugins/prerender-sugar.d.ts +42 -0
- package/dist/plugins/prerender-sugar.d.ts.map +1 -0
- package/dist/plugins/server-bundle.d.ts.map +1 -1
- package/dist/plugins/shims.d.ts.map +1 -1
- package/dist/plugins/static-build.d.ts.map +1 -1
- package/dist/routing/convention-lint.d.ts.map +1 -1
- package/dist/routing/export-detect.d.ts +56 -0
- package/dist/routing/export-detect.d.ts.map +1 -0
- package/dist/routing/index.js +2 -2
- package/dist/routing/manifest-codegen.d.ts.map +1 -1
- package/dist/rsc-runtime/rsc.d.ts +1 -1
- package/dist/rsc-runtime/rsc.d.ts.map +1 -1
- package/dist/server/action-handler.d.ts.map +1 -1
- package/dist/server/actions.d.ts +0 -3
- package/dist/server/actions.d.ts.map +1 -1
- package/dist/server/als-registry.d.ts +7 -0
- package/dist/server/als-registry.d.ts.map +1 -1
- package/dist/server/cookie-parsing.d.ts +15 -1
- package/dist/server/cookie-parsing.d.ts.map +1 -1
- package/dist/server/deny-renderer.d.ts.map +1 -1
- package/dist/server/form-data.d.ts +12 -0
- package/dist/server/form-data.d.ts.map +1 -1
- package/dist/server/html-injectors.d.ts +1 -0
- package/dist/server/html-injectors.d.ts.map +1 -1
- package/dist/server/index.js +2 -2
- package/dist/server/internal.d.ts +6 -1
- package/dist/server/internal.d.ts.map +1 -1
- package/dist/server/internal.js +563 -74
- package/dist/server/internal.js.map +1 -1
- package/dist/server/prebuilt/cache-key.d.ts +58 -0
- package/dist/server/prebuilt/cache-key.d.ts.map +1 -0
- package/dist/server/prebuilt/capture-state.d.ts +57 -0
- package/dist/server/prebuilt/capture-state.d.ts.map +1 -0
- package/dist/server/prebuilt/manifest-tag-index.d.ts +27 -0
- package/dist/server/prebuilt/manifest-tag-index.d.ts.map +1 -0
- package/dist/server/prebuilt/overlay.d.ts +100 -0
- package/dist/server/prebuilt/overlay.d.ts.map +1 -0
- package/dist/server/prebuilt/payload-source.d.ts +75 -0
- package/dist/server/prebuilt/payload-source.d.ts.map +1 -0
- package/dist/server/prebuilt/synthetic-store.d.ts +64 -0
- package/dist/server/prebuilt/synthetic-store.d.ts.map +1 -0
- package/dist/server/prebuilt-builder.d.ts +110 -0
- package/dist/server/prebuilt-builder.d.ts.map +1 -0
- package/dist/server/prebuilt-runtime.d.ts +101 -0
- package/dist/server/prebuilt-runtime.d.ts.map +1 -0
- package/dist/server/route-element-builder.d.ts +10 -8
- package/dist/server/route-element-builder.d.ts.map +1 -1
- package/dist/server/rsc-entry/helpers.d.ts +0 -5
- package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
- package/dist/server/rsc-entry/index.d.ts +10 -0
- package/dist/server/rsc-entry/index.d.ts.map +1 -1
- package/dist/server/rsc-entry/render-route.d.ts.map +1 -1
- package/dist/server/rsc-entry/revalidate-renderer.d.ts.map +1 -1
- package/dist/server/rsc-entry/rsc-payload.d.ts +3 -4
- package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
- package/dist/server/sitemap-generator.d.ts +6 -6
- package/dist/server/sitemap-generator.d.ts.map +1 -1
- package/dist/server/tracing.d.ts +37 -3
- package/dist/server/tracing.d.ts.map +1 -1
- package/docs/api/32-api-cache.mdx +10 -4
- package/docs/learn/00-introduction.mdx +8 -13
- package/docs/learn/02-pages-and-layouts.mdx +9 -0
- package/docs/learn/06-forms-and-actions.mdx +0 -4
- package/docs/learn/07-typed-routes.mdx +127 -0
- package/docs/learn/09-caching.mdx +12 -1
- package/docs/learn/10-middleware.mdx +1 -0
- package/docs/learn/11-error-handling.mdx +9 -0
- package/docs/learn/13-configuration.mdx +28 -0
- package/docs/learn/14-deploying.mdx +52 -0
- package/docs/more/06-instrumentation.mdx +27 -1
- package/docs/more/07-security.mdx +8 -2
- package/docs/more/08-developer-experience.mdx +134 -0
- package/package.json +31 -2
- package/src/adapters/build-output-helper.ts +10 -0
- package/src/adapters/cloudflare.ts +178 -4
- package/src/cache/cache-api.ts +60 -0
- package/src/cache/handler-store.ts +8 -0
- package/src/cache/index.ts +4 -0
- package/src/cache/redis-handler.ts +25 -13
- package/src/cache/singleflight.ts +11 -11
- package/src/cache/store.ts +71 -0
- package/src/cache/stores/cloudflare-kv.ts +52 -0
- package/src/cache/stores/memory.ts +125 -0
- package/src/cache/stores/redis.ts +97 -0
- package/src/cache/stores/vercel.ts +59 -0
- package/src/cache/tag-aware-handler.ts +233 -0
- package/src/cache/timber-cache.ts +17 -7
- package/src/cdn/cloudflare-purge.ts +50 -0
- package/src/cdn/fastly-purge.ts +63 -0
- package/src/cdn/headers.ts +127 -0
- package/src/cdn/index.ts +16 -0
- package/src/cdn/purge-store.ts +34 -0
- package/src/client/browser-entry/action-dispatch.ts +1 -10
- package/src/client/browser-entry/post-hydration.ts +0 -1
- package/src/client/browser-entry/router-init.ts +0 -5
- package/src/client/history.ts +0 -4
- package/src/client/navigation-api.ts +5 -11
- package/src/client/router.ts +9 -43
- package/src/client/rsc-fetch.ts +5 -27
- package/src/client/segment-cache.ts +1 -4
- package/src/client/use-cookie.ts +18 -12
- package/src/config-types.ts +3 -1
- package/src/config-validation.ts +1 -0
- package/src/dev-tools/browser-logs.ts +37 -273
- package/src/dev-tools/index.ts +2 -2
- package/src/dev-tools/instrumentation.ts +1 -1
- package/src/dev-tools/logger.ts +33 -51
- package/src/dev-tools/terminal.ts +21 -22
- package/src/fonts/transform.ts +4 -43
- package/src/index.ts +16 -3
- package/src/plugin-context.ts +36 -0
- package/src/plugins/build-report.ts +133 -1
- package/src/plugins/cache.ts +32 -131
- package/src/plugins/callsite-ast.ts +167 -0
- package/src/plugins/callsite-jsx.ts +243 -0
- package/src/plugins/entries.ts +23 -4
- package/src/plugins/fonts.ts +16 -2
- package/src/plugins/prebuilt-capture.ts +208 -0
- package/src/plugins/prebuilt.ts +673 -0
- package/src/plugins/prerender-sugar.ts +329 -0
- package/src/plugins/server-bundle.ts +12 -0
- package/src/plugins/shims.ts +9 -0
- package/src/plugins/static-build.ts +0 -1
- package/src/routing/codegen.ts +1 -21
- package/src/routing/convention-lint.ts +85 -49
- package/src/routing/export-detect.ts +304 -0
- package/src/routing/manifest-codegen.ts +2 -11
- package/src/rsc-runtime/rsc.ts +7 -0
- package/src/server/action-handler.ts +11 -27
- package/src/server/actions.ts +3 -4
- package/src/server/als-registry.ts +7 -0
- package/src/server/cookie-parsing.ts +75 -59
- package/src/server/deny-renderer.ts +7 -15
- package/src/server/form-data.ts +23 -7
- package/src/server/html-injectors.ts +11 -3
- package/src/server/internal.ts +17 -1
- package/src/server/prebuilt/cache-key.ts +224 -0
- package/src/server/prebuilt/capture-state.ts +89 -0
- package/src/server/prebuilt/manifest-tag-index.ts +46 -0
- package/src/server/prebuilt/overlay.ts +256 -0
- package/src/server/prebuilt/payload-source.ts +192 -0
- package/src/server/prebuilt/synthetic-store.ts +108 -0
- package/src/server/prebuilt-builder.ts +640 -0
- package/src/server/prebuilt-runtime.ts +706 -0
- package/src/server/route-element-builder.ts +40 -12
- package/src/server/rsc-entry/helpers.ts +0 -17
- package/src/server/rsc-entry/index.ts +83 -0
- package/src/server/rsc-entry/render-route.ts +6 -33
- package/src/server/rsc-entry/revalidate-renderer.ts +16 -11
- package/src/server/rsc-entry/rsc-payload.ts +5 -11
- package/src/server/sitemap-generator.ts +16 -10
- package/src/server/tracing.ts +88 -3
- package/dist/_chunks/actions-CqE7SW7E.js.map +0 -1
- package/dist/_chunks/cache-api-Cbyh3RW4.js +0 -401
- package/dist/_chunks/cache-api-Cbyh3RW4.js.map +0 -1
- package/dist/_chunks/logger-C5v1IMuh.js.map +0 -1
- package/dist/_chunks/plugin-context-DUl3dnCF.js.map +0 -1
- package/dist/_chunks/validation-CnKhGJsp.js +0 -113
- package/dist/_chunks/validation-CnKhGJsp.js.map +0 -1
- package/dist/_chunks/walkers-D5jCGPum.js.map +0 -1
- package/dist/cache/index.js.map +0 -1
- package/dist/client/head.d.ts +0 -16
- package/dist/client/head.d.ts.map +0 -1
- package/dist/client/navigation-api-types.d.ts +0 -90
- package/dist/client/navigation-api-types.d.ts.map +0 -1
- package/dist/server/client-module-map.d.ts +0 -47
- package/dist/server/client-module-map.d.ts.map +0 -1
- package/dist/shared/head-header.d.ts +0 -54
- package/dist/shared/head-header.d.ts.map +0 -1
- package/src/client/head.ts +0 -61
- package/src/client/navigation-api-types.ts +0 -112
- package/src/server/client-module-map.ts +0 -58
- package/src/shared/head-header.ts +0 -69
|
@@ -1,23 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Browser log forwarding — maps timber's devBrowserLogs config to Vite 8's
|
|
3
|
+
* server.forwardConsole option.
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Vite 8 ships built-in browser→terminal console forwarding via
|
|
6
|
+
* server.forwardConsole, which patches console methods in /@vite/client
|
|
7
|
+
* and prints forwarded output server-side. This replaces timber's custom
|
|
8
|
+
* client-side interception script and HMR WebSocket transport.
|
|
7
9
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
+
* Client error forwarding (overlay echo, digest filtering, componentStack)
|
|
11
|
+
* remains in browser-dev.ts — Vite's forwardConsole only logs to terminal
|
|
12
|
+
* and has no client-side filter hook. See TIM-1152.
|
|
10
13
|
*
|
|
11
|
-
*
|
|
12
|
-
* No runtime overhead in production.
|
|
13
|
-
*
|
|
14
|
-
* See TIM-513 for design context.
|
|
15
|
-
* See design/18-build-system.md §"Dev Server" for sub-plugin architecture.
|
|
14
|
+
* See design/21-dev-server.md §"Browser Log Forwarding"
|
|
16
15
|
*/
|
|
17
16
|
|
|
18
|
-
import type { Plugin, ViteDevServer } from 'vite';
|
|
19
|
-
import type { PluginContext } from '../plugin-context.js';
|
|
20
|
-
|
|
21
17
|
// ─── Types ───────────────────────────────────────────────────────────────
|
|
22
18
|
|
|
23
19
|
/** Log levels forwarded from the browser. */
|
|
@@ -26,266 +22,34 @@ export type BrowserLogLevel = 'error' | 'warn' | 'info';
|
|
|
26
22
|
/** Configuration value for devBrowserLogs in timber.config.ts. */
|
|
27
23
|
export type DevBrowserLogsConfig = BrowserLogLevel | 'none' | undefined;
|
|
28
24
|
|
|
29
|
-
|
|
30
|
-
* Payload sent from browser to server via Vite's HMR WebSocket.
|
|
31
|
-
* Kept small — truncated before sending.
|
|
32
|
-
*/
|
|
33
|
-
export interface BrowserLogPayload {
|
|
34
|
-
level: BrowserLogLevel;
|
|
35
|
-
/** Serialized message string. */
|
|
36
|
-
message: string;
|
|
37
|
-
/** Error stack trace, if the first argument was an Error. */
|
|
38
|
-
stack: string | null;
|
|
39
|
-
/** Source URL and line number, if available. */
|
|
40
|
-
source: string | null;
|
|
41
|
-
/** Timestamp in ms (Date.now()) from the browser. */
|
|
42
|
-
timestamp: number;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// ─── Constants ───────────────────────────────────────────────────────────
|
|
46
|
-
|
|
47
|
-
/** Max message size in bytes before truncation. */
|
|
48
|
-
const MAX_MESSAGE_BYTES = 2048;
|
|
49
|
-
|
|
50
|
-
/** HMR event name for browser→server log forwarding. */
|
|
51
|
-
const HMR_EVENT = 'timber:browser-log';
|
|
52
|
-
|
|
53
|
-
/** ANSI color codes for terminal output. */
|
|
54
|
-
const COLORS = {
|
|
55
|
-
red: '\x1b[31m',
|
|
56
|
-
yellow: '\x1b[33m',
|
|
57
|
-
blue: '\x1b[34m',
|
|
58
|
-
dim: '\x1b[2m',
|
|
59
|
-
reset: '\x1b[0m',
|
|
60
|
-
} as const;
|
|
61
|
-
|
|
62
|
-
/** Level severity ordering for threshold comparison. */
|
|
63
|
-
const LEVEL_SEVERITY: Record<BrowserLogLevel | 'none', number> = {
|
|
64
|
-
none: 0,
|
|
65
|
-
info: 1,
|
|
66
|
-
warn: 2,
|
|
67
|
-
error: 3,
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
// ─── Formatting ──────────────────────────────────────────────────────────
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Format a browser log payload for server terminal output.
|
|
74
|
-
*
|
|
75
|
-
* Produces color-coded output like:
|
|
76
|
-
* [browser:error] Uncaught TypeError: x is not a function
|
|
77
|
-
* at App (app.tsx:10:5)
|
|
78
|
-
* [browser:warn] Deprecation warning (app/page.tsx:42:12)
|
|
79
|
-
*/
|
|
80
|
-
export function formatBrowserLog(payload: BrowserLogPayload): string {
|
|
81
|
-
const { level, message, stack, source } = payload;
|
|
82
|
-
|
|
83
|
-
// Color-coded prefix
|
|
84
|
-
const color = level === 'error' ? COLORS.red : level === 'warn' ? COLORS.yellow : COLORS.blue;
|
|
85
|
-
const prefix = `${color}[browser:${level}]${COLORS.reset}`;
|
|
86
|
-
|
|
87
|
-
// Source suffix
|
|
88
|
-
const sourceSuffix = source ? ` ${COLORS.dim}(${source})${COLORS.reset}` : '';
|
|
89
|
-
|
|
90
|
-
let output = `${prefix} ${message}${sourceSuffix}`;
|
|
91
|
-
|
|
92
|
-
// Append stack trace indented
|
|
93
|
-
if (stack) {
|
|
94
|
-
const indented = stack
|
|
95
|
-
.split('\n')
|
|
96
|
-
.map((line) => ` ${COLORS.dim}${line}${COLORS.reset}`)
|
|
97
|
-
.join('\n');
|
|
98
|
-
output += `\n${indented}`;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
return output;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// ─── Level Filtering ─────────────────────────────────────────────────────
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Check if a log at `level` should be forwarded given the configured threshold.
|
|
108
|
-
*
|
|
109
|
-
* The threshold acts as a minimum severity:
|
|
110
|
-
* - 'error' → only errors
|
|
111
|
-
* - 'warn' → errors + warnings
|
|
112
|
-
* - 'info' → errors + warnings + info
|
|
113
|
-
* - 'none' → nothing
|
|
114
|
-
*/
|
|
115
|
-
export function shouldForwardLevel(
|
|
116
|
-
level: BrowserLogLevel,
|
|
117
|
-
threshold: BrowserLogLevel | 'none'
|
|
118
|
-
): boolean {
|
|
119
|
-
if (threshold === 'none') return false;
|
|
120
|
-
return LEVEL_SEVERITY[level] >= LEVEL_SEVERITY[threshold];
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// ─── Truncation ──────────────────────────────────────────────────────────
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Truncate a message to `maxBytes` to avoid flooding the terminal.
|
|
127
|
-
* Appends a suffix indicating how many bytes were dropped.
|
|
128
|
-
*/
|
|
129
|
-
export function truncateMessage(message: string, maxBytes: number): string {
|
|
130
|
-
if (message.length <= maxBytes) return message;
|
|
131
|
-
|
|
132
|
-
const truncated = message.slice(0, maxBytes);
|
|
133
|
-
const droppedBytes = message.length - maxBytes;
|
|
134
|
-
return `${truncated}… [truncated ${droppedBytes} bytes]`;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// ─── Client Injection Script ─────────────────────────────────────────────
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Generate the inline script injected into the browser in dev mode.
|
|
141
|
-
*
|
|
142
|
-
* This script:
|
|
143
|
-
* 1. Saves references to the original console methods
|
|
144
|
-
* 2. Wraps `console.error`, `console.warn`, `console.info`
|
|
145
|
-
* 3. Calls the original first (browser devtools still work)
|
|
146
|
-
* 4. Serializes and forwards via `import.meta.hot.send()`
|
|
147
|
-
* 5. Truncates messages to MAX_MESSAGE_BYTES
|
|
148
|
-
*
|
|
149
|
-
* The script is minimal and self-contained — no imports, no dependencies.
|
|
150
|
-
*/
|
|
151
|
-
export function generateClientScript(threshold: BrowserLogLevel | 'none'): string {
|
|
152
|
-
if (threshold === 'none') return '';
|
|
153
|
-
|
|
154
|
-
// Only intercept levels that meet the threshold
|
|
155
|
-
const levels: BrowserLogLevel[] = ['error', 'warn', 'info'].filter((l) =>
|
|
156
|
-
shouldForwardLevel(l as BrowserLogLevel, threshold)
|
|
157
|
-
) as BrowserLogLevel[];
|
|
158
|
-
|
|
159
|
-
if (levels.length === 0) return '';
|
|
160
|
-
|
|
161
|
-
return `
|
|
162
|
-
(function() {
|
|
163
|
-
if (!import.meta.hot) return;
|
|
164
|
-
var MAX_BYTES = ${MAX_MESSAGE_BYTES};
|
|
165
|
-
var levels = ${JSON.stringify(levels)};
|
|
166
|
-
var originals = {};
|
|
167
|
-
|
|
168
|
-
function serialize(arg) {
|
|
169
|
-
if (arg === null) return 'null';
|
|
170
|
-
if (arg === undefined) return 'undefined';
|
|
171
|
-
if (arg instanceof Error) return arg.stack || arg.message || String(arg);
|
|
172
|
-
if (typeof arg === 'object') {
|
|
173
|
-
try { return JSON.stringify(arg); } catch(e) { return String(arg); }
|
|
174
|
-
}
|
|
175
|
-
return String(arg);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
function truncate(s) {
|
|
179
|
-
if (s.length <= MAX_BYTES) return s;
|
|
180
|
-
return s.slice(0, MAX_BYTES) + '... [truncated ' + (s.length - MAX_BYTES) + ' bytes]';
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
levels.forEach(function(level) {
|
|
184
|
-
originals[level] = console[level];
|
|
185
|
-
console[level] = function() {
|
|
186
|
-
originals[level].apply(console, arguments);
|
|
187
|
-
try {
|
|
188
|
-
var args = Array.prototype.slice.call(arguments);
|
|
189
|
-
var firstArg = args[0];
|
|
190
|
-
var stack = null;
|
|
191
|
-
var source = null;
|
|
192
|
-
|
|
193
|
-
if (firstArg instanceof Error) {
|
|
194
|
-
stack = firstArg.stack || null;
|
|
195
|
-
source = null;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
var message = args.map(serialize).join(' ');
|
|
199
|
-
message = truncate(message);
|
|
200
|
-
|
|
201
|
-
import.meta.hot.send('${HMR_EVENT}', {
|
|
202
|
-
level: level,
|
|
203
|
-
message: message,
|
|
204
|
-
stack: stack,
|
|
205
|
-
source: source,
|
|
206
|
-
timestamp: Date.now()
|
|
207
|
-
});
|
|
208
|
-
} catch(e) {
|
|
209
|
-
// Never let log forwarding break the page
|
|
210
|
-
}
|
|
211
|
-
};
|
|
212
|
-
});
|
|
213
|
-
})();
|
|
214
|
-
`.trim();
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// ─── Plugin ──────────────────────────────────────────────────────────────
|
|
25
|
+
// ─── Vite Config Mapping ─────────────────────────────────────────────────
|
|
218
26
|
|
|
219
27
|
/**
|
|
220
|
-
*
|
|
221
|
-
*
|
|
222
|
-
*
|
|
223
|
-
*
|
|
224
|
-
*
|
|
225
|
-
*
|
|
28
|
+
* Map timber's devBrowserLogs threshold to Vite 8's server.forwardConsole.
|
|
29
|
+
*
|
|
30
|
+
* Vite defaults forwardConsole OFF for non-agent dev; timber enables it
|
|
31
|
+
* based on the configured threshold (default: 'warn'). The threshold acts
|
|
32
|
+
* as a minimum severity — 'warn' forwards errors + warnings, 'info'
|
|
33
|
+
* forwards everything, 'error' forwards only errors, 'none' disables.
|
|
34
|
+
*
|
|
35
|
+
* Vite's unhandledErrors is explicitly disabled — timber's own
|
|
36
|
+
* setupClientErrorForwarding (browser-dev.ts) already catches window
|
|
37
|
+
* error/unhandledrejection, filters framework control-flow signals
|
|
38
|
+
* (redirect/deny digests) and page-unload aborts (TIM-838), and
|
|
39
|
+
* echoes errors to the Vite overlay. Enabling Vite's unhandledErrors
|
|
40
|
+
* would duplicate terminal output for normal errors and reintroduce
|
|
41
|
+
* unfiltered noise for framework signals.
|
|
226
42
|
*/
|
|
227
|
-
export function
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
// Register the client injection script on globalThis so the RSC entry
|
|
241
|
-
// can include it in headHtml for all Timber route responses.
|
|
242
|
-
// transformIndexHtml only runs for Vite's index.html fallback, not
|
|
243
|
-
// for responses served by createTimberMiddleware (TIM-575).
|
|
244
|
-
const script = generateClientScript(threshold);
|
|
245
|
-
if (script) {
|
|
246
|
-
(globalThis as Record<string, unknown>).__timber_dev_browser_log_script =
|
|
247
|
-
`<script type="module">${script}</script>`;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// Listen for browser log messages via HMR WebSocket
|
|
251
|
-
server.hot.on(HMR_EVENT, (payload: BrowserLogPayload) => {
|
|
252
|
-
try {
|
|
253
|
-
// Validate level
|
|
254
|
-
if (!shouldForwardLevel(payload.level, threshold)) return;
|
|
255
|
-
|
|
256
|
-
// Truncate server-side too (defense in depth)
|
|
257
|
-
payload.message = truncateMessage(payload.message, MAX_MESSAGE_BYTES);
|
|
258
|
-
|
|
259
|
-
const formatted = formatBrowserLog(payload);
|
|
260
|
-
|
|
261
|
-
// Use the correct console method for the log level
|
|
262
|
-
if (payload.level === 'error') {
|
|
263
|
-
process.stderr.write(formatted + '\n');
|
|
264
|
-
} else {
|
|
265
|
-
process.stdout.write(formatted + '\n');
|
|
266
|
-
}
|
|
267
|
-
} catch (e) {
|
|
268
|
-
// Use process.stderr directly to avoid re-entering patched console
|
|
269
|
-
process.stderr.write(
|
|
270
|
-
`[timber] log forwarding failed: ${e instanceof Error ? e.message : e}\n`
|
|
271
|
-
);
|
|
272
|
-
}
|
|
273
|
-
});
|
|
274
|
-
},
|
|
275
|
-
|
|
276
|
-
transformIndexHtml() {
|
|
277
|
-
const threshold = ctx.config.devBrowserLogs ?? 'warn';
|
|
278
|
-
const script = generateClientScript(threshold);
|
|
279
|
-
if (!script) return [];
|
|
280
|
-
|
|
281
|
-
return [
|
|
282
|
-
{
|
|
283
|
-
tag: 'script',
|
|
284
|
-
attrs: { type: 'module' },
|
|
285
|
-
children: script,
|
|
286
|
-
injectTo: 'head' as const,
|
|
287
|
-
},
|
|
288
|
-
];
|
|
289
|
-
},
|
|
290
|
-
};
|
|
43
|
+
export function resolveForwardConsole(
|
|
44
|
+
threshold: DevBrowserLogsConfig
|
|
45
|
+
): boolean | { unhandledErrors: boolean; logLevels: string[] } {
|
|
46
|
+
const resolved = threshold ?? 'warn';
|
|
47
|
+
if (resolved === 'none') return false;
|
|
48
|
+
|
|
49
|
+
const logLevels: string[] = [];
|
|
50
|
+
if (resolved === 'info') logLevels.push('info', 'warn', 'error');
|
|
51
|
+
else if (resolved === 'warn') logLevels.push('warn', 'error');
|
|
52
|
+
else logLevels.push('error');
|
|
53
|
+
|
|
54
|
+
return { unhandledErrors: false, logLevels };
|
|
291
55
|
}
|
package/src/dev-tools/index.ts
CHANGED
|
@@ -63,9 +63,9 @@ export {
|
|
|
63
63
|
type DevLoggerConfig,
|
|
64
64
|
} from './logger.js';
|
|
65
65
|
|
|
66
|
-
// Browser log forwarding
|
|
66
|
+
// Browser log forwarding (Vite 8 config mapping)
|
|
67
67
|
export {
|
|
68
|
-
|
|
68
|
+
resolveForwardConsole,
|
|
69
69
|
type BrowserLogLevel,
|
|
70
70
|
type DevBrowserLogsConfig,
|
|
71
71
|
} from './browser-logs.js';
|
|
@@ -35,7 +35,7 @@ export type DevFetchCleanup = () => void;
|
|
|
35
35
|
* instrumentDevFetch() again. Without this guard each call would wrap the
|
|
36
36
|
* previous wrapper — N nested wrappers emitting N duplicate timber.fetch
|
|
37
37
|
* spans per request. Symbol.for() survives module re-evaluation (same
|
|
38
|
-
* pattern as
|
|
38
|
+
* pattern as globalThis Symbol guards). See TIM-1067, B49.
|
|
39
39
|
*/
|
|
40
40
|
const FETCH_PATCH_KEY = Symbol.for('timber.dev.fetch-instrumentation-cleanup');
|
|
41
41
|
|
package/src/dev-tools/logger.ts
CHANGED
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
* Design doc: 21-dev-server.md §"Dev Logging", 17-logging.md §"Dev Logging"
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
+
import { STATUS_CODES } from 'node:http';
|
|
18
|
+
import type { InspectColor } from 'node:util';
|
|
19
|
+
import { styleText } from 'node:util';
|
|
17
20
|
import type { ReadableSpan } from '@opentelemetry/sdk-trace-base';
|
|
18
21
|
|
|
19
22
|
// ─── Configuration ──────────────────────────────────────────────────────────
|
|
@@ -27,16 +30,11 @@ export interface DevLoggerConfig {
|
|
|
27
30
|
slowPhaseMs?: number;
|
|
28
31
|
}
|
|
29
32
|
|
|
30
|
-
// ───
|
|
33
|
+
// ─── Styling ────────────────────────────────────────────────────────────────
|
|
31
34
|
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
const
|
|
35
|
-
const GREEN = '\x1b[32m';
|
|
36
|
-
const YELLOW = '\x1b[33m';
|
|
37
|
-
const RED = '\x1b[31m';
|
|
38
|
-
const CYAN = '\x1b[36m';
|
|
39
|
-
const MAGENTA = '\x1b[35m';
|
|
35
|
+
type Format = InspectColor | readonly InspectColor[];
|
|
36
|
+
const noValidate = { validateStream: false } as const;
|
|
37
|
+
const style = (fmt: Format, text: string) => styleText(fmt, text, noValidate);
|
|
40
38
|
|
|
41
39
|
/**
|
|
42
40
|
* Color an HTTP method for dev log output.
|
|
@@ -45,21 +43,21 @@ const MAGENTA = '\x1b[35m';
|
|
|
45
43
|
function colorMethod(method: string): string {
|
|
46
44
|
switch (method) {
|
|
47
45
|
case 'GET':
|
|
48
|
-
return
|
|
46
|
+
return style('dim', method);
|
|
49
47
|
case 'POST':
|
|
50
|
-
return
|
|
48
|
+
return style(['green', 'bold'], method);
|
|
51
49
|
case 'PUT':
|
|
52
|
-
return
|
|
50
|
+
return style(['yellow', 'bold'], method);
|
|
53
51
|
case 'DELETE':
|
|
54
|
-
return
|
|
52
|
+
return style(['red', 'bold'], method);
|
|
55
53
|
case 'PATCH':
|
|
56
|
-
return
|
|
54
|
+
return style(['cyan', 'bold'], method);
|
|
57
55
|
case 'HEAD':
|
|
58
|
-
return
|
|
56
|
+
return style('dim', method);
|
|
59
57
|
case 'OPTIONS':
|
|
60
|
-
return
|
|
58
|
+
return style(['magenta', 'bold'], method);
|
|
61
59
|
default:
|
|
62
|
-
return
|
|
60
|
+
return style('bold', method);
|
|
63
61
|
}
|
|
64
62
|
}
|
|
65
63
|
|
|
@@ -251,14 +249,14 @@ export function formatSpanTree(spans: ReadableSpan[], config?: DevLoggerConfig):
|
|
|
251
249
|
const traceId = root.spanContext().traceId;
|
|
252
250
|
const actionName = root.attributes['timber.action_name'] as string | undefined;
|
|
253
251
|
|
|
254
|
-
const dimTrace =
|
|
252
|
+
const dimTrace = style('dim', `trace_id: ${traceId}`);
|
|
255
253
|
if (actionName) {
|
|
256
254
|
const actionFile = root.attributes['timber.action_file'] as string | undefined;
|
|
257
255
|
lines.push(
|
|
258
|
-
`${
|
|
256
|
+
`${style('bold', `ACTION ${actionName}${actionFile ? ` (${actionFile})` : ''}`)} ${dimTrace}`
|
|
259
257
|
);
|
|
260
258
|
} else {
|
|
261
|
-
lines.push(`${colorMethod(method)} ${
|
|
259
|
+
lines.push(`${colorMethod(method)} ${style('bold', path)} ${dimTrace}`);
|
|
262
260
|
}
|
|
263
261
|
|
|
264
262
|
// Render child span nodes
|
|
@@ -271,17 +269,17 @@ export function formatSpanTree(spans: ReadableSpan[], config?: DevLoggerConfig):
|
|
|
271
269
|
const statusCode = root.attributes['http.response.status_code'] as number | undefined;
|
|
272
270
|
const status = statusCode ?? (root.status.code === 2 ? 500 : 200);
|
|
273
271
|
const totalMs = Math.round(hrTimeToMs(root.duration));
|
|
274
|
-
const
|
|
272
|
+
const statusFmt = status < 400 ? 'green' : status < 500 ? 'yellow' : 'red';
|
|
275
273
|
const statusText = `${status} ${httpStatusText(status)}`;
|
|
276
274
|
|
|
277
275
|
// Surface deny() signal info for 500s caused by deny-inside-suspense
|
|
278
276
|
const denyInfo = root.attributes['timber.deny_status'] as number | undefined;
|
|
279
277
|
const denyNote = denyInfo
|
|
280
|
-
?
|
|
278
|
+
? style('dim', ` (caused by deny(${denyInfo}) inside Suspense — status already committed)`)
|
|
281
279
|
: '';
|
|
282
280
|
|
|
283
281
|
lines.push(
|
|
284
|
-
`${
|
|
282
|
+
`${style(statusFmt, `└─ ✓ ${statusText}`)}${style('dim', ` total ${totalMs}ms`)}${denyNote}`
|
|
285
283
|
);
|
|
286
284
|
|
|
287
285
|
return lines.join('\n') + '\n';
|
|
@@ -307,18 +305,18 @@ function formatFetchLine(
|
|
|
307
305
|
const fetchError = span.attributes['timber.fetch_error'] as string | undefined;
|
|
308
306
|
const isError = span.status.code === 2; // SpanStatusCode.ERROR
|
|
309
307
|
|
|
310
|
-
let line = `${prefix}${connector} ${
|
|
311
|
-
line += ` ${
|
|
308
|
+
let line = `${prefix}${connector} ${style('dim', `fetch ${method}`)} ${url}`;
|
|
309
|
+
line += ` ${style('dim', `${startMs}ms → ${endMs}ms (${durationMs}ms)`)}`;
|
|
312
310
|
|
|
313
311
|
if (cacheStatus) {
|
|
314
|
-
line += ` ${
|
|
312
|
+
line += ` ${style('dim', `[cdn: ${cacheStatus}]`)}`;
|
|
315
313
|
}
|
|
316
314
|
|
|
317
315
|
if (isError) {
|
|
318
316
|
const errMsg = fetchError ? `: ${fetchError}` : '';
|
|
319
|
-
line += ` ${
|
|
317
|
+
line += ` ${style('red', `ERROR${errMsg}`)}`;
|
|
320
318
|
} else if (statusCode && statusCode >= 400) {
|
|
321
|
-
line += ` ${
|
|
319
|
+
line += ` ${style('yellow', String(statusCode))}`;
|
|
322
320
|
}
|
|
323
321
|
|
|
324
322
|
return line;
|
|
@@ -363,10 +361,9 @@ function formatSpanNode(
|
|
|
363
361
|
}
|
|
364
362
|
}
|
|
365
363
|
|
|
366
|
-
const
|
|
367
|
-
const
|
|
368
|
-
const
|
|
369
|
-
const line = `${prefix}${connector} ${envTag} ${slowHighlight}${label}${slowReset} ${DIM}${timing}${RESET}`;
|
|
364
|
+
const styledLabel = isSlow ? style('yellow', label) : label;
|
|
365
|
+
const envTag = style('cyan', `[${env}]`);
|
|
366
|
+
const line = `${prefix}${connector} ${envTag} ${styledLabel} ${style('dim', timing)}`;
|
|
370
367
|
lines.push(line);
|
|
371
368
|
|
|
372
369
|
// Span events (cache hits/misses) as child annotations
|
|
@@ -378,7 +375,7 @@ function formatSpanNode(
|
|
|
378
375
|
const durStr = durMs !== undefined ? ` ${durMs < 1 ? '<1' : Math.round(durMs)}ms` : '';
|
|
379
376
|
const stale = event.attributes?.['stale'] ? ' (stale)' : '';
|
|
380
377
|
lines.push(
|
|
381
|
-
`${childPrefix}${
|
|
378
|
+
`${childPrefix}${style('dim', `└── ${key} timber.cache ${hitMiss}${durStr}${stale}`)}`
|
|
382
379
|
);
|
|
383
380
|
}
|
|
384
381
|
}
|
|
@@ -405,8 +402,8 @@ export function formatSpanSummary(spans: ReadableSpan[], _config?: DevLoggerConf
|
|
|
405
402
|
const traceId = root.spanContext().traceId;
|
|
406
403
|
const traceIdShort = traceId.slice(0, 8);
|
|
407
404
|
|
|
408
|
-
const
|
|
409
|
-
return `${colorMethod(method)} ${path} → ${
|
|
405
|
+
const statusFmt = status < 400 ? 'green' : status < 500 ? 'yellow' : 'red';
|
|
406
|
+
return `${colorMethod(method)} ${path} → ${style(statusFmt, `${status} ${httpStatusText(status)}`)} ${totalMs}ms ${style('dim', `trace_id: ${traceIdShort}...`)}\n`;
|
|
410
407
|
}
|
|
411
408
|
|
|
412
409
|
/**
|
|
@@ -449,20 +446,5 @@ export function formatJson(spans: ReadableSpan[]): string {
|
|
|
449
446
|
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
450
447
|
|
|
451
448
|
function httpStatusText(status: number): string {
|
|
452
|
-
|
|
453
|
-
200: 'OK',
|
|
454
|
-
201: 'Created',
|
|
455
|
-
204: 'No Content',
|
|
456
|
-
301: 'Moved Permanently',
|
|
457
|
-
302: 'Found',
|
|
458
|
-
304: 'Not Modified',
|
|
459
|
-
400: 'Bad Request',
|
|
460
|
-
401: 'Unauthorized',
|
|
461
|
-
403: 'Forbidden',
|
|
462
|
-
404: 'Not Found',
|
|
463
|
-
405: 'Method Not Allowed',
|
|
464
|
-
413: 'Payload Too Large',
|
|
465
|
-
500: 'Internal Server Error',
|
|
466
|
-
};
|
|
467
|
-
return texts[status] ?? String(status);
|
|
449
|
+
return STATUS_CODES[status] ?? String(status);
|
|
468
450
|
}
|
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import { pathToFileURL } from 'node:url';
|
|
18
|
+
import type { InspectColor } from 'node:util';
|
|
19
|
+
import { styleText } from 'node:util';
|
|
18
20
|
import {
|
|
19
21
|
extractComponentStack,
|
|
20
22
|
parseFirstAppFrame,
|
|
@@ -23,14 +25,11 @@ import {
|
|
|
23
25
|
} from './overlay.js';
|
|
24
26
|
import { classifyStack } from './stack-classifier.js';
|
|
25
27
|
|
|
26
|
-
// ───
|
|
28
|
+
// ─── Styling ────────────────────────────────────────────────────────────────
|
|
27
29
|
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
const
|
|
31
|
-
const RESET = '\x1b[0m';
|
|
32
|
-
const BOLD = '\x1b[1m';
|
|
33
|
-
const UNDERLINE = '\x1b[4m';
|
|
30
|
+
type Format = InspectColor | readonly InspectColor[];
|
|
31
|
+
const noValidate = { validateStream: false } as const;
|
|
32
|
+
const style = (fmt: Format, text: string) => styleText(fmt, text, noValidate);
|
|
34
33
|
|
|
35
34
|
// ─── OSC 8 Hyperlinks ──────────────────────────────────────────────────────
|
|
36
35
|
|
|
@@ -59,7 +58,7 @@ function fileLink(filePath: string, line?: number, col?: number): string {
|
|
|
59
58
|
// drive letters (TIM-792). Append :line:col after the URL.
|
|
60
59
|
const base = pathToFileURL(filePath).href;
|
|
61
60
|
const url = line ? `${base}:${line}${col ? `:${col}` : ''}` : base;
|
|
62
|
-
return
|
|
61
|
+
return style(['cyan', 'underline'], hyperlink(display, url));
|
|
63
62
|
}
|
|
64
63
|
|
|
65
64
|
// ─── Box Drawing ────────────────────────────────────────────────────────────
|
|
@@ -79,15 +78,15 @@ const BOX = {
|
|
|
79
78
|
* @param lines - Content lines (no ANSI length calculation — keeps it simple)
|
|
80
79
|
* @param width - Box width (characters). Lines longer than this are not truncated.
|
|
81
80
|
*/
|
|
82
|
-
function box(lines: string[],
|
|
81
|
+
function box(lines: string[], borderFormat: Format, width = 80): string {
|
|
83
82
|
const bar = BOX.horizontal.repeat(width - 2);
|
|
84
83
|
const output: string[] = [];
|
|
85
84
|
|
|
86
|
-
output.push(`${
|
|
85
|
+
output.push(style(borderFormat, `${BOX.topLeft}${bar}${BOX.topRight}`));
|
|
87
86
|
for (const line of lines) {
|
|
88
|
-
output.push(`${
|
|
87
|
+
output.push(`${style(borderFormat, BOX.vertical)} ${line}`);
|
|
89
88
|
}
|
|
90
|
-
output.push(`${
|
|
89
|
+
output.push(style(borderFormat, `${BOX.bottomLeft}${bar}${BOX.bottomRight}`));
|
|
91
90
|
|
|
92
91
|
return output.join('\n');
|
|
93
92
|
}
|
|
@@ -116,12 +115,12 @@ export function formatTerminalError(error: Error, phase: ErrorPhase, projectRoot
|
|
|
116
115
|
|
|
117
116
|
// ── Box: Phase + Message ──────────────────────────────────────────
|
|
118
117
|
const boxLines: string[] = [];
|
|
119
|
-
boxLines.push(`${
|
|
118
|
+
boxLines.push(style(['red', 'bold'], `${PHASE_LABELS[phase]} Error`));
|
|
120
119
|
boxLines.push('');
|
|
121
120
|
|
|
122
121
|
// Error message — may be multi-line
|
|
123
122
|
for (const msgLine of error.message.split('\n')) {
|
|
124
|
-
boxLines.push(
|
|
123
|
+
boxLines.push(style('red', msgLine));
|
|
125
124
|
}
|
|
126
125
|
|
|
127
126
|
// Primary file location (clickable)
|
|
@@ -131,31 +130,31 @@ export function formatTerminalError(error: Error, phase: ErrorPhase, projectRoot
|
|
|
131
130
|
? loc.file.slice(projectRoot.length + 1)
|
|
132
131
|
: loc.file;
|
|
133
132
|
boxLines.push(
|
|
134
|
-
`${
|
|
133
|
+
`${style('bold', '→')} ${fileLink(loc.file, loc.line, loc.column)} ${style('dim', `(${relPath})`)}`
|
|
135
134
|
);
|
|
136
135
|
}
|
|
137
136
|
|
|
138
|
-
sections.push(box(boxLines,
|
|
137
|
+
sections.push(box(boxLines, 'red'));
|
|
139
138
|
|
|
140
139
|
// ── Component Stack ───────────────────────────────────────────────
|
|
141
140
|
if (componentStack) {
|
|
142
141
|
sections.push('');
|
|
143
|
-
sections.push(` ${
|
|
142
|
+
sections.push(` ${style('bold', 'Component Stack:')}`);
|
|
144
143
|
for (const csLine of componentStack.trim().split('\n')) {
|
|
145
|
-
sections.push(` ${
|
|
144
|
+
sections.push(` ${style('dim', csLine.trim())}`);
|
|
146
145
|
}
|
|
147
146
|
}
|
|
148
147
|
|
|
149
148
|
// ── App Frames ────────────────────────────────────────────────────
|
|
150
149
|
if (appFrames.length > 0) {
|
|
151
150
|
sections.push('');
|
|
152
|
-
sections.push(` ${
|
|
151
|
+
sections.push(` ${style('bold', 'Application Frames:')}`);
|
|
153
152
|
for (let i = 0; i < appFrames.length; i++) {
|
|
154
153
|
const f = appFrames[i]!;
|
|
155
154
|
if (f.file && f.line) {
|
|
156
|
-
const prefix = i === 0 ?
|
|
155
|
+
const prefix = i === 0 ? style('bold', '▸') : ' ';
|
|
157
156
|
sections.push(
|
|
158
|
-
` ${prefix} ${fileLink(f.file, f.line, f.col)} ${
|
|
157
|
+
` ${prefix} ${fileLink(f.file, f.line, f.col)} ${style('dim', extractFnName(f.raw))}`
|
|
159
158
|
);
|
|
160
159
|
} else {
|
|
161
160
|
sections.push(` ${f.raw}`);
|
|
@@ -166,7 +165,7 @@ export function formatTerminalError(error: Error, phase: ErrorPhase, projectRoot
|
|
|
166
165
|
// ── Internal Frames (collapsed) ───────────────────────────────────
|
|
167
166
|
if (internalCount > 0) {
|
|
168
167
|
sections.push(
|
|
169
|
-
` ${
|
|
168
|
+
` ${style('dim', `… ${internalCount} internal frame${internalCount !== 1 ? 's' : ''} hidden`)}`
|
|
170
169
|
);
|
|
171
170
|
}
|
|
172
171
|
|