@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.
- package/dist/adapters/aws-lambda.d.ts.map +1 -1
- package/dist/adapters/aws-lambda.js +17 -22
- package/dist/adapters/aws-lambda.js.map +1 -1
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +3 -1
- package/dist/build.js.map +1 -1
- package/dist/client-route-inference.d.ts +2 -0
- package/dist/client-route-inference.d.ts.map +1 -0
- package/dist/client-route-inference.js +2 -0
- package/dist/client-route-inference.js.map +1 -0
- package/dist/client.d.ts +2 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +25 -19
- package/dist/client.js.map +1 -1
- package/dist/layout-composer.d.ts +24 -0
- package/dist/layout-composer.d.ts.map +1 -0
- package/dist/layout-composer.js +83 -0
- package/dist/layout-composer.js.map +1 -0
- package/dist/metadata.d.ts +9 -0
- package/dist/metadata.d.ts.map +1 -0
- package/dist/metadata.js +419 -0
- package/dist/metadata.js.map +1 -0
- package/dist/middleware.d.ts +22 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +94 -0
- package/dist/middleware.js.map +1 -0
- package/dist/module-runner.d.ts +2 -2
- package/dist/module-runner.d.ts.map +1 -1
- package/dist/module-runner.js +1 -1
- package/dist/module-runner.js.map +1 -1
- package/dist/navigation-runtime.d.ts +2 -0
- package/dist/navigation-runtime.d.ts.map +1 -0
- package/dist/navigation-runtime.js +2 -0
- package/dist/navigation-runtime.js.map +1 -0
- package/dist/preload-policy.d.ts +24 -0
- package/dist/preload-policy.d.ts.map +1 -0
- package/dist/preload-policy.js +45 -0
- package/dist/preload-policy.js.map +1 -0
- package/dist/render.d.ts +1 -3
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +32 -676
- package/dist/render.js.map +1 -1
- package/dist/route-hydration-contract.d.ts +11 -0
- package/dist/route-hydration-contract.d.ts.map +1 -0
- package/dist/route-hydration-contract.js +21 -0
- package/dist/route-hydration-contract.js.map +1 -0
- package/dist/route-module-loader.d.ts +10 -0
- package/dist/route-module-loader.d.ts.map +1 -0
- package/dist/route-module-loader.js +61 -0
- package/dist/route-module-loader.js.map +1 -0
- package/dist/route-source.d.ts.map +1 -1
- package/dist/route-source.js +7 -5
- package/dist/route-source.js.map +1 -1
- package/dist/routes.js +6 -0
- package/dist/routes.js.map +1 -1
- package/dist/serve.d.ts +13 -0
- package/dist/serve.d.ts.map +1 -1
- package/dist/serve.js +63 -28
- package/dist/serve.js.map +1 -1
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +2 -1
- package/dist/vite.js.map +1 -1
- package/package.json +11 -11
- package/src/adapters/aws-lambda.ts +21 -28
- package/src/build.ts +9 -6
- package/src/client-route-inference.ts +18 -0
- package/src/client.ts +32 -20
- package/src/layout-composer.ts +142 -0
- package/src/metadata.ts +578 -0
- package/src/middleware.ts +153 -0
- package/src/module-runner.ts +3 -2
- package/src/navigation-runtime.ts +16 -0
- package/src/preload-policy.ts +89 -0
- package/src/render.ts +75 -986
- package/src/route-hydration-contract.ts +22 -0
- package/src/route-module-loader.ts +95 -0
- package/src/route-source.ts +9 -4
- package/src/routes.ts +8 -0
- package/src/serve.ts +110 -29
- 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"}
|
package/dist/metadata.js
ADDED
|
@@ -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("&", "&")
|
|
320
|
+
.replaceAll("<", "<")
|
|
321
|
+
.replaceAll(">", ">")
|
|
322
|
+
.replaceAll('"', """);
|
|
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(\"&\", \"&\")\n .replaceAll(\"<\", \"<\")\n .replaceAll(\">\", \">\")\n .replaceAll('\"', \""\");\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"]}
|