@reckona/mreact-router 0.0.82 → 0.0.84

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.
Files changed (80) hide show
  1. package/dist/adapters/aws-lambda.d.ts.map +1 -1
  2. package/dist/adapters/aws-lambda.js +17 -22
  3. package/dist/adapters/aws-lambda.js.map +1 -1
  4. package/dist/build.d.ts.map +1 -1
  5. package/dist/build.js +3 -1
  6. package/dist/build.js.map +1 -1
  7. package/dist/client-route-inference.d.ts +2 -0
  8. package/dist/client-route-inference.d.ts.map +1 -0
  9. package/dist/client-route-inference.js +2 -0
  10. package/dist/client-route-inference.js.map +1 -0
  11. package/dist/client.d.ts +2 -1
  12. package/dist/client.d.ts.map +1 -1
  13. package/dist/client.js +25 -19
  14. package/dist/client.js.map +1 -1
  15. package/dist/layout-composer.d.ts +24 -0
  16. package/dist/layout-composer.d.ts.map +1 -0
  17. package/dist/layout-composer.js +83 -0
  18. package/dist/layout-composer.js.map +1 -0
  19. package/dist/metadata.d.ts +9 -0
  20. package/dist/metadata.d.ts.map +1 -0
  21. package/dist/metadata.js +419 -0
  22. package/dist/metadata.js.map +1 -0
  23. package/dist/middleware.d.ts +22 -0
  24. package/dist/middleware.d.ts.map +1 -0
  25. package/dist/middleware.js +94 -0
  26. package/dist/middleware.js.map +1 -0
  27. package/dist/module-runner.d.ts +2 -2
  28. package/dist/module-runner.d.ts.map +1 -1
  29. package/dist/module-runner.js +1 -1
  30. package/dist/module-runner.js.map +1 -1
  31. package/dist/navigation-runtime.d.ts +2 -0
  32. package/dist/navigation-runtime.d.ts.map +1 -0
  33. package/dist/navigation-runtime.js +2 -0
  34. package/dist/navigation-runtime.js.map +1 -0
  35. package/dist/preload-policy.d.ts +24 -0
  36. package/dist/preload-policy.d.ts.map +1 -0
  37. package/dist/preload-policy.js +45 -0
  38. package/dist/preload-policy.js.map +1 -0
  39. package/dist/render.d.ts +1 -3
  40. package/dist/render.d.ts.map +1 -1
  41. package/dist/render.js +32 -676
  42. package/dist/render.js.map +1 -1
  43. package/dist/route-hydration-contract.d.ts +11 -0
  44. package/dist/route-hydration-contract.d.ts.map +1 -0
  45. package/dist/route-hydration-contract.js +21 -0
  46. package/dist/route-hydration-contract.js.map +1 -0
  47. package/dist/route-module-loader.d.ts +10 -0
  48. package/dist/route-module-loader.d.ts.map +1 -0
  49. package/dist/route-module-loader.js +61 -0
  50. package/dist/route-module-loader.js.map +1 -0
  51. package/dist/route-source.d.ts.map +1 -1
  52. package/dist/route-source.js +7 -5
  53. package/dist/route-source.js.map +1 -1
  54. package/dist/routes.js +6 -0
  55. package/dist/routes.js.map +1 -1
  56. package/dist/serve.d.ts +13 -0
  57. package/dist/serve.d.ts.map +1 -1
  58. package/dist/serve.js +63 -28
  59. package/dist/serve.js.map +1 -1
  60. package/dist/vite.d.ts.map +1 -1
  61. package/dist/vite.js +2 -1
  62. package/dist/vite.js.map +1 -1
  63. package/package.json +11 -11
  64. package/src/adapters/aws-lambda.ts +21 -28
  65. package/src/build.ts +9 -6
  66. package/src/client-route-inference.ts +18 -0
  67. package/src/client.ts +32 -20
  68. package/src/layout-composer.ts +142 -0
  69. package/src/metadata.ts +578 -0
  70. package/src/middleware.ts +153 -0
  71. package/src/module-runner.ts +3 -2
  72. package/src/navigation-runtime.ts +16 -0
  73. package/src/preload-policy.ts +89 -0
  74. package/src/render.ts +75 -986
  75. package/src/route-hydration-contract.ts +22 -0
  76. package/src/route-module-loader.ts +95 -0
  77. package/src/route-source.ts +9 -4
  78. package/src/routes.ts +8 -0
  79. package/src/serve.ts +110 -29
  80. package/src/vite.ts +6 -4
@@ -0,0 +1,24 @@
1
+ export interface SlotRenderContext {
2
+ consumedSlots: Set<string>;
3
+ namedSlots: Readonly<Record<string, string>>;
4
+ }
5
+ export interface ShellFile {
6
+ file: string;
7
+ id: string;
8
+ kind: "layout" | "template";
9
+ }
10
+ export interface RenderedShellParts {
11
+ prefix: string;
12
+ suffix: string;
13
+ }
14
+ export declare function createSlotRenderContext(namedSlots?: Readonly<Record<string, string>>): SlotRenderContext;
15
+ export declare function splitLayoutSlot(layoutHtml: string, slotContext?: SlotRenderContext): RenderedShellParts;
16
+ export declare function markShellBoundary(html: string, shell: ShellFile): string;
17
+ export declare function shellBoundaryId(appDir: string, directory: string): string;
18
+ export declare function warnUnconsumedRouteSlots(options: {
19
+ appDir: string;
20
+ pageFile: string;
21
+ serverModuleCacheVersion: string | undefined;
22
+ slotContext: SlotRenderContext;
23
+ }): void;
24
+ //# sourceMappingURL=layout-composer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layout-composer.d.ts","sourceRoot":"","sources":["../src/layout-composer.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,iBAAiB;IAChC,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CAC9C;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,QAAQ,GAAG,UAAU,CAAC;CAC7B;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,uBAAuB,CACrC,UAAU,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAM,GAChD,iBAAiB,CAKnB;AAED,wBAAgB,eAAe,CAC7B,UAAU,EAAE,MAAM,EAClB,WAAW,GAAE,iBAA6C,GACzD,kBAAkB,CAYpB;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,MAAM,CAYxE;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAMzE;AAED,wBAAgB,wBAAwB,CAAC,OAAO,EAAE;IAChD,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,wBAAwB,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7C,WAAW,EAAE,iBAAiB,CAAC;CAChC,GAAG,IAAI,CA0BP"}
@@ -0,0 +1,83 @@
1
+ import { relative, sep } from "node:path";
2
+ import { escapeHtmlAttribute } from "@reckona/mreact-shared/html-escape";
3
+ export function createSlotRenderContext(namedSlots = {}) {
4
+ return {
5
+ consumedSlots: new Set(),
6
+ namedSlots,
7
+ };
8
+ }
9
+ export function splitLayoutSlot(layoutHtml, slotContext = createSlotRenderContext()) {
10
+ const html = replaceNamedLayoutSlots(layoutHtml, slotContext);
11
+ const match = findDefaultLayoutSlot(html);
12
+ if (match === null) {
13
+ return { prefix: html, suffix: "" };
14
+ }
15
+ return {
16
+ prefix: html.slice(0, match.index),
17
+ suffix: html.slice(match.index + match[0].length),
18
+ };
19
+ }
20
+ export function markShellBoundary(html, shell) {
21
+ const attributeName = shell.kind === "layout" ? "data-mreact-layout-boundary" : "data-mreact-template-boundary";
22
+ if (html.includes(`${attributeName}=`)) {
23
+ return html;
24
+ }
25
+ return html.replace(/<([A-Za-z][^\s/>]*)([^>]*)>/, `<$1$2 ${attributeName}="${escapeHtmlAttribute(shell.id)}">`);
26
+ }
27
+ export function shellBoundaryId(appDir, directory) {
28
+ const relativeDirectory = relative(appDir, directory);
29
+ return relativeDirectory === ""
30
+ ? "root"
31
+ : relativeDirectory.replaceAll(sep, "/").replace(/[^A-Za-z0-9_$/-]/g, "_");
32
+ }
33
+ export function warnUnconsumedRouteSlots(options) {
34
+ if (options.serverModuleCacheVersion !== undefined) {
35
+ return;
36
+ }
37
+ const slotNames = Object.keys(options.slotContext.namedSlots);
38
+ if (slotNames.length === 0) {
39
+ return;
40
+ }
41
+ const routeLabel = relative(options.appDir, options.pageFile).replaceAll(sep, "/");
42
+ for (const name of slotNames) {
43
+ if (name === "default") {
44
+ console.warn(`[mreact] ${routeLabel}: slots.default does not target <Slot />; use the page body for default slot content.`);
45
+ continue;
46
+ }
47
+ if (!options.slotContext.consumedSlots.has(name)) {
48
+ console.warn(`[mreact] ${routeLabel}: slots.{${name}} is not consumed by any ancestor layout or template.`);
49
+ }
50
+ }
51
+ }
52
+ function replaceNamedLayoutSlots(layoutHtml, slotContext) {
53
+ return layoutHtml.replace(SLOT_TAG_PATTERN, (source, openAttributes) => {
54
+ const name = readSlotName(openAttributes);
55
+ if (name === undefined || name === "default") {
56
+ return source;
57
+ }
58
+ if (Object.hasOwn(slotContext.namedSlots, name)) {
59
+ slotContext.consumedSlots.add(name);
60
+ return slotContext.namedSlots[name] ?? "";
61
+ }
62
+ return "";
63
+ });
64
+ }
65
+ const SLOT_TAG_PATTERN = /<slot\b([^>]*)>(?:<\/slot\s*>)?/g;
66
+ function findDefaultLayoutSlot(html) {
67
+ SLOT_TAG_PATTERN.lastIndex = 0;
68
+ for (;;) {
69
+ const match = SLOT_TAG_PATTERN.exec(html);
70
+ if (match === null) {
71
+ return null;
72
+ }
73
+ const name = readSlotName(match[1] ?? "");
74
+ if (name === undefined || name === "default") {
75
+ return match;
76
+ }
77
+ }
78
+ }
79
+ function readSlotName(attributes) {
80
+ const match = /\bname\s*=\s*(?:"([^"]*)"|'([^']*)')/.exec(attributes);
81
+ return match?.[1] ?? match?.[2];
82
+ }
83
+ //# sourceMappingURL=layout-composer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layout-composer.js","sourceRoot":"","sources":["../src/layout-composer.ts"],"names":[],"mappings":"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","sourcesContent":["import { relative, sep } from \"node:path\";\nimport { escapeHtmlAttribute } from \"@reckona/mreact-shared/html-escape\";\n\nexport interface SlotRenderContext {\n consumedSlots: Set<string>;\n namedSlots: Readonly<Record<string, string>>;\n}\n\nexport interface ShellFile {\n file: string;\n id: string;\n kind: \"layout\" | \"template\";\n}\n\nexport interface RenderedShellParts {\n prefix: string;\n suffix: string;\n}\n\nexport function createSlotRenderContext(\n namedSlots: Readonly<Record<string, string>> = {},\n): SlotRenderContext {\n return {\n consumedSlots: new Set(),\n namedSlots,\n };\n}\n\nexport function splitLayoutSlot(\n layoutHtml: string,\n slotContext: SlotRenderContext = createSlotRenderContext(),\n): RenderedShellParts {\n const html = replaceNamedLayoutSlots(layoutHtml, slotContext);\n const match = findDefaultLayoutSlot(html);\n\n if (match === null) {\n return { prefix: html, suffix: \"\" };\n }\n\n return {\n prefix: html.slice(0, match.index),\n suffix: html.slice(match.index + match[0].length),\n };\n}\n\nexport function markShellBoundary(html: string, shell: ShellFile): string {\n const attributeName =\n shell.kind === \"layout\" ? \"data-mreact-layout-boundary\" : \"data-mreact-template-boundary\";\n\n if (html.includes(`${attributeName}=`)) {\n return html;\n }\n\n return html.replace(\n /<([A-Za-z][^\\s/>]*)([^>]*)>/,\n `<$1$2 ${attributeName}=\"${escapeHtmlAttribute(shell.id)}\">`,\n );\n}\n\nexport function shellBoundaryId(appDir: string, directory: string): string {\n const relativeDirectory = relative(appDir, directory);\n\n return relativeDirectory === \"\"\n ? \"root\"\n : relativeDirectory.replaceAll(sep, \"/\").replace(/[^A-Za-z0-9_$/-]/g, \"_\");\n}\n\nexport function warnUnconsumedRouteSlots(options: {\n appDir: string;\n pageFile: string;\n serverModuleCacheVersion: string | undefined;\n slotContext: SlotRenderContext;\n}): void {\n if (options.serverModuleCacheVersion !== undefined) {\n return;\n }\n\n const slotNames = Object.keys(options.slotContext.namedSlots);\n if (slotNames.length === 0) {\n return;\n }\n\n const routeLabel = relative(options.appDir, options.pageFile).replaceAll(sep, \"/\");\n\n for (const name of slotNames) {\n if (name === \"default\") {\n console.warn(\n `[mreact] ${routeLabel}: slots.default does not target <Slot />; use the page body for default slot content.`,\n );\n continue;\n }\n\n if (!options.slotContext.consumedSlots.has(name)) {\n console.warn(\n `[mreact] ${routeLabel}: slots.{${name}} is not consumed by any ancestor layout or template.`,\n );\n }\n }\n}\n\nfunction replaceNamedLayoutSlots(layoutHtml: string, slotContext: SlotRenderContext): string {\n return layoutHtml.replace(SLOT_TAG_PATTERN, (source, openAttributes: string) => {\n const name = readSlotName(openAttributes);\n\n if (name === undefined || name === \"default\") {\n return source;\n }\n\n if (Object.hasOwn(slotContext.namedSlots, name)) {\n slotContext.consumedSlots.add(name);\n return slotContext.namedSlots[name] ?? \"\";\n }\n\n return \"\";\n });\n}\n\nconst SLOT_TAG_PATTERN = /<slot\\b([^>]*)>(?:<\\/slot\\s*>)?/g;\n\nfunction findDefaultLayoutSlot(html: string): RegExpExecArray | null {\n SLOT_TAG_PATTERN.lastIndex = 0;\n\n for (;;) {\n const match = SLOT_TAG_PATTERN.exec(html);\n\n if (match === null) {\n return null;\n }\n\n const name = readSlotName(match[1] ?? \"\");\n\n if (name === undefined || name === \"default\") {\n return match;\n }\n }\n}\n\nfunction readSlotName(attributes: string): string | undefined {\n const match = /\\bname\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)')/.exec(attributes);\n\n return match?.[1] ?? match?.[2];\n}\n"]}
@@ -0,0 +1,9 @@
1
+ import type { AppRoute } from "./routes.js";
2
+ import type { RobotsManifest, RouteMetadata, RouteParams, SitemapEntry } from "./types.js";
3
+ export declare function mergeRouteMetadata(metadata: readonly RouteMetadata[]): RouteMetadata | undefined;
4
+ export declare function applyFileConventionMetadata(metadata: RouteMetadata | undefined, routes: readonly AppRoute[], filename: string, params: RouteParams): RouteMetadata | undefined;
5
+ export declare function injectHeadMetadata(html: string, metadata: RouteMetadata | undefined): string;
6
+ export declare function responseHeadersForMetadata(metadata: RouteMetadata | undefined, request: Request, extra?: Readonly<Record<string, string>>): HeadersInit;
7
+ export declare function serializeRobots(manifest: RobotsManifest): string;
8
+ export declare function serializeSitemap(entries: readonly SitemapEntry[]): string;
9
+ //# sourceMappingURL=metadata.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metadata.d.ts","sourceRoot":"","sources":["../src/metadata.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,KAAK,EAKV,cAAc,EAEd,aAAa,EACb,WAAW,EACX,YAAY,EACb,MAAM,YAAY,CAAC;AAQpB,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,SAAS,aAAa,EAAE,GAAG,aAAa,GAAG,SAAS,CA+BhG;AAED,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,aAAa,GAAG,SAAS,EACnC,MAAM,EAAE,SAAS,QAAQ,EAAE,EAC3B,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,WAAW,GAClB,aAAa,GAAG,SAAS,CA4B3B;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,GAAG,SAAS,GAAG,MAAM,CA2D5F;AAED,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE,aAAa,GAAG,SAAS,EACnC,OAAO,EAAE,OAAO,EAChB,KAAK,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GACvC,WAAW,CAoBb;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,cAAc,GAAG,MAAM,CA6BhE;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,GAAG,MAAM,CAmBzE"}
@@ -0,0 +1,419 @@
1
+ import { escapeHtmlAttribute, escapeHtmlText as escapeHtml, } from "@reckona/mreact-shared/html-escape";
2
+ import { contentSecurityPolicy } from "./csp.js";
3
+ import { routeSecurityHeaders } from "./security-headers.js";
4
+ const DEFAULT_HTML_RESPONSE_HEADERS = Object.freeze({
5
+ "content-type": "text/html; charset=utf-8",
6
+ });
7
+ export function mergeRouteMetadata(metadata) {
8
+ if (metadata.length === 0) {
9
+ return undefined;
10
+ }
11
+ return metadata.reduce((merged, next) => {
12
+ const mergedMetadata = { ...merged, ...next };
13
+ const alternates = mergeObject(merged.alternates, next.alternates);
14
+ const csp = mergeCspMetadata(merged.csp, next.csp);
15
+ const head = mergeReadonlyArrays(merged.head, next.head);
16
+ const icons = mergeObject(merged.icons, next.icons);
17
+ const openGraph = mergeOpenGraphMetadata(merged.openGraph, next.openGraph);
18
+ if (alternates !== undefined) {
19
+ mergedMetadata.alternates = alternates;
20
+ }
21
+ if (csp !== undefined) {
22
+ mergedMetadata.csp = csp;
23
+ }
24
+ if (head !== undefined) {
25
+ mergedMetadata.head = head;
26
+ }
27
+ if (icons !== undefined) {
28
+ mergedMetadata.icons = icons;
29
+ }
30
+ if (openGraph !== undefined) {
31
+ mergedMetadata.openGraph = openGraph;
32
+ }
33
+ return mergedMetadata;
34
+ }, {});
35
+ }
36
+ export function applyFileConventionMetadata(metadata, routes, filename, params) {
37
+ const next = metadata === undefined ? {} : { ...metadata };
38
+ const iconRoute = routes.find((route) => route.kind === "asset" && route.convention === "icon");
39
+ const appleIconRoute = routes.find((route) => route.kind === "asset" && route.convention === "apple-icon");
40
+ const openGraphImagePath = fileConventionMetadataRoutePath(routes, filename, params, "opengraph-image");
41
+ if (iconRoute !== undefined && next.icons?.icon === undefined) {
42
+ next.icons = { ...next.icons, icon: iconRoute.path };
43
+ }
44
+ if (appleIconRoute !== undefined && next.icons?.apple === undefined) {
45
+ next.icons = { ...next.icons, apple: appleIconRoute.path };
46
+ }
47
+ if (openGraphImagePath !== undefined &&
48
+ next.openGraph?.image === undefined &&
49
+ (next.openGraph?.images === undefined || next.openGraph.images.length === 0)) {
50
+ next.openGraph = { ...next.openGraph, image: openGraphImagePath };
51
+ }
52
+ return Object.keys(next).length === 0 ? undefined : next;
53
+ }
54
+ export function injectHeadMetadata(html, metadata) {
55
+ if (metadata === undefined) {
56
+ return html;
57
+ }
58
+ let nextHtml = metadata.lang === undefined
59
+ ? html
60
+ : injectHtmlLangAttribute(html, metadataString(metadata.lang, "lang"));
61
+ const tags = [
62
+ metadata.title === undefined
63
+ ? undefined
64
+ : `<title>${escapeHtml(metadataString(metadata.title, "title"))}</title>`,
65
+ metadata.description === undefined
66
+ ? undefined
67
+ : `<meta name="description" content="${escapeHtmlAttribute(metadataString(metadata.description, "description"))}">`,
68
+ metadata.alternates?.canonical === undefined
69
+ ? undefined
70
+ : `<link rel="canonical" href="${escapeHtmlAttribute(metadataString(metadata.alternates.canonical, "alternates.canonical"))}">`,
71
+ metadata.openGraph?.title === undefined
72
+ ? undefined
73
+ : `<meta property="og:title" content="${escapeHtmlAttribute(metadataString(metadata.openGraph.title, "openGraph.title"))}">`,
74
+ metadata.openGraph?.description === undefined
75
+ ? undefined
76
+ : `<meta property="og:description" content="${escapeHtmlAttribute(metadataString(metadata.openGraph.description, "openGraph.description"))}">`,
77
+ ...openGraphImages(metadata.openGraph).map((image) => `<meta property="og:image" content="${escapeHtmlAttribute(image)}">`),
78
+ metadata.icons?.icon === undefined
79
+ ? undefined
80
+ : `<link rel="icon" href="${escapeHtmlAttribute(metadataString(metadata.icons.icon, "icons.icon"))}">`,
81
+ metadata.icons?.apple === undefined
82
+ ? undefined
83
+ : `<link rel="apple-touch-icon" href="${escapeHtmlAttribute(metadataString(metadata.icons.apple, "icons.apple"))}">`,
84
+ metadata.robots === undefined
85
+ ? undefined
86
+ : `<meta name="robots" content="${escapeHtmlAttribute(robotsContent(metadata.robots))}">`,
87
+ metadata.themeColor === undefined ? undefined : themeColorTag(metadata.themeColor),
88
+ metadata.viewport === undefined
89
+ ? undefined
90
+ : `<meta name="viewport" content="${escapeHtmlAttribute(viewportContent(metadata.viewport))}">`,
91
+ ...headDescriptorTags(metadata.head, metadata.csp?.nonce),
92
+ ]
93
+ .filter((tag) => tag !== undefined)
94
+ .join("");
95
+ if (tags === "") {
96
+ return nextHtml;
97
+ }
98
+ if (/<head(?:\s[^>]*)?>/i.test(nextHtml)) {
99
+ return nextHtml.replace(/<head(\s[^>]*)?>/i, (match) => `${match}${tags}`);
100
+ }
101
+ if (/<html(?:\s[^>]*)?>/i.test(nextHtml)) {
102
+ return nextHtml.replace(/<html(\s[^>]*)?>/i, (match) => `${match}<head>${tags}</head>`);
103
+ }
104
+ return `<head>${tags}</head>${nextHtml}`;
105
+ }
106
+ export function responseHeadersForMetadata(metadata, request, extra) {
107
+ const csp = contentSecurityPolicy(metadata?.csp);
108
+ const security = routeSecurityHeaders({
109
+ request,
110
+ security: metadata?.security,
111
+ });
112
+ if (csp === undefined && extra === undefined) {
113
+ return {
114
+ ...DEFAULT_HTML_RESPONSE_HEADERS,
115
+ ...security,
116
+ };
117
+ }
118
+ return {
119
+ ...DEFAULT_HTML_RESPONSE_HEADERS,
120
+ ...security,
121
+ ...(csp === undefined ? undefined : { "content-security-policy": csp }),
122
+ ...extra,
123
+ };
124
+ }
125
+ export function serializeRobots(manifest) {
126
+ const lines = [];
127
+ const rules = manifest.rules === undefined
128
+ ? []
129
+ : Array.isArray(manifest.rules)
130
+ ? manifest.rules
131
+ : [manifest.rules];
132
+ for (const rule of rules) {
133
+ for (const userAgent of arrayValue(rule.userAgent)) {
134
+ lines.push(`User-agent: ${userAgent}`);
135
+ }
136
+ for (const allow of arrayValue(rule.allow)) {
137
+ lines.push(`Allow: ${allow}`);
138
+ }
139
+ for (const disallow of arrayValue(rule.disallow)) {
140
+ lines.push(`Disallow: ${disallow}`);
141
+ }
142
+ }
143
+ for (const sitemap of arrayValue(manifest.sitemap)) {
144
+ lines.push(`Sitemap: ${sitemap}`);
145
+ }
146
+ if (manifest.host !== undefined) {
147
+ lines.push(`Host: ${manifest.host}`);
148
+ }
149
+ return `${lines.join("\n")}\n`;
150
+ }
151
+ export function serializeSitemap(entries) {
152
+ const urls = entries
153
+ .map((entry) => {
154
+ const fields = [
155
+ `<loc>${escapeXml(entry.url)}</loc>`,
156
+ entry.lastModified === undefined
157
+ ? undefined
158
+ : `<lastmod>${escapeXml(sitemapDate(entry.lastModified))}</lastmod>`,
159
+ entry.changeFrequency === undefined
160
+ ? undefined
161
+ : `<changefreq>${escapeXml(entry.changeFrequency)}</changefreq>`,
162
+ entry.priority === undefined ? undefined : `<priority>${entry.priority}</priority>`,
163
+ ].filter((field) => field !== undefined);
164
+ return `<url>${fields.join("")}</url>`;
165
+ })
166
+ .join("");
167
+ return `<?xml version="1.0" encoding="UTF-8"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">${urls}</urlset>`;
168
+ }
169
+ function fileConventionMetadataRoutePath(routes, filename, params, convention) {
170
+ const pageRoute = routes.find((route) => route.kind === "page" && route.file === filename);
171
+ const candidateRoutes = routes.filter((route) => (route.kind === "asset" || route.kind === "metadata") &&
172
+ route.convention === convention);
173
+ if (pageRoute !== undefined) {
174
+ const expectedPath = pageRoute.path === "/" ? `/${convention}` : `${pageRoute.path}/${convention}`;
175
+ const routeLocal = candidateRoutes.find((route) => route.path === expectedPath);
176
+ const routeLocalPath = routeLocal === undefined ? undefined : concreteRoutePath(routeLocal.path, params);
177
+ if (routeLocalPath !== undefined) {
178
+ return routeLocalPath;
179
+ }
180
+ }
181
+ return candidateRoutes.find((route) => route.path === `/${convention}`)?.path;
182
+ }
183
+ function concreteRoutePath(path, params) {
184
+ const segments = path === "/" ? [] : path.slice(1).split("/");
185
+ const concrete = [];
186
+ for (const segment of segments) {
187
+ if (segment.startsWith(":...")) {
188
+ const value = params[segment.slice(4)];
189
+ const values = Array.isArray(value)
190
+ ? value
191
+ : typeof value === "string"
192
+ ? value.split("/").filter((part) => part !== "")
193
+ : undefined;
194
+ if (values === undefined) {
195
+ return undefined;
196
+ }
197
+ concrete.push(...values.map((part) => encodeURIComponent(part)));
198
+ continue;
199
+ }
200
+ if (segment.startsWith(":")) {
201
+ const value = params[segment.slice(1)];
202
+ const stringValue = Array.isArray(value) ? value[0] : value;
203
+ if (typeof stringValue !== "string") {
204
+ return undefined;
205
+ }
206
+ concrete.push(encodeURIComponent(stringValue));
207
+ continue;
208
+ }
209
+ concrete.push(segment);
210
+ }
211
+ return `/${concrete.join("/")}`;
212
+ }
213
+ function mergeObject(left, right) {
214
+ if (left === undefined) {
215
+ return right;
216
+ }
217
+ if (right === undefined) {
218
+ return left;
219
+ }
220
+ return { ...left, ...right };
221
+ }
222
+ function mergeReadonlyArrays(left, right) {
223
+ if (left === undefined || left.length === 0) {
224
+ return right;
225
+ }
226
+ if (right === undefined || right.length === 0) {
227
+ return left;
228
+ }
229
+ return [...left, ...right];
230
+ }
231
+ function mergeCspMetadata(left, right) {
232
+ if (right?.disable === true) {
233
+ return { disable: true };
234
+ }
235
+ if (left === undefined) {
236
+ if (right === undefined) {
237
+ return undefined;
238
+ }
239
+ const merged = { ...right };
240
+ const directives = applyCspOverrides(undefined, right);
241
+ if (directives !== undefined) {
242
+ merged.directives = directives;
243
+ }
244
+ else {
245
+ delete merged.directives;
246
+ }
247
+ return merged;
248
+ }
249
+ if (right === undefined) {
250
+ return left;
251
+ }
252
+ const merged = {
253
+ ...left,
254
+ ...right,
255
+ };
256
+ const directives = applyCspOverrides(left.directives, right);
257
+ if (directives !== undefined) {
258
+ merged.directives = directives;
259
+ }
260
+ else {
261
+ delete merged.directives;
262
+ }
263
+ return merged;
264
+ }
265
+ function applyCspOverrides(left, right) {
266
+ if (right === undefined) {
267
+ return left;
268
+ }
269
+ const merged = { ...left, ...right.directives };
270
+ for (const [name, value] of Object.entries(right.replace ?? {})) {
271
+ merged[name] = value;
272
+ }
273
+ for (const name of right.remove ?? []) {
274
+ delete merged[name];
275
+ }
276
+ return Object.keys(merged).length === 0 ? undefined : merged;
277
+ }
278
+ function mergeOpenGraphMetadata(left, right) {
279
+ if (left === undefined) {
280
+ return right;
281
+ }
282
+ if (right === undefined) {
283
+ return left;
284
+ }
285
+ const merged = {
286
+ ...left,
287
+ ...right,
288
+ };
289
+ const images = mergeReadonlyArrays(openGraphImages(left), openGraphImages(right));
290
+ if (images !== undefined && images.length > 0) {
291
+ merged.images = images;
292
+ }
293
+ return merged;
294
+ }
295
+ function injectHtmlLangAttribute(html, lang) {
296
+ const escapedLang = escapeHtmlAttribute(lang);
297
+ if (!/<html(?:\s[^>]*)?>/i.test(html)) {
298
+ return html;
299
+ }
300
+ return html.replace(/<html(\s[^>]*)?>/i, (_match, attrs = "") => {
301
+ const strippedAttrs = String(attrs).replace(/\s+lang=(?:"[^"]*"|'[^']*'|[^\s>]+)/i, "");
302
+ return `<html lang="${escapedLang}"${strippedAttrs}>`;
303
+ });
304
+ }
305
+ function sitemapDate(value) {
306
+ if (value instanceof Date) {
307
+ return value.toISOString();
308
+ }
309
+ return typeof value === "number" ? new Date(value).toISOString() : value;
310
+ }
311
+ function arrayValue(value) {
312
+ if (value === undefined) {
313
+ return [];
314
+ }
315
+ return Array.isArray(value) ? value : [value];
316
+ }
317
+ function escapeXml(value) {
318
+ return value
319
+ .replaceAll("&", "&amp;")
320
+ .replaceAll("<", "&lt;")
321
+ .replaceAll(">", "&gt;")
322
+ .replaceAll('"', "&quot;");
323
+ }
324
+ function headDescriptorTags(descriptors, nonce) {
325
+ return (descriptors ?? []).flatMap((descriptor) => {
326
+ const descriptorNonce = descriptor.nonce === true ? nonce : descriptor.nonce || undefined;
327
+ const attrs = {
328
+ ...descriptor.attrs,
329
+ ...(descriptorNonce === undefined ? {} : { nonce: descriptorNonce }),
330
+ };
331
+ const attrText = Object.entries(attrs)
332
+ .flatMap(([name, value]) => {
333
+ if (value === undefined || value === false) {
334
+ return [];
335
+ }
336
+ return value === true
337
+ ? [escapeHtmlAttribute(name)]
338
+ : [`${escapeHtmlAttribute(name)}="${escapeHtmlAttribute(String(value))}"`];
339
+ })
340
+ .join(" ");
341
+ const open = attrText === "" ? `<${descriptor.tag}>` : `<${descriptor.tag} ${attrText}>`;
342
+ if (descriptor.tag === "meta" || descriptor.tag === "link" || descriptor.tag === "base") {
343
+ return [open.slice(0, -1) + ">"];
344
+ }
345
+ return [`${open}${escapeHeadTextContent(descriptor.content ?? "")}</${descriptor.tag}>`];
346
+ });
347
+ }
348
+ function escapeHeadTextContent(value) {
349
+ return value.replaceAll("<", "\\u003c");
350
+ }
351
+ function metadataString(value, path) {
352
+ if (isMetadataScalar(value)) {
353
+ return String(value);
354
+ }
355
+ throw new Error(`Invalid metadata field ${path}: expected string, number, or boolean.`);
356
+ }
357
+ function metadataKebabName(name) {
358
+ return name.replace(/[A-Z]/g, (char) => `-${char.toLowerCase()}`);
359
+ }
360
+ function viewportContent(viewport) {
361
+ if (isMetadataScalar(viewport)) {
362
+ return metadataString(viewport, "viewport");
363
+ }
364
+ return Object.entries(viewport)
365
+ .flatMap(([key, value]) => {
366
+ if (value === undefined || value === null || value === false) {
367
+ return [];
368
+ }
369
+ return [`${metadataKebabName(key)}=${metadataString(value, `viewport.${key}`)}`];
370
+ })
371
+ .join(", ");
372
+ }
373
+ function themeColorTag(themeColor) {
374
+ if (isMetadataScalar(themeColor)) {
375
+ return `<meta name="theme-color" content="${escapeHtmlAttribute(metadataString(themeColor, "themeColor"))}">`;
376
+ }
377
+ const content = themeColor.color;
378
+ if (!isMetadataScalar(content)) {
379
+ throw new Error("Invalid metadata field themeColor.color: expected string, number, or boolean.");
380
+ }
381
+ const media = themeColor.media === undefined
382
+ ? ""
383
+ : ` media="${escapeHtmlAttribute(metadataString(metadataScalarField(themeColor.media, "themeColor.media"), "themeColor.media"))}"`;
384
+ return `<meta name="theme-color"${media} content="${escapeHtmlAttribute(metadataString(content, "themeColor.color"))}">`;
385
+ }
386
+ function metadataScalarField(value, path) {
387
+ if (isMetadataScalar(value)) {
388
+ return value;
389
+ }
390
+ throw new Error(`Invalid metadata field ${path}: expected string, number, or boolean.`);
391
+ }
392
+ function isMetadataScalar(value) {
393
+ return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
394
+ }
395
+ function openGraphImages(openGraph) {
396
+ if (openGraph?.images !== undefined) {
397
+ return openGraph.images.map((image, index) => metadataImageUrl(image, `openGraph.images.${index}`));
398
+ }
399
+ return openGraph?.image === undefined ? [] : [metadataImageUrl(openGraph.image, "openGraph.image")];
400
+ }
401
+ function metadataImageUrl(value, path) {
402
+ if (isMetadataScalar(value)) {
403
+ return metadataString(value, path);
404
+ }
405
+ if (typeof value === "object" && value !== null && "url" in value) {
406
+ return metadataString(value.url, `${path}.url`);
407
+ }
408
+ throw new Error(`Invalid metadata field ${path}: expected string, number, boolean, or object with url.`);
409
+ }
410
+ function robotsContent(robots) {
411
+ if (typeof robots === "string") {
412
+ return robots;
413
+ }
414
+ return [
415
+ robots.index === false ? "noindex" : "index",
416
+ robots.follow === false ? "nofollow" : "follow",
417
+ ].join(",");
418
+ }
419
+ //# sourceMappingURL=metadata.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metadata.js","sourceRoot":"","sources":["../src/metadata.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n escapeHtmlAttribute,\n escapeHtmlText as escapeHtml,\n} from \"@reckona/mreact-shared/html-escape\";\nimport { contentSecurityPolicy } from \"./csp.js\";\nimport type { AppFileConvention } from \"./file-conventions.js\";\nimport type { AppRoute } from \"./routes.js\";\nimport { routeSecurityHeaders } from \"./security-headers.js\";\nimport type {\n MetadataImage,\n MetadataScalar,\n MetadataThemeColor,\n MetadataViewport,\n RobotsManifest,\n RouteHeadDescriptor,\n RouteMetadata,\n RouteParams,\n SitemapEntry,\n} from \"./types.js\";\n\ntype CspDirectiveMap = Record<string, readonly string[] | string>;\n\nconst DEFAULT_HTML_RESPONSE_HEADERS = Object.freeze({\n \"content-type\": \"text/html; charset=utf-8\",\n});\n\nexport function mergeRouteMetadata(metadata: readonly RouteMetadata[]): RouteMetadata | undefined {\n if (metadata.length === 0) {\n return undefined;\n }\n\n return metadata.reduce<RouteMetadata>((merged, next) => {\n const mergedMetadata: RouteMetadata = { ...merged, ...next };\n const alternates = mergeObject(merged.alternates, next.alternates);\n const csp = mergeCspMetadata(merged.csp, next.csp);\n const head = mergeReadonlyArrays(merged.head, next.head);\n const icons = mergeObject(merged.icons, next.icons);\n const openGraph = mergeOpenGraphMetadata(merged.openGraph, next.openGraph);\n\n if (alternates !== undefined) {\n mergedMetadata.alternates = alternates;\n }\n if (csp !== undefined) {\n mergedMetadata.csp = csp;\n }\n if (head !== undefined) {\n mergedMetadata.head = head;\n }\n if (icons !== undefined) {\n mergedMetadata.icons = icons;\n }\n if (openGraph !== undefined) {\n mergedMetadata.openGraph = openGraph;\n }\n\n return mergedMetadata;\n }, {});\n}\n\nexport function applyFileConventionMetadata(\n metadata: RouteMetadata | undefined,\n routes: readonly AppRoute[],\n filename: string,\n params: RouteParams,\n): RouteMetadata | undefined {\n const next: RouteMetadata = metadata === undefined ? {} : { ...metadata };\n const iconRoute = routes.find((route) => route.kind === \"asset\" && route.convention === \"icon\");\n const appleIconRoute = routes.find(\n (route) => route.kind === \"asset\" && route.convention === \"apple-icon\",\n );\n const openGraphImagePath = fileConventionMetadataRoutePath(\n routes,\n filename,\n params,\n \"opengraph-image\",\n );\n\n if (iconRoute !== undefined && next.icons?.icon === undefined) {\n next.icons = { ...next.icons, icon: iconRoute.path };\n }\n if (appleIconRoute !== undefined && next.icons?.apple === undefined) {\n next.icons = { ...next.icons, apple: appleIconRoute.path };\n }\n if (\n openGraphImagePath !== undefined &&\n next.openGraph?.image === undefined &&\n (next.openGraph?.images === undefined || next.openGraph.images.length === 0)\n ) {\n next.openGraph = { ...next.openGraph, image: openGraphImagePath };\n }\n\n return Object.keys(next).length === 0 ? undefined : next;\n}\n\nexport function injectHeadMetadata(html: string, metadata: RouteMetadata | undefined): string {\n if (metadata === undefined) {\n return html;\n }\n\n let nextHtml =\n metadata.lang === undefined\n ? html\n : injectHtmlLangAttribute(html, metadataString(metadata.lang, \"lang\"));\n const tags = [\n metadata.title === undefined\n ? undefined\n : `<title>${escapeHtml(metadataString(metadata.title, \"title\"))}</title>`,\n metadata.description === undefined\n ? undefined\n : `<meta name=\"description\" content=\"${escapeHtmlAttribute(metadataString(metadata.description, \"description\"))}\">`,\n metadata.alternates?.canonical === undefined\n ? undefined\n : `<link rel=\"canonical\" href=\"${escapeHtmlAttribute(metadataString(metadata.alternates.canonical, \"alternates.canonical\"))}\">`,\n metadata.openGraph?.title === undefined\n ? undefined\n : `<meta property=\"og:title\" content=\"${escapeHtmlAttribute(metadataString(metadata.openGraph.title, \"openGraph.title\"))}\">`,\n metadata.openGraph?.description === undefined\n ? undefined\n : `<meta property=\"og:description\" content=\"${escapeHtmlAttribute(metadataString(metadata.openGraph.description, \"openGraph.description\"))}\">`,\n ...openGraphImages(metadata.openGraph).map(\n (image) => `<meta property=\"og:image\" content=\"${escapeHtmlAttribute(image)}\">`,\n ),\n metadata.icons?.icon === undefined\n ? undefined\n : `<link rel=\"icon\" href=\"${escapeHtmlAttribute(metadataString(metadata.icons.icon, \"icons.icon\"))}\">`,\n metadata.icons?.apple === undefined\n ? undefined\n : `<link rel=\"apple-touch-icon\" href=\"${escapeHtmlAttribute(metadataString(metadata.icons.apple, \"icons.apple\"))}\">`,\n metadata.robots === undefined\n ? undefined\n : `<meta name=\"robots\" content=\"${escapeHtmlAttribute(robotsContent(metadata.robots))}\">`,\n metadata.themeColor === undefined ? undefined : themeColorTag(metadata.themeColor),\n metadata.viewport === undefined\n ? undefined\n : `<meta name=\"viewport\" content=\"${escapeHtmlAttribute(viewportContent(metadata.viewport))}\">`,\n ...headDescriptorTags(metadata.head, metadata.csp?.nonce),\n ]\n .filter((tag): tag is string => tag !== undefined)\n .join(\"\");\n\n if (tags === \"\") {\n return nextHtml;\n }\n\n if (/<head(?:\\s[^>]*)?>/i.test(nextHtml)) {\n return nextHtml.replace(/<head(\\s[^>]*)?>/i, (match) => `${match}${tags}`);\n }\n\n if (/<html(?:\\s[^>]*)?>/i.test(nextHtml)) {\n return nextHtml.replace(/<html(\\s[^>]*)?>/i, (match) => `${match}<head>${tags}</head>`);\n }\n\n return `<head>${tags}</head>${nextHtml}`;\n}\n\nexport function responseHeadersForMetadata(\n metadata: RouteMetadata | undefined,\n request: Request,\n extra?: Readonly<Record<string, string>>,\n): HeadersInit {\n const csp = contentSecurityPolicy(metadata?.csp);\n const security = routeSecurityHeaders({\n request,\n security: metadata?.security,\n });\n\n if (csp === undefined && extra === undefined) {\n return {\n ...DEFAULT_HTML_RESPONSE_HEADERS,\n ...security,\n };\n }\n\n return {\n ...DEFAULT_HTML_RESPONSE_HEADERS,\n ...security,\n ...(csp === undefined ? undefined : { \"content-security-policy\": csp }),\n ...extra,\n };\n}\n\nexport function serializeRobots(manifest: RobotsManifest): string {\n const lines: string[] = [];\n const rules =\n manifest.rules === undefined\n ? []\n : Array.isArray(manifest.rules)\n ? manifest.rules\n : [manifest.rules];\n\n for (const rule of rules) {\n for (const userAgent of arrayValue(rule.userAgent)) {\n lines.push(`User-agent: ${userAgent}`);\n }\n for (const allow of arrayValue(rule.allow)) {\n lines.push(`Allow: ${allow}`);\n }\n for (const disallow of arrayValue(rule.disallow)) {\n lines.push(`Disallow: ${disallow}`);\n }\n }\n\n for (const sitemap of arrayValue(manifest.sitemap)) {\n lines.push(`Sitemap: ${sitemap}`);\n }\n if (manifest.host !== undefined) {\n lines.push(`Host: ${manifest.host}`);\n }\n\n return `${lines.join(\"\\n\")}\\n`;\n}\n\nexport function serializeSitemap(entries: readonly SitemapEntry[]): string {\n const urls = entries\n .map((entry) => {\n const fields = [\n `<loc>${escapeXml(entry.url)}</loc>`,\n entry.lastModified === undefined\n ? undefined\n : `<lastmod>${escapeXml(sitemapDate(entry.lastModified))}</lastmod>`,\n entry.changeFrequency === undefined\n ? undefined\n : `<changefreq>${escapeXml(entry.changeFrequency)}</changefreq>`,\n entry.priority === undefined ? undefined : `<priority>${entry.priority}</priority>`,\n ].filter((field): field is string => field !== undefined);\n\n return `<url>${fields.join(\"\")}</url>`;\n })\n .join(\"\");\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?><urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">${urls}</urlset>`;\n}\n\nfunction fileConventionMetadataRoutePath(\n routes: readonly AppRoute[],\n filename: string,\n params: RouteParams,\n convention: AppFileConvention,\n): string | undefined {\n const pageRoute = routes.find((route) => route.kind === \"page\" && route.file === filename);\n const candidateRoutes = routes.filter(\n (route) =>\n (route.kind === \"asset\" || route.kind === \"metadata\") &&\n route.convention === convention,\n );\n\n if (pageRoute !== undefined) {\n const expectedPath = pageRoute.path === \"/\" ? `/${convention}` : `${pageRoute.path}/${convention}`;\n const routeLocal = candidateRoutes.find((route) => route.path === expectedPath);\n const routeLocalPath =\n routeLocal === undefined ? undefined : concreteRoutePath(routeLocal.path, params);\n\n if (routeLocalPath !== undefined) {\n return routeLocalPath;\n }\n }\n\n return candidateRoutes.find((route) => route.path === `/${convention}`)?.path;\n}\n\nfunction concreteRoutePath(path: string, params: RouteParams): string | undefined {\n const segments = path === \"/\" ? [] : path.slice(1).split(\"/\");\n const concrete: string[] = [];\n\n for (const segment of segments) {\n if (segment.startsWith(\":...\")) {\n const value = params[segment.slice(4)];\n const values = Array.isArray(value)\n ? value\n : typeof value === \"string\"\n ? value.split(\"/\").filter((part) => part !== \"\")\n : undefined;\n\n if (values === undefined) {\n return undefined;\n }\n concrete.push(...values.map((part) => encodeURIComponent(part)));\n continue;\n }\n\n if (segment.startsWith(\":\")) {\n const value = params[segment.slice(1)];\n const stringValue = Array.isArray(value) ? value[0] : value;\n\n if (typeof stringValue !== \"string\") {\n return undefined;\n }\n concrete.push(encodeURIComponent(stringValue));\n continue;\n }\n\n concrete.push(segment);\n }\n\n return `/${concrete.join(\"/\")}`;\n}\n\nfunction mergeObject<T extends object>(left: T | undefined, right: T | undefined): T | undefined {\n if (left === undefined) {\n return right;\n }\n\n if (right === undefined) {\n return left;\n }\n\n return { ...left, ...right };\n}\n\nfunction mergeReadonlyArrays<T>(\n left: readonly T[] | undefined,\n right: readonly T[] | undefined,\n): readonly T[] | undefined {\n if (left === undefined || left.length === 0) {\n return right;\n }\n\n if (right === undefined || right.length === 0) {\n return left;\n }\n\n return [...left, ...right];\n}\n\nfunction mergeCspMetadata(\n left: RouteMetadata[\"csp\"],\n right: RouteMetadata[\"csp\"],\n): RouteMetadata[\"csp\"] | undefined {\n if (right?.disable === true) {\n return { disable: true };\n }\n\n if (left === undefined) {\n if (right === undefined) {\n return undefined;\n }\n\n const merged: NonNullable<RouteMetadata[\"csp\"]> = { ...right };\n const directives = applyCspOverrides(undefined, right);\n\n if (directives !== undefined) {\n merged.directives = directives;\n } else {\n delete merged.directives;\n }\n\n return merged;\n }\n\n if (right === undefined) {\n return left;\n }\n\n const merged: NonNullable<RouteMetadata[\"csp\"]> = {\n ...left,\n ...right,\n };\n const directives = applyCspOverrides(left.directives, right);\n\n if (directives !== undefined) {\n merged.directives = directives;\n } else {\n delete merged.directives;\n }\n\n return merged;\n}\n\nfunction applyCspOverrides(\n left: CspDirectiveMap | undefined,\n right: RouteMetadata[\"csp\"] | undefined,\n): CspDirectiveMap | undefined {\n if (right === undefined) {\n return left;\n }\n\n const merged = { ...left, ...right.directives };\n\n for (const [name, value] of Object.entries(right.replace ?? {})) {\n merged[name] = value;\n }\n\n for (const name of right.remove ?? []) {\n delete merged[name];\n }\n\n return Object.keys(merged).length === 0 ? undefined : merged;\n}\n\nfunction mergeOpenGraphMetadata(\n left: RouteMetadata[\"openGraph\"],\n right: RouteMetadata[\"openGraph\"],\n): RouteMetadata[\"openGraph\"] | undefined {\n if (left === undefined) {\n return right;\n }\n\n if (right === undefined) {\n return left;\n }\n\n const merged: NonNullable<RouteMetadata[\"openGraph\"]> = {\n ...left,\n ...right,\n };\n const images = mergeReadonlyArrays(openGraphImages(left), openGraphImages(right));\n\n if (images !== undefined && images.length > 0) {\n merged.images = images;\n }\n\n return merged;\n}\n\nfunction injectHtmlLangAttribute(html: string, lang: string): string {\n const escapedLang = escapeHtmlAttribute(lang);\n\n if (!/<html(?:\\s[^>]*)?>/i.test(html)) {\n return html;\n }\n\n return html.replace(/<html(\\s[^>]*)?>/i, (_match, attrs = \"\") => {\n const strippedAttrs = String(attrs).replace(/\\s+lang=(?:\"[^\"]*\"|'[^']*'|[^\\s>]+)/i, \"\");\n return `<html lang=\"${escapedLang}\"${strippedAttrs}>`;\n });\n}\n\nfunction sitemapDate(value: Date | number | string): string {\n if (value instanceof Date) {\n return value.toISOString();\n }\n\n return typeof value === \"number\" ? new Date(value).toISOString() : value;\n}\n\nfunction arrayValue<T>(value: T | readonly T[] | undefined): readonly T[] {\n if (value === undefined) {\n return [];\n }\n\n return Array.isArray(value) ? (value as readonly T[]) : [value as T];\n}\n\nfunction escapeXml(value: string): string {\n return value\n .replaceAll(\"&\", \"&amp;\")\n .replaceAll(\"<\", \"&lt;\")\n .replaceAll(\">\", \"&gt;\")\n .replaceAll('\"', \"&quot;\");\n}\n\nfunction headDescriptorTags(\n descriptors: readonly RouteHeadDescriptor[] | undefined,\n nonce: string | undefined,\n): string[] {\n return (descriptors ?? []).flatMap((descriptor) => {\n const descriptorNonce = descriptor.nonce === true ? nonce : descriptor.nonce || undefined;\n const attrs: Record<string, boolean | number | string | undefined> = {\n ...descriptor.attrs,\n ...(descriptorNonce === undefined ? {} : { nonce: descriptorNonce }),\n };\n const attrText = Object.entries(attrs)\n .flatMap(([name, value]) => {\n if (value === undefined || value === false) {\n return [];\n }\n\n return value === true\n ? [escapeHtmlAttribute(name)]\n : [`${escapeHtmlAttribute(name)}=\"${escapeHtmlAttribute(String(value))}\"`];\n })\n .join(\" \");\n const open = attrText === \"\" ? `<${descriptor.tag}>` : `<${descriptor.tag} ${attrText}>`;\n\n if (descriptor.tag === \"meta\" || descriptor.tag === \"link\" || descriptor.tag === \"base\") {\n return [open.slice(0, -1) + \">\"];\n }\n\n return [`${open}${escapeHeadTextContent(descriptor.content ?? \"\")}</${descriptor.tag}>`];\n });\n}\n\nfunction escapeHeadTextContent(value: string): string {\n return value.replaceAll(\"<\", \"\\\\u003c\");\n}\n\nfunction metadataString(value: MetadataScalar, path: string): string {\n if (isMetadataScalar(value)) {\n return String(value);\n }\n\n throw new Error(`Invalid metadata field ${path}: expected string, number, or boolean.`);\n}\n\nfunction metadataKebabName(name: string): string {\n return name.replace(/[A-Z]/g, (char) => `-${char.toLowerCase()}`);\n}\n\nfunction viewportContent(viewport: MetadataScalar | MetadataViewport): string {\n if (isMetadataScalar(viewport)) {\n return metadataString(viewport, \"viewport\");\n }\n\n return Object.entries(viewport)\n .flatMap(([key, value]) => {\n if (value === undefined || value === null || value === false) {\n return [];\n }\n\n return [`${metadataKebabName(key)}=${metadataString(value, `viewport.${key}`)}`];\n })\n .join(\", \");\n}\n\nfunction themeColorTag(themeColor: MetadataScalar | MetadataThemeColor): string {\n if (isMetadataScalar(themeColor)) {\n return `<meta name=\"theme-color\" content=\"${escapeHtmlAttribute(metadataString(themeColor, \"themeColor\"))}\">`;\n }\n\n const content = themeColor.color;\n if (!isMetadataScalar(content)) {\n throw new Error(\n \"Invalid metadata field themeColor.color: expected string, number, or boolean.\",\n );\n }\n\n const media =\n themeColor.media === undefined\n ? \"\"\n : ` media=\"${escapeHtmlAttribute(metadataString(metadataScalarField(themeColor.media, \"themeColor.media\"), \"themeColor.media\"))}\"`;\n\n return `<meta name=\"theme-color\"${media} content=\"${escapeHtmlAttribute(metadataString(content, \"themeColor.color\"))}\">`;\n}\n\nfunction metadataScalarField(value: unknown, path: string): MetadataScalar {\n if (isMetadataScalar(value)) {\n return value;\n }\n\n throw new Error(`Invalid metadata field ${path}: expected string, number, or boolean.`);\n}\n\nfunction isMetadataScalar(value: unknown): value is MetadataScalar {\n return typeof value === \"string\" || typeof value === \"number\" || typeof value === \"boolean\";\n}\n\nfunction openGraphImages(openGraph: RouteMetadata[\"openGraph\"]): readonly string[] {\n if (openGraph?.images !== undefined) {\n return openGraph.images.map((image, index) =>\n metadataImageUrl(image, `openGraph.images.${index}`),\n );\n }\n\n return openGraph?.image === undefined ? [] : [metadataImageUrl(openGraph.image, \"openGraph.image\")];\n}\n\nfunction metadataImageUrl(value: MetadataImage | MetadataScalar, path: string): string {\n if (isMetadataScalar(value)) {\n return metadataString(value, path);\n }\n\n if (typeof value === \"object\" && value !== null && \"url\" in value) {\n return metadataString(value.url, `${path}.url`);\n }\n\n throw new Error(`Invalid metadata field ${path}: expected string, number, boolean, or object with url.`);\n}\n\nfunction robotsContent(robots: NonNullable<RouteMetadata[\"robots\"]>): string {\n if (typeof robots === \"string\") {\n return robots;\n }\n\n return [\n robots.index === false ? \"noindex\" : \"index\",\n robots.follow === false ? \"nofollow\" : \"follow\",\n ].join(\",\");\n}\n"]}