@reckona/mreact-router 0.0.202 → 0.0.204
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -11
- package/dist/actions.d.ts +8 -3
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +125 -45
- package/dist/actions.js.map +1 -1
- package/dist/adapters/aws-lambda.d.ts +15 -4
- package/dist/adapters/aws-lambda.d.ts.map +1 -1
- package/dist/adapters/aws-lambda.js +301 -63
- package/dist/adapters/aws-lambda.js.map +1 -1
- package/dist/adapters/cloudflare.d.ts +5 -2
- package/dist/adapters/cloudflare.d.ts.map +1 -1
- package/dist/adapters/cloudflare.js +35 -11
- package/dist/adapters/cloudflare.js.map +1 -1
- package/dist/adapters/edge.d.ts +1 -1
- package/dist/adapters/edge.d.ts.map +1 -1
- package/dist/adapters/edge.js.map +1 -1
- package/dist/adapters/node.d.ts +5 -3
- package/dist/adapters/node.d.ts.map +1 -1
- package/dist/adapters/node.js +2 -1
- package/dist/adapters/node.js.map +1 -1
- package/dist/adapters/static.d.ts.map +1 -1
- package/dist/adapters/static.js +24 -2
- package/dist/adapters/static.js.map +1 -1
- package/dist/build.d.ts +4 -1
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +185 -13
- package/dist/build.js.map +1 -1
- package/dist/built-runtime.d.ts +2 -0
- package/dist/built-runtime.d.ts.map +1 -1
- package/dist/built-runtime.js +16 -0
- package/dist/built-runtime.js.map +1 -1
- package/dist/cache.d.ts +4 -2
- package/dist/cache.d.ts.map +1 -1
- package/dist/cache.js +216 -17
- package/dist/cache.js.map +1 -1
- package/dist/cli-options.d.ts +3 -0
- package/dist/cli-options.d.ts.map +1 -1
- package/dist/cli-options.js +10 -2
- package/dist/cli-options.js.map +1 -1
- package/dist/cli.js +14 -3
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts +5 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +43 -3
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +3 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +21 -2
- package/dist/config.js.map +1 -1
- package/dist/csp.d.ts +1 -0
- package/dist/csp.d.ts.map +1 -1
- package/dist/csp.js +9 -0
- package/dist/csp.js.map +1 -1
- package/dist/dehydrate-policy.d.ts +3 -0
- package/dist/dehydrate-policy.d.ts.map +1 -0
- package/dist/dehydrate-policy.js +16 -0
- package/dist/dehydrate-policy.js.map +1 -0
- package/dist/dev-server.d.ts +7 -2
- package/dist/dev-server.d.ts.map +1 -1
- package/dist/dev-server.js +120 -11
- package/dist/dev-server.js.map +1 -1
- package/dist/http.d.ts +1 -1
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +147 -7
- package/dist/http.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +22 -1
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +22 -8
- package/dist/logger.js.map +1 -1
- package/dist/metadata.d.ts +8 -0
- package/dist/metadata.d.ts.map +1 -1
- package/dist/metadata.js +60 -22
- package/dist/metadata.js.map +1 -1
- package/dist/navigation-marker.d.ts +2 -0
- package/dist/navigation-marker.d.ts.map +1 -0
- package/dist/navigation-marker.js +357 -0
- package/dist/navigation-marker.js.map +1 -0
- package/dist/navigation-runtime.d.ts +1 -1
- package/dist/navigation-runtime.d.ts.map +1 -1
- package/dist/navigation-runtime.js +1 -1
- package/dist/navigation-runtime.js.map +1 -1
- package/dist/navigation.d.ts +1 -0
- package/dist/navigation.d.ts.map +1 -1
- package/dist/navigation.js +1 -1
- package/dist/navigation.js.map +1 -1
- package/dist/node-server.d.ts +5 -2
- package/dist/node-server.d.ts.map +1 -1
- package/dist/node-server.js +75 -6
- package/dist/node-server.js.map +1 -1
- package/dist/prerender-entry.d.ts +17 -1
- package/dist/prerender-entry.d.ts.map +1 -1
- package/dist/prerender-entry.js +98 -3
- package/dist/prerender-entry.js.map +1 -1
- package/dist/render.d.ts +16 -2
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +142 -135
- package/dist/render.js.map +1 -1
- package/dist/serve.d.ts +11 -2
- package/dist/serve.d.ts.map +1 -1
- package/dist/serve.js +67 -41
- package/dist/serve.js.map +1 -1
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +52 -19
- package/dist/session.js.map +1 -1
- package/dist/upgrade.d.ts +59 -2
- package/dist/upgrade.d.ts.map +1 -1
- package/dist/upgrade.js +282 -1
- package/dist/upgrade.js.map +1 -1
- package/dist/vite-config.d.ts +2 -0
- package/dist/vite-config.d.ts.map +1 -1
- package/dist/vite-config.js +2 -0
- package/dist/vite-config.js.map +1 -1
- package/dist/vite.d.ts +4 -1
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +117 -6
- package/dist/vite.js.map +1 -1
- package/package.json +11 -11
- package/src/actions.ts +146 -60
- package/src/adapters/aws-lambda.ts +426 -115
- package/src/adapters/cloudflare.ts +58 -12
- package/src/adapters/edge.ts +2 -0
- package/src/adapters/node.ts +8 -10
- package/src/adapters/static.ts +31 -2
- package/src/build.ts +217 -13
- package/src/built-runtime.ts +28 -0
- package/src/cache.ts +272 -19
- package/src/cli-options.ts +15 -2
- package/src/cli.ts +16 -2
- package/src/client.ts +47 -3
- package/src/config.ts +43 -6
- package/src/csp.ts +11 -0
- package/src/dehydrate-policy.ts +23 -0
- package/src/dev-server.ts +188 -15
- package/src/http.ts +172 -6
- package/src/index.ts +14 -1
- package/src/logger.ts +47 -10
- package/src/metadata.ts +125 -60
- package/src/navigation-marker.ts +423 -0
- package/src/navigation-runtime.ts +1 -0
- package/src/navigation.ts +1 -1
- package/src/node-server.ts +128 -22
- package/src/prerender-entry.ts +127 -5
- package/src/render.ts +859 -749
- package/src/serve.ts +112 -82
- package/src/session.ts +56 -25
- package/src/upgrade.ts +402 -3
- package/src/vite-config.ts +6 -0
- package/src/vite.ts +157 -5
package/src/logger.ts
CHANGED
|
@@ -27,7 +27,9 @@ export type AppRouterLogEvent =
|
|
|
27
27
|
| AppRouterRequestErrorLogEvent
|
|
28
28
|
| AppRouterRequestTimingLogEvent
|
|
29
29
|
| AppRouterRenderTimingLogEvent
|
|
30
|
-
| AppRouterCspInlineNonceWarningLogEvent
|
|
30
|
+
| AppRouterCspInlineNonceWarningLogEvent
|
|
31
|
+
| AppRouterUpgradeErrorLogEvent
|
|
32
|
+
| AppRouterUpgradeRejectedLogEvent;
|
|
31
33
|
|
|
32
34
|
/**
|
|
33
35
|
* Logs the start of an app-router request.
|
|
@@ -63,6 +65,29 @@ export interface AppRouterRequestErrorLogEvent {
|
|
|
63
65
|
type: "router:request:error";
|
|
64
66
|
}
|
|
65
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Logs a failed or timed-out HTTP upgrade handler.
|
|
70
|
+
*/
|
|
71
|
+
export interface AppRouterUpgradeErrorLogEvent {
|
|
72
|
+
durationMs: number;
|
|
73
|
+
error: AppRouterLogError;
|
|
74
|
+
method: string;
|
|
75
|
+
path: string;
|
|
76
|
+
runtime: "node";
|
|
77
|
+
type: "router:upgrade:error";
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Logs an HTTP upgrade rejected before application ownership.
|
|
82
|
+
*/
|
|
83
|
+
export interface AppRouterUpgradeRejectedLogEvent {
|
|
84
|
+
method: string;
|
|
85
|
+
path: string;
|
|
86
|
+
reason: "disallowed-origin" | "malformed-origin" | "missing-origin" | "opaque-origin";
|
|
87
|
+
runtime: "node";
|
|
88
|
+
type: "router:upgrade:rejected";
|
|
89
|
+
}
|
|
90
|
+
|
|
66
91
|
/**
|
|
67
92
|
* Logs per-phase timings for a complete app-router request.
|
|
68
93
|
*/
|
|
@@ -116,7 +141,12 @@ export function emitRouterLog(
|
|
|
116
141
|
level: AppRouterLogLevel,
|
|
117
142
|
event: AppRouterLogEvent,
|
|
118
143
|
): void {
|
|
119
|
-
|
|
144
|
+
let sink: AppRouterLogger[AppRouterLogLevel];
|
|
145
|
+
try {
|
|
146
|
+
sink = logger?.[level];
|
|
147
|
+
} catch {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
120
150
|
|
|
121
151
|
if (sink === undefined) {
|
|
122
152
|
return;
|
|
@@ -176,15 +206,22 @@ export function logDurationMs(startedAt: number): number {
|
|
|
176
206
|
}
|
|
177
207
|
|
|
178
208
|
export function logError(error: unknown): AppRouterLogError {
|
|
179
|
-
|
|
209
|
+
try {
|
|
210
|
+
if (error instanceof Error) {
|
|
211
|
+
return {
|
|
212
|
+
message: String(error.message),
|
|
213
|
+
name: String(error.name),
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
180
217
|
return {
|
|
181
|
-
message: error
|
|
182
|
-
name:
|
|
218
|
+
message: String(error),
|
|
219
|
+
name: "Error",
|
|
220
|
+
};
|
|
221
|
+
} catch {
|
|
222
|
+
return {
|
|
223
|
+
message: "Unknown non-coercible error",
|
|
224
|
+
name: "Error",
|
|
183
225
|
};
|
|
184
226
|
}
|
|
185
|
-
|
|
186
|
-
return {
|
|
187
|
-
message: String(error),
|
|
188
|
-
name: "Error",
|
|
189
|
-
};
|
|
190
227
|
}
|
package/src/metadata.ts
CHANGED
|
@@ -4,10 +4,10 @@ import {
|
|
|
4
4
|
} from "@reckona/mreact-shared/html-escape";
|
|
5
5
|
import {
|
|
6
6
|
isDangerousHtmlAttribute,
|
|
7
|
+
isUnsafeMetaRefreshContent,
|
|
7
8
|
isUnsafeUrlAttribute,
|
|
8
9
|
} from "@reckona/mreact-shared/url-safety";
|
|
9
10
|
import { contentSecurityPolicy } from "./csp.js";
|
|
10
|
-
import type { AppFileConvention } from "./file-conventions.js";
|
|
11
11
|
import type { AppRoute } from "./routes.js";
|
|
12
12
|
import { routeSecurityHeaders } from "./security-headers.js";
|
|
13
13
|
import type {
|
|
@@ -28,6 +28,13 @@ const DEFAULT_HTML_RESPONSE_HEADERS = Object.freeze({
|
|
|
28
28
|
"content-type": "text/html; charset=utf-8",
|
|
29
29
|
});
|
|
30
30
|
|
|
31
|
+
export interface FileConventionMetadataTemplate {
|
|
32
|
+
appleIconPath?: string | undefined;
|
|
33
|
+
iconPath?: string | undefined;
|
|
34
|
+
openGraphImageFallbackPath?: string | undefined;
|
|
35
|
+
openGraphImageRoutePath?: string | undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
31
38
|
export function mergeRouteMetadata(metadata: readonly RouteMetadata[]): RouteMetadata | undefined {
|
|
32
39
|
if (metadata.length === 0) {
|
|
33
40
|
return undefined;
|
|
@@ -67,23 +74,64 @@ export function applyFileConventionMetadata(
|
|
|
67
74
|
filename: string,
|
|
68
75
|
params: RouteParams,
|
|
69
76
|
): RouteMetadata | undefined {
|
|
70
|
-
|
|
77
|
+
return applyFileConventionMetadataTemplate(
|
|
78
|
+
metadata,
|
|
79
|
+
fileConventionMetadataTemplate(routes, filename),
|
|
80
|
+
params,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function fileConventionMetadataTemplate(
|
|
85
|
+
routes: readonly AppRoute[],
|
|
86
|
+
filename: string,
|
|
87
|
+
): FileConventionMetadataTemplate {
|
|
88
|
+
const pageRoute = routes.find((route) => route.kind === "page" && route.file === filename);
|
|
71
89
|
const iconRoute = routes.find((route) => route.kind === "asset" && route.convention === "icon");
|
|
72
90
|
const appleIconRoute = routes.find(
|
|
73
91
|
(route) => route.kind === "asset" && route.convention === "apple-icon",
|
|
74
92
|
);
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
"opengraph-image",
|
|
93
|
+
const openGraphImageRoutes = routes.filter(
|
|
94
|
+
(route) =>
|
|
95
|
+
(route.kind === "asset" || route.kind === "metadata") &&
|
|
96
|
+
route.convention === "opengraph-image",
|
|
80
97
|
);
|
|
98
|
+
const expectedOpenGraphImagePath =
|
|
99
|
+
pageRoute === undefined
|
|
100
|
+
? undefined
|
|
101
|
+
: pageRoute.path === "/"
|
|
102
|
+
? "/opengraph-image"
|
|
103
|
+
: `${pageRoute.path}/opengraph-image`;
|
|
81
104
|
|
|
82
|
-
|
|
83
|
-
|
|
105
|
+
return {
|
|
106
|
+
...(appleIconRoute === undefined ? {} : { appleIconPath: appleIconRoute.path }),
|
|
107
|
+
...(iconRoute === undefined ? {} : { iconPath: iconRoute.path }),
|
|
108
|
+
...(openGraphImageRoutes.find((route) => route.path === "/opengraph-image") === undefined
|
|
109
|
+
? {}
|
|
110
|
+
: { openGraphImageFallbackPath: "/opengraph-image" }),
|
|
111
|
+
...(expectedOpenGraphImagePath === undefined ||
|
|
112
|
+
openGraphImageRoutes.find((route) => route.path === expectedOpenGraphImagePath) === undefined
|
|
113
|
+
? {}
|
|
114
|
+
: { openGraphImageRoutePath: expectedOpenGraphImagePath }),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function applyFileConventionMetadataTemplate(
|
|
119
|
+
metadata: RouteMetadata | undefined,
|
|
120
|
+
template: FileConventionMetadataTemplate,
|
|
121
|
+
params: RouteParams,
|
|
122
|
+
): RouteMetadata | undefined {
|
|
123
|
+
const next: RouteMetadata = metadata === undefined ? {} : { ...metadata };
|
|
124
|
+
const openGraphImagePath =
|
|
125
|
+
(template.openGraphImageRoutePath === undefined
|
|
126
|
+
? undefined
|
|
127
|
+
: concreteRoutePath(template.openGraphImageRoutePath, params)) ??
|
|
128
|
+
template.openGraphImageFallbackPath;
|
|
129
|
+
|
|
130
|
+
if (template.iconPath !== undefined && next.icons?.icon === undefined) {
|
|
131
|
+
next.icons = { ...next.icons, icon: template.iconPath };
|
|
84
132
|
}
|
|
85
|
-
if (
|
|
86
|
-
next.icons = { ...next.icons, apple:
|
|
133
|
+
if (template.appleIconPath !== undefined && next.icons?.apple === undefined) {
|
|
134
|
+
next.icons = { ...next.icons, apple: template.appleIconPath };
|
|
87
135
|
}
|
|
88
136
|
if (
|
|
89
137
|
openGraphImagePath !== undefined &&
|
|
@@ -268,33 +316,6 @@ export function serializeSitemap(entries: readonly SitemapEntry[]): string {
|
|
|
268
316
|
return `<?xml version="1.0" encoding="UTF-8"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">${urls}</urlset>`;
|
|
269
317
|
}
|
|
270
318
|
|
|
271
|
-
function fileConventionMetadataRoutePath(
|
|
272
|
-
routes: readonly AppRoute[],
|
|
273
|
-
filename: string,
|
|
274
|
-
params: RouteParams,
|
|
275
|
-
convention: AppFileConvention,
|
|
276
|
-
): string | undefined {
|
|
277
|
-
const pageRoute = routes.find((route) => route.kind === "page" && route.file === filename);
|
|
278
|
-
const candidateRoutes = routes.filter(
|
|
279
|
-
(route) =>
|
|
280
|
-
(route.kind === "asset" || route.kind === "metadata") &&
|
|
281
|
-
route.convention === convention,
|
|
282
|
-
);
|
|
283
|
-
|
|
284
|
-
if (pageRoute !== undefined) {
|
|
285
|
-
const expectedPath = pageRoute.path === "/" ? `/${convention}` : `${pageRoute.path}/${convention}`;
|
|
286
|
-
const routeLocal = candidateRoutes.find((route) => route.path === expectedPath);
|
|
287
|
-
const routeLocalPath =
|
|
288
|
-
routeLocal === undefined ? undefined : concreteRoutePath(routeLocal.path, params);
|
|
289
|
-
|
|
290
|
-
if (routeLocalPath !== undefined) {
|
|
291
|
-
return routeLocalPath;
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
return candidateRoutes.find((route) => route.path === `/${convention}`)?.path;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
319
|
function concreteRoutePath(path: string, params: RouteParams): string | undefined {
|
|
299
320
|
const segments = path === "/" ? [] : path.slice(1).split("/");
|
|
300
321
|
const concrete: string[] = [];
|
|
@@ -695,7 +716,9 @@ function validateOptionalHeadMetadata(value: unknown, path: string): void {
|
|
|
695
716
|
typeof head.nonce !== "boolean" &&
|
|
696
717
|
typeof head.nonce !== "string"
|
|
697
718
|
) {
|
|
698
|
-
throw new Error(
|
|
719
|
+
throw new Error(
|
|
720
|
+
`Invalid metadata field ${descriptorPath}.nonce: expected string or boolean.`,
|
|
721
|
+
);
|
|
699
722
|
}
|
|
700
723
|
if (head.attrs !== undefined) {
|
|
701
724
|
assertPlainMetadataObject(head.attrs, `${descriptorPath}.attrs`);
|
|
@@ -713,6 +736,7 @@ function validateOptionalHeadMetadata(value: unknown, path: string): void {
|
|
|
713
736
|
}
|
|
714
737
|
}
|
|
715
738
|
}
|
|
739
|
+
validateMetaRefreshHeadDescriptor(head, descriptorPath);
|
|
716
740
|
validateUnknownJsonMetadataFields(
|
|
717
741
|
descriptor as Record<string, unknown>,
|
|
718
742
|
descriptorPath,
|
|
@@ -721,6 +745,36 @@ function validateOptionalHeadMetadata(value: unknown, path: string): void {
|
|
|
721
745
|
});
|
|
722
746
|
}
|
|
723
747
|
|
|
748
|
+
function validateMetaRefreshHeadDescriptor(head: RouteHeadDescriptor, path: string): void {
|
|
749
|
+
if (head.tag !== "meta" || head.attrs === undefined) {
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
const attrs = Object.entries(head.attrs);
|
|
754
|
+
const hasRefresh = attrs.some(([name, value]) => {
|
|
755
|
+
const canonicalName = name.toLowerCase();
|
|
756
|
+
return (
|
|
757
|
+
(canonicalName === "http-equiv" || canonicalName === "httpequiv") &&
|
|
758
|
+
typeof value === "string" &&
|
|
759
|
+
value.toLowerCase() === "refresh"
|
|
760
|
+
);
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
if (!hasRefresh) {
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
for (const [name, value] of attrs) {
|
|
768
|
+
if (
|
|
769
|
+
name.toLowerCase() === "content" &&
|
|
770
|
+
typeof value === "string" &&
|
|
771
|
+
isUnsafeMetaRefreshContent("refresh", value)
|
|
772
|
+
) {
|
|
773
|
+
throw new Error(`Invalid metadata field ${path}.attrs.${name}: unsafe refresh URL value.`);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
|
|
724
778
|
function validateHeadAttribute(name: string, value: unknown, path: string): void {
|
|
725
779
|
if (!isSafeHeadAttributeName(name)) {
|
|
726
780
|
throw new Error(`Invalid metadata field ${path}: expected safe HTML attribute name.`);
|
|
@@ -728,7 +782,9 @@ function validateHeadAttribute(name: string, value: unknown, path: string): void
|
|
|
728
782
|
|
|
729
783
|
const canonicalName = name.toLowerCase();
|
|
730
784
|
if (canonicalName.startsWith("on") || isDangerousHtmlAttribute(canonicalName)) {
|
|
731
|
-
throw new Error(
|
|
785
|
+
throw new Error(
|
|
786
|
+
`Invalid metadata field ${path}: event and dangerous attributes are not allowed.`,
|
|
787
|
+
);
|
|
732
788
|
}
|
|
733
789
|
|
|
734
790
|
if (typeof value === "string" && isUnsafeUrlAttribute(canonicalName, value)) {
|
|
@@ -866,20 +922,24 @@ function validateOptionalViewportMetadata(value: unknown, path: string): void {
|
|
|
866
922
|
}
|
|
867
923
|
|
|
868
924
|
function validateUnknownMetadataFields(metadata: RouteMetadata, path: string): void {
|
|
869
|
-
validateUnknownJsonMetadataFields(
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
925
|
+
validateUnknownJsonMetadataFields(
|
|
926
|
+
metadata as Record<string, unknown>,
|
|
927
|
+
path,
|
|
928
|
+
new Set([
|
|
929
|
+
"alternates",
|
|
930
|
+
"csp",
|
|
931
|
+
"description",
|
|
932
|
+
"head",
|
|
933
|
+
"icons",
|
|
934
|
+
"lang",
|
|
935
|
+
"openGraph",
|
|
936
|
+
"robots",
|
|
937
|
+
"security",
|
|
938
|
+
"themeColor",
|
|
939
|
+
"title",
|
|
940
|
+
"viewport",
|
|
941
|
+
]),
|
|
942
|
+
);
|
|
883
943
|
}
|
|
884
944
|
|
|
885
945
|
function validateUnknownJsonMetadataFields(
|
|
@@ -911,12 +971,13 @@ function validateJsonSerializableMetadata(value: unknown, path: string): void {
|
|
|
911
971
|
return;
|
|
912
972
|
}
|
|
913
973
|
|
|
914
|
-
throw new Error(
|
|
915
|
-
`Invalid metadata field ${path}: expected a JSON-serializable value.`,
|
|
916
|
-
);
|
|
974
|
+
throw new Error(`Invalid metadata field ${path}: expected a JSON-serializable value.`);
|
|
917
975
|
}
|
|
918
976
|
|
|
919
|
-
function assertPlainMetadataObject(
|
|
977
|
+
function assertPlainMetadataObject(
|
|
978
|
+
value: unknown,
|
|
979
|
+
path: string,
|
|
980
|
+
): asserts value is Record<string, unknown> {
|
|
920
981
|
if (!isPlainMetadataObject(value)) {
|
|
921
982
|
throw new Error(`Invalid metadata field ${path}: expected object.`);
|
|
922
983
|
}
|
|
@@ -938,7 +999,9 @@ function openGraphImages(openGraph: RouteMetadata["openGraph"]): readonly string
|
|
|
938
999
|
);
|
|
939
1000
|
}
|
|
940
1001
|
|
|
941
|
-
return openGraph?.image === undefined
|
|
1002
|
+
return openGraph?.image === undefined
|
|
1003
|
+
? []
|
|
1004
|
+
: [metadataImageUrl(openGraph.image, "openGraph.image")];
|
|
942
1005
|
}
|
|
943
1006
|
|
|
944
1007
|
function metadataImageUrl(value: MetadataImage | MetadataScalar, path: string): string {
|
|
@@ -950,7 +1013,9 @@ function metadataImageUrl(value: MetadataImage | MetadataScalar, path: string):
|
|
|
950
1013
|
return metadataString(value.url, `${path}.url`);
|
|
951
1014
|
}
|
|
952
1015
|
|
|
953
|
-
throw new Error(
|
|
1016
|
+
throw new Error(
|
|
1017
|
+
`Invalid metadata field ${path}: expected string, number, boolean, or object with url.`,
|
|
1018
|
+
);
|
|
954
1019
|
}
|
|
955
1020
|
|
|
956
1021
|
function robotsContent(robots: NonNullable<RouteMetadata["robots"]>): string {
|