@timber-js/app 0.1.23 → 0.1.25
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/{ssr-data-B2yikEEB.js → ssr-data-DLnbYpj1.js} +2 -4
- package/dist/_chunks/{ssr-data-B2yikEEB.js.map → ssr-data-DLnbYpj1.js.map} +1 -1
- package/dist/_chunks/{use-cookie-D5aS4slY.js → use-cookie-dDbpCTx-.js} +2 -2
- package/dist/_chunks/{use-cookie-D5aS4slY.js.map → use-cookie-dDbpCTx-.js.map} +1 -1
- package/dist/adapters/nitro.d.ts.map +1 -1
- package/dist/adapters/nitro.js +4 -3
- package/dist/adapters/nitro.js.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/client/browser-dev.d.ts +29 -0
- package/dist/client/browser-dev.d.ts.map +1 -0
- package/dist/client/browser-links.d.ts +32 -0
- package/dist/client/browser-links.d.ts.map +1 -0
- package/dist/client/error-boundary.js +1 -1
- package/dist/client/index.d.ts +2 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +150 -122
- package/dist/client/index.js.map +1 -1
- package/dist/client/navigation-context.d.ts +52 -0
- package/dist/client/navigation-context.d.ts.map +1 -0
- package/dist/client/router.d.ts.map +1 -1
- package/dist/client/transition-root.d.ts +54 -0
- package/dist/client/transition-root.d.ts.map +1 -0
- package/dist/client/use-params.d.ts +35 -25
- package/dist/client/use-params.d.ts.map +1 -1
- package/dist/client/use-pathname.d.ts +11 -4
- package/dist/client/use-pathname.d.ts.map +1 -1
- package/dist/client/use-router.d.ts +14 -0
- package/dist/client/use-router.d.ts.map +1 -1
- package/dist/cookies/index.js +2 -2
- package/dist/server/index.js +264 -218
- package/dist/server/index.js.map +1 -1
- package/dist/server/metadata-platform.d.ts +34 -0
- package/dist/server/metadata-platform.d.ts.map +1 -0
- package/dist/server/metadata-render.d.ts.map +1 -1
- package/dist/server/metadata-social.d.ts +24 -0
- package/dist/server/metadata-social.d.ts.map +1 -0
- package/dist/server/pipeline-interception.d.ts +32 -0
- package/dist/server/pipeline-interception.d.ts.map +1 -0
- package/dist/server/pipeline-metadata.d.ts +31 -0
- package/dist/server/pipeline-metadata.d.ts.map +1 -0
- package/dist/server/pipeline.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/adapters/nitro.ts +9 -7
- package/src/cli.ts +9 -2
- package/src/client/browser-dev.ts +142 -0
- package/src/client/browser-entry.ts +73 -223
- package/src/client/browser-links.ts +90 -0
- package/src/client/index.ts +4 -0
- package/src/client/navigation-context.ts +118 -0
- package/src/client/router.ts +37 -33
- package/src/client/transition-root.tsx +86 -0
- package/src/client/use-params.ts +50 -54
- package/src/client/use-pathname.ts +31 -24
- package/src/client/use-router.ts +17 -15
- package/src/server/metadata-platform.ts +229 -0
- package/src/server/metadata-render.ts +9 -363
- package/src/server/metadata-social.ts +184 -0
- package/src/server/pipeline-interception.ts +76 -0
- package/src/server/pipeline-metadata.ts +90 -0
- package/src/server/pipeline.ts +2 -148
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform-specific metadata rendering — icons, Apple Web App, App Links, iTunes.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from metadata-render.ts to keep files under 500 lines.
|
|
5
|
+
*
|
|
6
|
+
* See design/16-metadata.md
|
|
7
|
+
*/
|
|
8
|
+
import type { Metadata } from './types.js';
|
|
9
|
+
import type { HeadElement } from './metadata.js';
|
|
10
|
+
/**
|
|
11
|
+
* Render icon link elements (favicon, shortcut, apple-touch-icon, custom).
|
|
12
|
+
*/
|
|
13
|
+
export declare function renderIcons(icons: NonNullable<Metadata['icons']>, elements: HeadElement[]): void;
|
|
14
|
+
/**
|
|
15
|
+
* Render alternate link elements (canonical, hreflang, media, types).
|
|
16
|
+
*/
|
|
17
|
+
export declare function renderAlternates(alternates: NonNullable<Metadata['alternates']>, elements: HeadElement[]): void;
|
|
18
|
+
/**
|
|
19
|
+
* Render site verification meta tags (Google, Yahoo, Yandex, custom).
|
|
20
|
+
*/
|
|
21
|
+
export declare function renderVerification(verification: NonNullable<Metadata['verification']>, elements: HeadElement[]): void;
|
|
22
|
+
/**
|
|
23
|
+
* Render Apple Web App meta tags and startup image links.
|
|
24
|
+
*/
|
|
25
|
+
export declare function renderAppleWebApp(appleWebApp: NonNullable<Metadata['appleWebApp']>, elements: HeadElement[]): void;
|
|
26
|
+
/**
|
|
27
|
+
* Render App Links (al:*) meta tags for deep linking across platforms.
|
|
28
|
+
*/
|
|
29
|
+
export declare function renderAppLinks(appLinks: NonNullable<Metadata['appLinks']>, elements: HeadElement[]): void;
|
|
30
|
+
/**
|
|
31
|
+
* Render Apple iTunes smart banner meta tag.
|
|
32
|
+
*/
|
|
33
|
+
export declare function renderItunes(itunes: NonNullable<Metadata['itunes']>, elements: HeadElement[]): void;
|
|
34
|
+
//# sourceMappingURL=metadata-platform.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metadata-platform.d.ts","sourceRoot":"","sources":["../../src/server/metadata-platform.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEjD;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,WAAW,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,EAAE,QAAQ,EAAE,WAAW,EAAE,GAAG,IAAI,CA6ChG;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,WAAW,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,EAC/C,QAAQ,EAAE,WAAW,EAAE,GACtB,IAAI,CA+BN;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,YAAY,EAAE,WAAW,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC,EACnD,QAAQ,EAAE,WAAW,EAAE,GACtB,IAAI,CAkBN;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,WAAW,EAAE,WAAW,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,EACjD,QAAQ,EAAE,WAAW,EAAE,GACtB,IAAI,CAmCN;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,WAAW,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,EAC3C,QAAQ,EAAE,WAAW,EAAE,GACtB,IAAI,CAwCN;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,WAAW,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,EAAE,QAAQ,EAAE,WAAW,EAAE,GAAG,IAAI,CAQnG"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metadata-render.d.ts","sourceRoot":"","sources":["../../src/server/metadata-render.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"metadata-render.d.ts","sourceRoot":"","sources":["../../src/server/metadata-render.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAajD;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,QAAQ,GAAG,WAAW,EAAE,CAkI1E"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Social metadata rendering — Open Graph and Twitter Card meta tags.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from metadata-render.ts to keep files under 500 lines.
|
|
5
|
+
*
|
|
6
|
+
* See design/16-metadata.md
|
|
7
|
+
*/
|
|
8
|
+
import type { Metadata } from './types.js';
|
|
9
|
+
import type { HeadElement } from './metadata.js';
|
|
10
|
+
/**
|
|
11
|
+
* Render Open Graph metadata into head element descriptors.
|
|
12
|
+
*
|
|
13
|
+
* Handles og:title, og:description, og:image (with dimensions/alt),
|
|
14
|
+
* og:video, og:audio, og:article:author, and other OG properties.
|
|
15
|
+
*/
|
|
16
|
+
export declare function renderOpenGraph(og: NonNullable<Metadata['openGraph']>, elements: HeadElement[]): void;
|
|
17
|
+
/**
|
|
18
|
+
* Render Twitter Card metadata into head element descriptors.
|
|
19
|
+
*
|
|
20
|
+
* Handles twitter:card, twitter:site, twitter:title, twitter:image,
|
|
21
|
+
* twitter:player, and twitter:app (per-platform name/id/url).
|
|
22
|
+
*/
|
|
23
|
+
export declare function renderTwitter(tw: NonNullable<Metadata['twitter']>, elements: HeadElement[]): void;
|
|
24
|
+
//# sourceMappingURL=metadata-social.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metadata-social.d.ts","sourceRoot":"","sources":["../../src/server/metadata-social.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEjD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,EAAE,EAAE,WAAW,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,EAAE,QAAQ,EAAE,WAAW,EAAE,GAAG,IAAI,CAoErG;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,EAAE,EAAE,WAAW,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,EAAE,QAAQ,EAAE,WAAW,EAAE,GAAG,IAAI,CA0FjG"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interception route matching for the request pipeline.
|
|
3
|
+
*
|
|
4
|
+
* Matches target URLs against interception rewrites to support the
|
|
5
|
+
* modal route pattern (soft navigation intercepts).
|
|
6
|
+
*
|
|
7
|
+
* Extracted from pipeline.ts to keep files under 500 lines.
|
|
8
|
+
*
|
|
9
|
+
* See design/07-routing.md §"Intercepting Routes"
|
|
10
|
+
*/
|
|
11
|
+
/** Result of a successful interception match. */
|
|
12
|
+
export interface InterceptionMatchResult {
|
|
13
|
+
/** The pathname to re-match (the source/intercepting route's parent). */
|
|
14
|
+
sourcePathname: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Check if an intercepting route applies for this soft navigation.
|
|
18
|
+
*
|
|
19
|
+
* Matches the target pathname against interception rewrites, constrained
|
|
20
|
+
* by the source URL (X-Timber-URL header — where the user navigates FROM).
|
|
21
|
+
*
|
|
22
|
+
* Returns the source pathname to re-match if interception applies, or null.
|
|
23
|
+
*/
|
|
24
|
+
export declare function findInterceptionMatch(targetPathname: string, sourceUrl: string, rewrites: import('#/routing/interception.js').InterceptionRewrite[]): InterceptionMatchResult | null;
|
|
25
|
+
/**
|
|
26
|
+
* Check if a pathname matches a URL pattern with dynamic segments.
|
|
27
|
+
*
|
|
28
|
+
* Supports [param] (single segment) and [...param] (one or more segments).
|
|
29
|
+
* Static segments must match exactly.
|
|
30
|
+
*/
|
|
31
|
+
export declare function pathnameMatchesPattern(pathname: string, pattern: string): boolean;
|
|
32
|
+
//# sourceMappingURL=pipeline-interception.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pipeline-interception.d.ts","sourceRoot":"","sources":["../../src/server/pipeline-interception.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,iDAAiD;AACjD,MAAM,WAAW,uBAAuB;IACtC,yEAAyE;IACzE,cAAc,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,cAAc,EAAE,MAAM,EACtB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,OAAO,2BAA2B,EAAE,mBAAmB,EAAE,GAClE,uBAAuB,GAAG,IAAI,CAYhC;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CA0BjF"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Metadata route helpers for the request pipeline.
|
|
3
|
+
*
|
|
4
|
+
* Handles serving static metadata files and serializing sitemap responses.
|
|
5
|
+
* Extracted from pipeline.ts to keep files under 500 lines.
|
|
6
|
+
*
|
|
7
|
+
* See design/16-metadata.md §"Metadata Routes"
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Serve a static metadata file by reading it from disk.
|
|
11
|
+
*
|
|
12
|
+
* Static metadata route files (.xml, .txt, .json, .png, .ico, .svg, etc.)
|
|
13
|
+
* are served as-is with the appropriate Content-Type header.
|
|
14
|
+
* Text files include charset=utf-8; binary files do not.
|
|
15
|
+
*
|
|
16
|
+
* See design/16-metadata.md §"Metadata Routes"
|
|
17
|
+
*/
|
|
18
|
+
export declare function serveStaticMetadataFile(metaMatch: import('./route-matcher.js').MetadataRouteMatch): Promise<Response>;
|
|
19
|
+
/**
|
|
20
|
+
* Serialize a sitemap array to XML.
|
|
21
|
+
* Follows the sitemap.org protocol: https://www.sitemaps.org/protocol.html
|
|
22
|
+
*/
|
|
23
|
+
export declare function serializeSitemap(entries: Array<{
|
|
24
|
+
url: string;
|
|
25
|
+
lastModified?: string | Date;
|
|
26
|
+
changeFrequency?: string;
|
|
27
|
+
priority?: number;
|
|
28
|
+
}>): string;
|
|
29
|
+
/** Escape special XML characters. */
|
|
30
|
+
export declare function escapeXml(str: string): string;
|
|
31
|
+
//# sourceMappingURL=pipeline-metadata.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pipeline-metadata.d.ts","sourceRoot":"","sources":["../../src/server/pipeline-metadata.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAgBH;;;;;;;;GAQG;AACH,wBAAsB,uBAAuB,CAC3C,SAAS,EAAE,OAAO,oBAAoB,EAAE,kBAAkB,GACzD,OAAO,CAAC,QAAQ,CAAC,CAYnB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,KAAK,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC,GACD,MAAM,CAoBR;AAED,qCAAqC;AACrC,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAO7C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/server/pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;
|
|
1
|
+
{"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/server/pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAAY,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AACxD,OAAO,EAAiB,KAAK,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAoC1E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAItD,sEAAsE;AACtE,MAAM,WAAW,UAAU;IACzB,mDAAmD;IACnD,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,oEAAoE;IACpE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;IAC1C,uDAAuD;IACvD,UAAU,CAAC,EAAE,YAAY,CAAC;CAC3B;AAED,6DAA6D;AAC7D,MAAM,MAAM,YAAY,GAAG,CAAC,QAAQ,EAAE,MAAM,KAAK,UAAU,GAAG,IAAI,CAAC;AAEnE,sEAAsE;AACtE,MAAM,MAAM,oBAAoB,GAAG,CACjC,QAAQ,EAAE,MAAM,KACb,OAAO,oBAAoB,EAAE,kBAAkB,GAAG,IAAI,CAAC;AAE5D,iEAAiE;AACjE,MAAM,WAAW,mBAAmB;IAClC,iEAAiE;IACjE,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,6DAA6D;AAC7D,MAAM,MAAM,aAAa,GAAG,CAC1B,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,UAAU,EACjB,eAAe,EAAE,OAAO,EACxB,oBAAoB,EAAE,OAAO,EAC7B,YAAY,CAAC,EAAE,mBAAmB,KAC/B,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;AAElC,+DAA+D;AAC/D,MAAM,MAAM,iBAAiB,GAAG,CAC9B,KAAK,EAAE,UAAU,EACjB,GAAG,EAAE,OAAO,EACZ,eAAe,EAAE,OAAO,KACrB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAI1B,MAAM,WAAW,cAAc;IAC7B,iFAAiF;IACjF,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,gFAAgF;IAChF,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC;QAAE,OAAO,EAAE,WAAW,CAAA;KAAE,CAAC,CAAC;IACtD,qEAAqE;IACrE,UAAU,EAAE,YAAY,CAAC;IACzB,iGAAiG;IACjG,kBAAkB,CAAC,EAAE,oBAAoB,CAAC;IAC1C,kEAAkE;IAClE,MAAM,EAAE,aAAa,CAAC;IACtB,kEAAkE;IAClE,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,eAAe,EAAE,OAAO,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IACzF,kFAAkF;IAClF,UAAU,CAAC,EAAE,iBAAiB,CAAC;IAC/B,gFAAgF;IAChF,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,yGAAyG;IACzG,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,OAAO,2BAA2B,EAAE,mBAAmB,EAAE,CAAC;IACjF;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxD;;;;;;;;;;OAUG;IACH,mBAAmB,CAAC,EAAE,CACpB,KAAK,EAAE,OAAO,EACd,GAAG,EAAE,OAAO,EACZ,eAAe,EAAE,OAAO,KACrB,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CACnC;AAID;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,cAAc,GAAG,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAuU1F"}
|
package/package.json
CHANGED
package/src/adapters/nitro.ts
CHANGED
|
@@ -239,7 +239,13 @@ export function generateNitroEntry(
|
|
|
239
239
|
preset: NitroPreset,
|
|
240
240
|
hasManifestInit = false
|
|
241
241
|
): string {
|
|
242
|
-
|
|
242
|
+
// The RSC entry is the main request handler — it exports the fetch handler as default.
|
|
243
|
+
// The Vite RSC plugin outputs it to rsc/index.js.
|
|
244
|
+
let serverEntryRelative = relative(outDir, join(buildDir, 'rsc', 'index.js'));
|
|
245
|
+
// Ensure the import path starts with ./ for ESM compatibility
|
|
246
|
+
if (!serverEntryRelative.startsWith('.')) {
|
|
247
|
+
serverEntryRelative = './' + serverEntryRelative;
|
|
248
|
+
}
|
|
243
249
|
const runtimeName = PRESET_CONFIGS[preset].runtimeName;
|
|
244
250
|
const earlyHints = PRESET_CONFIGS[preset].supportsEarlyHints;
|
|
245
251
|
|
|
@@ -250,10 +256,6 @@ export function generateNitroEntry(
|
|
|
250
256
|
// On node-server and bun, wrap the handler with ALS so the pipeline
|
|
251
257
|
// can send 103 Early Hints via res.writeEarlyHints(). Other presets
|
|
252
258
|
// either don't support 103 or handle it at the CDN level.
|
|
253
|
-
const earlyHintsImport = earlyHints
|
|
254
|
-
? `import { runWithEarlyHintsSender } from '${serverEntryRelative}'\n`
|
|
255
|
-
: '';
|
|
256
|
-
|
|
257
259
|
const handlerCall = earlyHints
|
|
258
260
|
? ` const nodeRes = event.node?.res
|
|
259
261
|
const earlyHintsSender = (typeof nodeRes?.writeEarlyHints === 'function')
|
|
@@ -268,8 +270,8 @@ export function generateNitroEntry(
|
|
|
268
270
|
return `// Generated by @timber-js/app/adapters/nitro
|
|
269
271
|
// Do not edit — this file is regenerated on each build.
|
|
270
272
|
|
|
271
|
-
${manifestImport}
|
|
272
|
-
import {
|
|
273
|
+
${manifestImport}import { defineEventHandler, toWebRequest, sendWebResponse } from 'h3'
|
|
274
|
+
import handler, { runWithEarlyHintsSender } from '${serverEntryRelative}'
|
|
273
275
|
|
|
274
276
|
// Set TIMBER_RUNTIME for instrumentation.ts conditional SDK initialization.
|
|
275
277
|
// See design/25-production-deployments.md §"TIMBER_RUNTIME".
|
package/src/cli.ts
CHANGED
|
@@ -189,9 +189,16 @@ async function main(): Promise<void> {
|
|
|
189
189
|
}
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
-
//
|
|
192
|
+
// Run main when executed as a CLI (not imported in tests).
|
|
193
|
+
// The bin shim (bin/timber.mjs) does `import '../dist/cli.js'`, so
|
|
194
|
+
// process.argv[1] points to the shim, not this file. We check both:
|
|
195
|
+
// direct execution AND being imported by the timber bin shim.
|
|
193
196
|
const isDirectExecution =
|
|
194
|
-
typeof process !== 'undefined' &&
|
|
197
|
+
typeof process !== 'undefined' &&
|
|
198
|
+
process.argv[1] &&
|
|
199
|
+
(import.meta.url.endsWith(process.argv[1]) ||
|
|
200
|
+
process.argv[1].endsWith('bin/timber.mjs') ||
|
|
201
|
+
process.argv[1].endsWith('bin/timber'));
|
|
195
202
|
|
|
196
203
|
if (isDirectExecution) {
|
|
197
204
|
main().catch((err) => {
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dev-only browser helpers — server log replay and client error forwarding.
|
|
3
|
+
*
|
|
4
|
+
* These are only active when import.meta.hot is available (Vite dev mode).
|
|
5
|
+
* Extracted from browser-entry.ts to keep files under 500 lines.
|
|
6
|
+
*
|
|
7
|
+
* See design/21-dev-server.md §"HMR Wiring"
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { isPageUnloading } from './unload-guard.js';
|
|
11
|
+
|
|
12
|
+
// ─── HMR Hot Interface ──────────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
/** Minimal interface for Vite's HMR channel. */
|
|
15
|
+
export interface HotInterface {
|
|
16
|
+
on(event: string, cb: (...args: unknown[]) => void): void;
|
|
17
|
+
send(event: string, data: unknown): void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// ─── Server Log Replay ──────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
/** Payload shape from plugins/dev-logs.ts */
|
|
23
|
+
interface ServerLogPayload {
|
|
24
|
+
level: 'log' | 'warn' | 'error' | 'debug' | 'info';
|
|
25
|
+
args: unknown[];
|
|
26
|
+
location: string | null;
|
|
27
|
+
timestamp: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Deserialize a serialized arg back into a console-friendly value.
|
|
32
|
+
*
|
|
33
|
+
* Handles Error objects (serialized as { __type: 'Error', ... }),
|
|
34
|
+
* Maps, Sets, and passes everything else through.
|
|
35
|
+
*/
|
|
36
|
+
function deserializeArg(arg: unknown): unknown {
|
|
37
|
+
if (arg === '[undefined]') return undefined;
|
|
38
|
+
if (arg === null || typeof arg !== 'object') return arg;
|
|
39
|
+
|
|
40
|
+
const obj = arg as Record<string, unknown>;
|
|
41
|
+
|
|
42
|
+
if (obj.__type === 'Error') {
|
|
43
|
+
const err = new Error(obj.message as string);
|
|
44
|
+
err.name = obj.name as string;
|
|
45
|
+
if (obj.stack) err.stack = obj.stack as string;
|
|
46
|
+
return err;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (obj.__type === 'Map') {
|
|
50
|
+
return new Map(
|
|
51
|
+
Object.entries(obj.entries as Record<string, unknown>).map(([k, v]) => [k, deserializeArg(v)])
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (obj.__type === 'Set') {
|
|
56
|
+
return new Set((obj.values as unknown[]).map(deserializeArg));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (Array.isArray(arg)) {
|
|
60
|
+
return arg.map(deserializeArg);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Plain object — recurse
|
|
64
|
+
const result: Record<string, unknown> = {};
|
|
65
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
66
|
+
result[key] = deserializeArg(value);
|
|
67
|
+
}
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Set up the HMR listener that replays server console output in the browser.
|
|
73
|
+
*
|
|
74
|
+
* Each message arrives with a log level and serialized args. We prepend
|
|
75
|
+
* a styled "[SERVER]" badge and call the matching console method.
|
|
76
|
+
*/
|
|
77
|
+
export function setupServerLogReplay(hot: Pick<HotInterface, 'on'>): void {
|
|
78
|
+
/** CSS styles for the [SERVER] badge in browser console. */
|
|
79
|
+
const BADGE_STYLES: Record<string, string> = {
|
|
80
|
+
log: 'background: #0070f3; color: white; padding: 1px 5px; border-radius: 3px; font-weight: bold;',
|
|
81
|
+
info: 'background: #0070f3; color: white; padding: 1px 5px; border-radius: 3px; font-weight: bold;',
|
|
82
|
+
warn: 'background: #f5a623; color: white; padding: 1px 5px; border-radius: 3px; font-weight: bold;',
|
|
83
|
+
error:
|
|
84
|
+
'background: #e00; color: white; padding: 1px 5px; border-radius: 3px; font-weight: bold;',
|
|
85
|
+
debug:
|
|
86
|
+
'background: #666; color: white; padding: 1px 5px; border-radius: 3px; font-weight: bold;',
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
hot.on('timber:server-log', (data: unknown) => {
|
|
90
|
+
const payload = data as ServerLogPayload;
|
|
91
|
+
const level = payload.level;
|
|
92
|
+
const fn = console[level] ?? console.log;
|
|
93
|
+
const args = payload.args.map(deserializeArg);
|
|
94
|
+
|
|
95
|
+
const badge = `%cSERVER`;
|
|
96
|
+
const style = BADGE_STYLES[level] ?? BADGE_STYLES.log;
|
|
97
|
+
const locationSuffix = payload.location ? ` (${payload.location})` : '';
|
|
98
|
+
|
|
99
|
+
fn.call(console, badge, style, ...args, locationSuffix ? `\n → ${payload.location}` : '');
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ─── Client Error Forwarding ────────────────────────────────────────
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Set up global error handlers that forward uncaught client-side
|
|
107
|
+
* errors to the dev server via Vite's HMR channel.
|
|
108
|
+
*
|
|
109
|
+
* The server receives 'timber:client-error' events, and echoes them
|
|
110
|
+
* back as Vite '{ type: "error" }' payloads to trigger the overlay.
|
|
111
|
+
*/
|
|
112
|
+
export function setupClientErrorForwarding(hot: Pick<HotInterface, 'send'>): void {
|
|
113
|
+
window.addEventListener('error', (event: ErrorEvent) => {
|
|
114
|
+
// Skip errors without useful information
|
|
115
|
+
if (!event.error && !event.message) return;
|
|
116
|
+
// Skip errors during page unload — these are abort-related, not application errors
|
|
117
|
+
if (isPageUnloading()) return;
|
|
118
|
+
|
|
119
|
+
const error = event.error;
|
|
120
|
+
hot.send('timber:client-error', {
|
|
121
|
+
message: error?.message ?? event.message,
|
|
122
|
+
stack: error?.stack ?? '',
|
|
123
|
+
componentStack: error?.componentStack ?? null,
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
window.addEventListener('unhandledrejection', (event: PromiseRejectionEvent) => {
|
|
128
|
+
const reason = event.reason;
|
|
129
|
+
if (!reason) return;
|
|
130
|
+
// Skip rejections during page unload — aborted fetches/streams cause these
|
|
131
|
+
if (isPageUnloading()) return;
|
|
132
|
+
|
|
133
|
+
const message = reason instanceof Error ? reason.message : String(reason);
|
|
134
|
+
const stack = reason instanceof Error ? (reason.stack ?? '') : '';
|
|
135
|
+
|
|
136
|
+
hot.send('timber:client-error', {
|
|
137
|
+
message,
|
|
138
|
+
stack,
|
|
139
|
+
componentStack: null,
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
}
|