@aotter/mantle-cloudflare 0.1.0-alpha.1 → 0.1.0-alpha.3
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 +12 -3
- package/dist/auth/createAuth.d.ts +22 -10
- package/dist/auth/createAuth.d.ts.map +1 -1
- package/dist/auth/createAuth.js +69 -1
- package/dist/auth/createAuth.js.map +1 -1
- package/dist/bindings/D1DatabaseDriver.js +21 -13
- package/dist/bindings/D1DatabaseDriver.js.map +1 -1
- package/dist/mount/bootRuntimeOnce.d.ts +1 -1
- package/dist/mount/bootRuntimeOnce.d.ts.map +1 -1
- package/dist/mount/bootRuntimeOnce.js +50 -4
- package/dist/mount/bootRuntimeOnce.js.map +1 -1
- package/dist/mount/cmsConfig.d.ts +2 -1
- package/dist/mount/cmsConfig.d.ts.map +1 -1
- package/dist/mount/index.d.ts +1 -1
- package/dist/mount/index.d.ts.map +1 -1
- package/dist/mount/index.js.map +1 -1
- package/dist/mount/mountMcp.d.ts +4 -4
- package/dist/mount/mountMcp.d.ts.map +1 -1
- package/dist/mount/mountMcp.js +34 -20
- package/dist/mount/mountMcp.js.map +1 -1
- package/dist/mount/mountPublicRoutes.d.ts +8 -3
- package/dist/mount/mountPublicRoutes.d.ts.map +1 -1
- package/dist/mount/mountPublicRoutes.js +209 -28
- package/dist/mount/mountPublicRoutes.js.map +1 -1
- package/dist/mount/mountServerEndpoints.d.ts +3 -4
- package/dist/mount/mountServerEndpoints.d.ts.map +1 -1
- package/dist/mount/mountServerEndpoints.js +216 -75
- package/dist/mount/mountServerEndpoints.js.map +1 -1
- package/dist/mount/resolveCaller.d.ts +2 -2
- package/dist/mount/resolveCaller.d.ts.map +1 -1
- package/dist/mount/resolveCaller.js +45 -14
- package/dist/mount/resolveCaller.js.map +1 -1
- package/dist/oauth/cachePolicy.d.ts +1 -0
- package/dist/oauth/cachePolicy.d.ts.map +1 -1
- package/dist/oauth/cachePolicy.js +2 -0
- package/dist/oauth/cachePolicy.js.map +1 -1
- package/dist/oauth/consentHtml.d.ts +1 -1
- package/dist/oauth/consentHtml.d.ts.map +1 -1
- package/dist/oauth/consentHtml.js +15 -13
- package/dist/oauth/consentHtml.js.map +1 -1
- package/dist/oauth/index.d.ts +1 -1
- package/dist/oauth/index.d.ts.map +1 -1
- package/dist/oauth/index.js.map +1 -1
- package/dist/oauth/mountOAuth.d.ts +0 -7
- package/dist/oauth/mountOAuth.d.ts.map +1 -1
- package/dist/oauth/mountOAuth.js +3 -7
- package/dist/oauth/mountOAuth.js.map +1 -1
- package/dist/oauth/oauthSingleton.d.ts +2 -3
- package/dist/oauth/oauthSingleton.d.ts.map +1 -1
- package/dist/oauth/oauthSingleton.js +6 -0
- package/dist/oauth/oauthSingleton.js.map +1 -1
- package/dist/worker/createMantleWorker.d.ts +4 -1
- package/dist/worker/createMantleWorker.d.ts.map +1 -1
- package/dist/worker/createMantleWorker.js +16 -1
- package/dist/worker/createMantleWorker.js.map +1 -1
- package/package.json +9 -9
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cmsConfig.d.ts","sourceRoot":"","sources":["../../src/mount/cmsConfig.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EACV,oBAAoB,EACpB,kBAAkB,EAClB,gBAAgB,EACjB,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAClE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,oBAAoB,CAAC;AAErE;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,SAAS,EAAE,SAAS,QAAQ,EAAE,CAAC;IACxC,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC;IACzD,QAAQ,CAAC,SAAS,CAAC,EAAE,gBAAgB,CAAC;IACtC,QAAQ,CAAC,YAAY,CAAC,EAAE,YAAY,CAAC;IACrC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;IACjD,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC,oBAAoB,EAAE,IAAI,GAAG,QAAQ,CAAC,GAAG;QAC/D;;yCAEiC;QACjC,QAAQ,CAAC,YAAY,CAAC,EAAE,oBAAoB,CAAC,cAAc,CAAC,CAAC;QAC7D;;;;;oBAKY;QACZ,QAAQ,CAAC,sBAAsB,CAAC,EAAE,oBAAoB,CAAC,wBAAwB,CAAC,CAAC;KAClF,CAAC;IACF,gEAAgE;IAChE,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB;;yBAEqB;IACrB,QAAQ,CAAC,kBAAkB,CAAC,EAAE,0BAA0B,CAAC;IACzD,sEAAsE;IACtE,QAAQ,CAAC,
|
|
1
|
+
{"version":3,"file":"cmsConfig.d.ts","sourceRoot":"","sources":["../../src/mount/cmsConfig.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EACV,oBAAoB,EACpB,kBAAkB,EAClB,gBAAgB,EACjB,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAClE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,oBAAoB,CAAC;AAErE;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,SAAS,EAAE,SAAS,QAAQ,EAAE,CAAC;IACxC,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC;IACzD,QAAQ,CAAC,SAAS,CAAC,EAAE,gBAAgB,CAAC;IACtC,QAAQ,CAAC,YAAY,CAAC,EAAE,YAAY,CAAC;IACrC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;IACjD,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC,oBAAoB,EAAE,IAAI,GAAG,QAAQ,CAAC,GAAG;QAC/D;;yCAEiC;QACjC,QAAQ,CAAC,YAAY,CAAC,EAAE,oBAAoB,CAAC,cAAc,CAAC,CAAC;QAC7D;;;;;oBAKY;QACZ,QAAQ,CAAC,sBAAsB,CAAC,EAAE,oBAAoB,CAAC,wBAAwB,CAAC,CAAC;KAClF,CAAC;IACF,gEAAgE;IAChE,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB;;yBAEqB;IACrB,QAAQ,CAAC,kBAAkB,CAAC,EAAE,0BAA0B,CAAC;IACzD,sEAAsE;IACtE,QAAQ,CAAC,SAAS,CAAC,EAAE;QACnB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAC1B,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;KACrC,CAAC;IACF,QAAQ,CAAC,cAAc,CAAC,EAAE,oBAAoB,CAAC,gBAAgB,CAAC,CAAC;CAClE"}
|
package/dist/mount/index.d.ts
CHANGED
|
@@ -3,6 +3,6 @@ export { runMantleUseCase } from "./runMantleUseCase.js";
|
|
|
3
3
|
export { resolveCaller, type CallerResolution, type ConsumerCredentialResolution, type ConsumerCredentialResolver, type ResolveCallerOptions, } from "./resolveCaller.js";
|
|
4
4
|
export { type CmsRuntimeRef, createCmsRef } from "./bootRuntimeOnce.js";
|
|
5
5
|
export { createMcpApiHandler, type CreateMcpApiHandlerOptions, } from "./mountMcp.js";
|
|
6
|
-
export { mountPublicRoutes, type CollectionRouteConfig, type MountPublicRoutesOptions, type PublicRouteContext, type SlugOverride, } from "./mountPublicRoutes.js";
|
|
6
|
+
export { mountPublicRoutes, type CollectionRouteConfig, type MountPublicRoutesOptions, type PublicContentContext, type PublicRouteContext, type SlugOverride, } from "./mountPublicRoutes.js";
|
|
7
7
|
export type { CmsConfig } from "./cmsConfig.js";
|
|
8
8
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/mount/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EACL,aAAa,EACb,KAAK,gBAAgB,EACrB,KAAK,4BAA4B,EACjC,KAAK,0BAA0B,EAC/B,KAAK,oBAAoB,GAC1B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,KAAK,aAAa,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACxE,OAAO,EACL,mBAAmB,EACnB,KAAK,0BAA0B,GAChC,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,iBAAiB,EACjB,KAAK,qBAAqB,EAC1B,KAAK,wBAAwB,EAC7B,KAAK,kBAAkB,EACvB,KAAK,YAAY,GAClB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/mount/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EACL,aAAa,EACb,KAAK,gBAAgB,EACrB,KAAK,4BAA4B,EACjC,KAAK,0BAA0B,EAC/B,KAAK,oBAAoB,GAC1B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,KAAK,aAAa,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACxE,OAAO,EACL,mBAAmB,EACnB,KAAK,0BAA0B,GAChC,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,iBAAiB,EACjB,KAAK,qBAAqB,EAC1B,KAAK,wBAAwB,EAC7B,KAAK,oBAAoB,EACzB,KAAK,kBAAkB,EACvB,KAAK,YAAY,GAClB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/mount/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/mount/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EACL,aAAa,GAKd,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAsB,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACxE,OAAO,EACL,mBAAmB,GAEpB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,iBAAiB,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/mount/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EACL,aAAa,GAKd,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAsB,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACxE,OAAO,EACL,mBAAmB,GAEpB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,iBAAiB,GAMlB,MAAM,wBAAwB,CAAC"}
|
package/dist/mount/mountMcp.d.ts
CHANGED
|
@@ -12,10 +12,10 @@ export interface CreateMcpApiHandlerOptions {
|
|
|
12
12
|
* resource path. Plug into `createOAuthProvider({ apiHandlers })`
|
|
13
13
|
* keyed by the resource path (e.g. `/mcp/staff` or `/mcp`).
|
|
14
14
|
*
|
|
15
|
-
* The OAuthProvider lib verifies the bearer token
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
15
|
+
* The OAuthProvider lib verifies the bearer token and sets its
|
|
16
|
+
* token-specific identity and scopes on `ctx.props` BEFORE calling
|
|
17
|
+
* this handler. We re-read the caller's mutable staff role from D1
|
|
18
|
+
* on every invocation.
|
|
19
19
|
*
|
|
20
20
|
* Note: OAuth scope distinction (`mcp:read` vs `mcp:staff`) used to
|
|
21
21
|
* differentiate surfaces here. Removed because claude.ai's MCP client
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mountMcp.d.ts","sourceRoot":"","sources":["../../src/mount/mountMcp.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"mountMcp.d.ts","sourceRoot":"","sources":["../../src/mount/mountMcp.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAG1D,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,GAAG,EAAE,aAAa,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,QAAQ,CAAC;IACrC;;6DAEyD;IACzD,QAAQ,CAAC,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7C;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/D,OAAO,EAAE,0BAA0B,GAClC,eAAe,CAAC,GAAG,CAAC,CA8FtB"}
|
package/dist/mount/mountMcp.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { McpJsonRpcDispatcher } from "@aotter/mantle-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { contextForVerifiedUser } from "./resolveCaller.js";
|
|
3
3
|
/**
|
|
4
4
|
* Build a Cloudflare Worker `ExportedHandler` that serves one MCP
|
|
5
5
|
* resource path. Plug into `createOAuthProvider({ apiHandlers })`
|
|
6
6
|
* keyed by the resource path (e.g. `/mcp/staff` or `/mcp`).
|
|
7
7
|
*
|
|
8
|
-
* The OAuthProvider lib verifies the bearer token
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* The OAuthProvider lib verifies the bearer token and sets its
|
|
9
|
+
* token-specific identity and scopes on `ctx.props` BEFORE calling
|
|
10
|
+
* this handler. We re-read the caller's mutable staff role from D1
|
|
11
|
+
* on every invocation.
|
|
12
12
|
*
|
|
13
13
|
* Note: OAuth scope distinction (`mcp:read` vs `mcp:staff`) used to
|
|
14
14
|
* differentiate surfaces here. Removed because claude.ai's MCP client
|
|
@@ -27,15 +27,21 @@ export function createMcpApiHandler(options) {
|
|
|
27
27
|
const dispatcherCache = new WeakMap();
|
|
28
28
|
return {
|
|
29
29
|
async fetch(request, env, ctx) {
|
|
30
|
-
const props = ctx.props;
|
|
31
|
-
if (!props
|
|
30
|
+
const props = readOAuthApiProps(ctx.props);
|
|
31
|
+
if (!props)
|
|
32
32
|
return forbidden(requiredScopes);
|
|
33
|
-
const grantedScopes = props.scopes
|
|
33
|
+
const grantedScopes = props.scopes;
|
|
34
34
|
if (requiredScopes.some((scope) => !grantedScopes.includes(scope))) {
|
|
35
35
|
return forbidden(requiredScopes);
|
|
36
36
|
}
|
|
37
|
-
const
|
|
38
|
-
|
|
37
|
+
const waitUntil = typeof ctx.waitUntil === "function" ? ctx.waitUntil.bind(ctx) : undefined;
|
|
38
|
+
const handlerContext = await contextForVerifiedUser(props.userId, {
|
|
39
|
+
credential: "oauth",
|
|
40
|
+
credentialId: null,
|
|
41
|
+
clientId: props.clientId,
|
|
42
|
+
scopes: grantedScopes,
|
|
43
|
+
}, ref.auth, { env, ...(waitUntil ? { waitUntil } : {}) });
|
|
44
|
+
if (surface === "staff" && !handlerContext.staff) {
|
|
39
45
|
return forbidden(requiredScopes);
|
|
40
46
|
}
|
|
41
47
|
const runtime = await ref.get();
|
|
@@ -86,19 +92,27 @@ export function createMcpApiHandler(options) {
|
|
|
86
92
|
cached = { mediaPurposesKey, dispatcher };
|
|
87
93
|
dispatcherCache.set(runtime, cached);
|
|
88
94
|
}
|
|
89
|
-
|
|
90
|
-
return cached.dispatcher.dispatch(request, {
|
|
91
|
-
userId: props.userId,
|
|
92
|
-
staff: role && STAFF_ROLE_SET.has(role)
|
|
93
|
-
? { userId: props.userId, role: role }
|
|
94
|
-
: null,
|
|
95
|
-
clientId: props.clientId ?? null,
|
|
96
|
-
credentialId: null,
|
|
97
|
-
scopes: grantedScopes,
|
|
98
|
-
}, { env, ...(waitUntil ? { waitUntil } : {}) });
|
|
95
|
+
return cached.dispatcher.dispatch(request, handlerContext);
|
|
99
96
|
},
|
|
100
97
|
};
|
|
101
98
|
}
|
|
99
|
+
function readOAuthApiProps(value) {
|
|
100
|
+
if (!value || typeof value !== "object")
|
|
101
|
+
return null;
|
|
102
|
+
const props = value;
|
|
103
|
+
if (typeof props["userId"] !== "string" ||
|
|
104
|
+
props["userId"].length === 0 ||
|
|
105
|
+
typeof props["clientId"] !== "string" ||
|
|
106
|
+
props["clientId"].length === 0 ||
|
|
107
|
+
!Array.isArray(props["scopes"]) ||
|
|
108
|
+
!props["scopes"].every((scope) => typeof scope === "string"))
|
|
109
|
+
return null;
|
|
110
|
+
return {
|
|
111
|
+
userId: props["userId"],
|
|
112
|
+
clientId: props["clientId"],
|
|
113
|
+
scopes: props["scopes"],
|
|
114
|
+
};
|
|
115
|
+
}
|
|
102
116
|
function forbidden(requiredScopes) {
|
|
103
117
|
const scope = requiredScopes.join(" ");
|
|
104
118
|
return new Response("forbidden", {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mountMcp.js","sourceRoot":"","sources":["../../src/mount/mountMcp.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"mountMcp.js","sourceRoot":"","sources":["../../src/mount/mountMcp.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Context, Hono } from "hono";
|
|
2
2
|
import type { SiteConfig } from "@aotter/mantle-spec";
|
|
3
|
-
import { type CmsRuntime } from "@aotter/mantle-runtime";
|
|
3
|
+
import { type CmsRuntime, type SeoMeta } from "@aotter/mantle-runtime";
|
|
4
4
|
import type { CmsRuntimeRef } from "./bootRuntimeOnce.js";
|
|
5
5
|
/**
|
|
6
6
|
* `mountPublicRoutes` — mounts the SDK-managed public surface on the
|
|
@@ -47,12 +47,15 @@ export interface CollectionRouteConfig {
|
|
|
47
47
|
* collection. */
|
|
48
48
|
readonly homeSlug?: string;
|
|
49
49
|
}
|
|
50
|
-
export interface
|
|
51
|
-
readonly c: Context;
|
|
50
|
+
export interface PublicContentContext {
|
|
52
51
|
readonly runtime: CmsRuntime;
|
|
53
52
|
readonly site: SiteConfig;
|
|
54
53
|
readonly locale: string;
|
|
55
54
|
}
|
|
55
|
+
export interface PublicRouteContext extends PublicContentContext {
|
|
56
|
+
readonly c: Context;
|
|
57
|
+
readonly seo: SeoMeta;
|
|
58
|
+
}
|
|
56
59
|
export interface SlugOverride {
|
|
57
60
|
readonly collection: string;
|
|
58
61
|
readonly slug: string;
|
|
@@ -64,6 +67,8 @@ export interface MountPublicRoutesOptions {
|
|
|
64
67
|
* recent posts across collections. Optional; without it `/` and
|
|
65
68
|
* `/{locale}` are not registered. */
|
|
66
69
|
readonly homeRenderer?: (ctx: PublicRouteContext) => Promise<Response>;
|
|
70
|
+
/** Agent-readable home body for composed homes without one backing Entry. */
|
|
71
|
+
readonly homeMarkdown?: (ctx: PublicContentContext) => Promise<string | null>;
|
|
67
72
|
/** Renderer for the locale 404 fallback. Required — every miss
|
|
68
73
|
* falls through here. */
|
|
69
74
|
readonly notFoundRenderer: (ctx: PublicRouteContext) => Promise<Response>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mountPublicRoutes.d.ts","sourceRoot":"","sources":["../../src/mount/mountPublicRoutes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC1C,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"mountPublicRoutes.d.ts","sourceRoot":"","sources":["../../src/mount/mountPublicRoutes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC1C,OAAO,KAAK,EAAS,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAML,KAAK,UAAU,EACf,KAAK,OAAO,EACb,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAI1D;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,WAAW,qBAAqB;IACpC,gDAAgD;IAChD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B;;kDAE8C;IAC9C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB;2EACuE;IACvE,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC7B;6CACyC;IACzC,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;IAClC;;sBAEkB;IAClB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,kBAAmB,SAAQ,oBAAoB;IAC9D,QAAQ,CAAC,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,kBAAkB,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;CACjE;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,gBAAgB,EAAE,aAAa,CAAC,qBAAqB,CAAC,CAAC;IAChE;;0CAEsC;IACtC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,kBAAkB,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IACvE,6EAA6E;IAC7E,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,oBAAoB,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC9E;8BAC0B;IAC1B,QAAQ,CAAC,gBAAgB,EAAE,CAAC,GAAG,EAAE,kBAAkB,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC1E,iFAAiF;IACjF,QAAQ,CAAC,aAAa,CAAC,EAAE,aAAa,CAAC,YAAY,CAAC,CAAC;IACrD,qEAAqE;IACrE,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;CAC5B;AAsCD,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,IAAI,EACT,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,wBAAwB,GAChC,IAAI,CA4GN"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { inferLocaleFromPath, serializeEntryAsMarkdown, toUrlLocale, } from "@aotter/mantle-runtime";
|
|
1
|
+
import { absoluteUrl, composePageSeoMeta, inferLocaleFromPath, serializeEntryAsMarkdown, toUrlLocale, } from "@aotter/mantle-runtime";
|
|
2
2
|
import { STAFF_ROLE_SET } from "../auth/createAuth.js";
|
|
3
|
+
import { PUBLIC_CACHE_TAG } from "../oauth/cachePolicy.js";
|
|
3
4
|
const PUBLIC_CACHE_CONTROL = "public, max-age=0, s-maxage=300";
|
|
4
5
|
const PRIVATE_CACHE_CONTROL = "private, no-store";
|
|
5
6
|
const HTML_NO_STORE = {
|
|
@@ -9,14 +10,17 @@ const HTML_NO_STORE = {
|
|
|
9
10
|
const HTML_PUBLIC = {
|
|
10
11
|
"content-type": "text/html; charset=utf-8",
|
|
11
12
|
"cache-control": PUBLIC_CACHE_CONTROL,
|
|
13
|
+
"cache-tag": PUBLIC_CACHE_TAG,
|
|
12
14
|
};
|
|
13
15
|
const MD_PUBLIC = {
|
|
14
16
|
"content-type": "text/markdown; charset=utf-8",
|
|
15
17
|
"cache-control": PUBLIC_CACHE_CONTROL,
|
|
18
|
+
"cache-tag": PUBLIC_CACHE_TAG,
|
|
16
19
|
};
|
|
17
20
|
const TEXT_PUBLIC = {
|
|
18
21
|
"content-type": "text/plain; charset=utf-8",
|
|
19
22
|
"cache-control": PUBLIC_CACHE_CONTROL,
|
|
23
|
+
"cache-tag": PUBLIC_CACHE_TAG,
|
|
20
24
|
};
|
|
21
25
|
const TEXT_NO_STORE = {
|
|
22
26
|
"content-type": "text/plain; charset=utf-8",
|
|
@@ -25,6 +29,7 @@ const TEXT_NO_STORE = {
|
|
|
25
29
|
const SITEMAP_HEADERS = {
|
|
26
30
|
"content-type": "application/xml; charset=utf-8",
|
|
27
31
|
"cache-control": PUBLIC_CACHE_CONTROL,
|
|
32
|
+
"cache-tag": PUBLIC_CACHE_TAG,
|
|
28
33
|
};
|
|
29
34
|
export function mountPublicRoutes(app, ref, options) {
|
|
30
35
|
const liveDev = options.liveDev === true;
|
|
@@ -34,7 +39,9 @@ export function mountPublicRoutes(app, ref, options) {
|
|
|
34
39
|
// `:locale = "llms.txt"` if the literal route registers later.
|
|
35
40
|
app.get("/llms.txt", async () => {
|
|
36
41
|
const runtime = await ref.get();
|
|
37
|
-
const body = await composeRootLlmsTxt(runtime, await runtime.siteConfig.load());
|
|
42
|
+
const body = await composeRootLlmsTxt(runtime, await runtime.siteConfig.load(), options);
|
|
43
|
+
if (!body)
|
|
44
|
+
return textNotFound();
|
|
38
45
|
return new Response(body, { status: 200, headers: TEXT_PUBLIC });
|
|
39
46
|
});
|
|
40
47
|
app.get("/sitemap.xml", async (c) => {
|
|
@@ -47,7 +54,27 @@ export function mountPublicRoutes(app, ref, options) {
|
|
|
47
54
|
}
|
|
48
55
|
const xml = await runtime.composeSitemap.execute({
|
|
49
56
|
site,
|
|
50
|
-
pathFor: (
|
|
57
|
+
pathFor: (entry) => {
|
|
58
|
+
const resolved = runtime.publicPathResolver.forEntry(entry);
|
|
59
|
+
if (entry.locale || !resolved)
|
|
60
|
+
return resolved;
|
|
61
|
+
const route = options.collectionRoutes.find((candidate) => candidate.collection === entry.collection);
|
|
62
|
+
if (!route)
|
|
63
|
+
return resolved;
|
|
64
|
+
const slug = typeof entry.data["slug"] === "string" ? entry.data["slug"] : entry.id;
|
|
65
|
+
const suffix = route.homeSlug === slug
|
|
66
|
+
? ""
|
|
67
|
+
: route.segment ? `${route.segment}/${slug}` : slug;
|
|
68
|
+
return site.locales.map((locale) => localizedPath(locale, suffix));
|
|
69
|
+
},
|
|
70
|
+
additionalPaths: options.homeRenderer
|
|
71
|
+
? site.locales.flatMap((locale) => [
|
|
72
|
+
localizedPath(locale),
|
|
73
|
+
...options.collectionRoutes
|
|
74
|
+
.filter((route) => route.listRoute && route.segment)
|
|
75
|
+
.map((route) => localizedPath(locale, route.segment)),
|
|
76
|
+
])
|
|
77
|
+
: [],
|
|
51
78
|
});
|
|
52
79
|
return new Response(xml, { status: 200, headers: SITEMAP_HEADERS });
|
|
53
80
|
});
|
|
@@ -58,25 +85,44 @@ export function mountPublicRoutes(app, ref, options) {
|
|
|
58
85
|
const canonical = site.canonicalLocale ?? site.locales[0] ?? "en";
|
|
59
86
|
return c.redirect(`/${toUrlLocale(canonical)}`);
|
|
60
87
|
});
|
|
88
|
+
app.get("/:locale{[^/]+\\.md}", async (c) => {
|
|
89
|
+
const runtime = await ref.get();
|
|
90
|
+
const site = await runtime.siteConfig.load();
|
|
91
|
+
const raw = c.req.param("locale");
|
|
92
|
+
const locale = canonicalLocaleParam(raw.endsWith(".md") ? raw.slice(0, -3) : raw, site.locales);
|
|
93
|
+
if (locale === null)
|
|
94
|
+
return textNotFound();
|
|
95
|
+
const ctx = buildCtx(c, runtime, site, locale, homeSeo(site, locale, false));
|
|
96
|
+
const body = options.homeMarkdown
|
|
97
|
+
? await options.homeMarkdown(ctx)
|
|
98
|
+
: await readHomeMarkdown(runtime, options.collectionRoutes, locale);
|
|
99
|
+
return body ? new Response(body, { status: 200, headers: MD_PUBLIC }) : textNotFound();
|
|
100
|
+
});
|
|
61
101
|
app.get("/:locale", async (c) => {
|
|
62
102
|
const runtime = await ref.get();
|
|
63
103
|
const site = await runtime.siteConfig.load();
|
|
64
104
|
const locale = canonicalLocaleParam(c.req.param("locale"), site.locales);
|
|
65
|
-
const
|
|
66
|
-
if (locale === null)
|
|
67
|
-
return options.notFoundRenderer(
|
|
68
|
-
|
|
105
|
+
const fallbackLocale = locale ?? inferLocaleFromPath(c.req.path, site);
|
|
106
|
+
if (locale === null) {
|
|
107
|
+
return options.notFoundRenderer(buildCtx(c, runtime, site, fallbackLocale, homeSeo(site, fallbackLocale, false)));
|
|
108
|
+
}
|
|
109
|
+
const baseCtx = buildCtx(c, runtime, site, locale, homeSeo(site, locale, false));
|
|
110
|
+
const markdown = options.homeMarkdown
|
|
111
|
+
? await options.homeMarkdown(baseCtx)
|
|
112
|
+
: await readHomeMarkdown(runtime, options.collectionRoutes, locale);
|
|
113
|
+
const response = await options.homeRenderer(buildCtx(c, runtime, site, locale, homeSeo(site, locale, markdown !== null)));
|
|
114
|
+
return applyPublicHeaders(response, liveDev);
|
|
69
115
|
});
|
|
70
116
|
}
|
|
71
117
|
app.get("/:locale/llms.txt", async (c) => {
|
|
72
118
|
const runtime = await ref.get();
|
|
73
119
|
const locale = canonicalLocaleParam(c.req.param("locale"), await runtime.siteConfig.readLocales());
|
|
74
120
|
if (locale === null)
|
|
75
|
-
return
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
121
|
+
return textNotFound();
|
|
122
|
+
const site = await runtime.siteConfig.load();
|
|
123
|
+
const body = await composeLocaleLlmsTxt(runtime, site, locale, options);
|
|
124
|
+
if (!body)
|
|
125
|
+
return textNotFound();
|
|
80
126
|
return new Response(body, { status: 200, headers: TEXT_PUBLIC });
|
|
81
127
|
});
|
|
82
128
|
for (const route of options.collectionRoutes) {
|
|
@@ -86,26 +132,61 @@ export function mountPublicRoutes(app, ref, options) {
|
|
|
86
132
|
const runtime = await ref.get();
|
|
87
133
|
const site = await runtime.siteConfig.load();
|
|
88
134
|
const locale = inferLocaleFromPath(c.req.path, site);
|
|
89
|
-
return options.notFoundRenderer(buildCtx(c, runtime, site, locale));
|
|
135
|
+
return options.notFoundRenderer(buildCtx(c, runtime, site, locale, homeSeo(site, locale, false)));
|
|
90
136
|
});
|
|
91
137
|
}
|
|
92
138
|
function mountCollection(app, ref, options, route, liveDev, overrides) {
|
|
93
139
|
const segPath = route.segment ? `/${route.segment}` : "";
|
|
94
140
|
if (route.listRoute) {
|
|
141
|
+
if (route.markdownMirror !== false && route.segment) {
|
|
142
|
+
app.get(`/:locale${segPath}.md`, async (c) => {
|
|
143
|
+
const runtime = await ref.get();
|
|
144
|
+
const locale = canonicalLocaleParam(c.req.param("locale") ?? "", await runtime.siteConfig.readLocales());
|
|
145
|
+
if (locale === null)
|
|
146
|
+
return textNotFound();
|
|
147
|
+
const site = await runtime.siteConfig.load();
|
|
148
|
+
const body = await runtime.composeLlmsTxt.execute({
|
|
149
|
+
site,
|
|
150
|
+
locale: contentLocale(runtime, route.collection, locale),
|
|
151
|
+
collection: route.collection,
|
|
152
|
+
pathFor: (entry) => entryPathForLocale(runtime, options.collectionRoutes, entry, locale),
|
|
153
|
+
});
|
|
154
|
+
return body ? new Response(body, { status: 200, headers: MD_PUBLIC }) : textNotFound();
|
|
155
|
+
});
|
|
156
|
+
}
|
|
95
157
|
app.get(`/:locale${segPath}`, async (c) => {
|
|
96
158
|
const runtime = await ref.get();
|
|
97
159
|
const locale = canonicalLocaleParam(c.req.param("locale"), await runtime.siteConfig.readLocales());
|
|
98
160
|
const loadSite = lazySite(runtime);
|
|
99
161
|
const notFound = async () => {
|
|
100
162
|
const site = await loadSite();
|
|
101
|
-
return options.notFoundRenderer(buildCtx(c, runtime, site, locale ?? inferLocaleFromPath(c.req.path, site)));
|
|
163
|
+
return options.notFoundRenderer(buildCtx(c, runtime, site, locale ?? inferLocaleFromPath(c.req.path, site), homeSeo(site, locale ?? inferLocaleFromPath(c.req.path, site), false)));
|
|
102
164
|
};
|
|
103
165
|
if (locale === null)
|
|
104
166
|
return notFound();
|
|
167
|
+
const site = await loadSite();
|
|
168
|
+
const markdown = route.markdownMirror === false ? null : await runtime.composeLlmsTxt.execute({
|
|
169
|
+
site,
|
|
170
|
+
locale: contentLocale(runtime, route.collection, locale),
|
|
171
|
+
collection: route.collection,
|
|
172
|
+
pathFor: (entry) => entryPathForLocale(runtime, options.collectionRoutes, entry, locale),
|
|
173
|
+
});
|
|
174
|
+
const publicPath = localizedPath(locale, route.segment);
|
|
175
|
+
const schemaTitle = runtime.schemasByName.get(route.collection)?.spec.title;
|
|
176
|
+
const seo = composePageSeoMeta({
|
|
177
|
+
site,
|
|
178
|
+
locale,
|
|
179
|
+
publicPath,
|
|
180
|
+
title: schemaTitle ? `${schemaTitle} · ${site.brand}` : site.title,
|
|
181
|
+
markdown: markdown !== null,
|
|
182
|
+
pathForLocale: (candidate) => localizedPath(candidate, route.segment),
|
|
183
|
+
});
|
|
105
184
|
const html = await runtime.renderListLive.execute({
|
|
106
185
|
collection: route.collection,
|
|
107
186
|
locale,
|
|
108
|
-
|
|
187
|
+
contentLocale: contentLocale(runtime, route.collection, locale),
|
|
188
|
+
site,
|
|
189
|
+
seo,
|
|
109
190
|
});
|
|
110
191
|
if (html === null)
|
|
111
192
|
return notFound();
|
|
@@ -127,18 +208,21 @@ function mountCollection(app, ref, options, route, liveDev, overrides) {
|
|
|
127
208
|
const locale = canonicalLocaleParam(c.req.param("locale"), await runtime.siteConfig.readLocales());
|
|
128
209
|
const slugParam = c.req.param("slug") ?? "";
|
|
129
210
|
const slug = slugParam.endsWith(".md") ? slugParam.slice(0, -3) : slugParam;
|
|
130
|
-
const notFound = () =>
|
|
211
|
+
const notFound = () => textNotFound();
|
|
131
212
|
if (locale === null)
|
|
132
213
|
return notFound();
|
|
133
214
|
const entry = await runtime.entryReader.readBySlug({
|
|
134
215
|
collection: route.collection,
|
|
135
216
|
slug,
|
|
136
|
-
locale,
|
|
217
|
+
locale: contentLocale(runtime, route.collection, locale),
|
|
137
218
|
status: "published",
|
|
138
219
|
});
|
|
139
220
|
if (!entry)
|
|
140
221
|
return notFound();
|
|
141
|
-
|
|
222
|
+
const markdown = serializeEntryAsMarkdown(entry);
|
|
223
|
+
if (!markdown)
|
|
224
|
+
return notFound();
|
|
225
|
+
return new Response(markdown, {
|
|
142
226
|
status: 200,
|
|
143
227
|
headers: MD_PUBLIC,
|
|
144
228
|
});
|
|
@@ -151,35 +235,41 @@ function mountCollection(app, ref, options, route, liveDev, overrides) {
|
|
|
151
235
|
const loadSite = lazySite(runtime);
|
|
152
236
|
const notFound = async () => {
|
|
153
237
|
const site = await loadSite();
|
|
154
|
-
return options.notFoundRenderer(buildCtx(c, runtime, site, locale ?? inferLocaleFromPath(c.req.path, site)));
|
|
238
|
+
return options.notFoundRenderer(buildCtx(c, runtime, site, locale ?? inferLocaleFromPath(c.req.path, site), homeSeo(site, locale ?? inferLocaleFromPath(c.req.path, site), false)));
|
|
155
239
|
};
|
|
156
240
|
if (locale === null)
|
|
157
241
|
return notFound();
|
|
158
242
|
const override = overrides.get(overrideKey(route.collection, slug));
|
|
159
243
|
if (override) {
|
|
160
244
|
const site = await loadSite();
|
|
161
|
-
return override.render(buildCtx(c, runtime, site, locale));
|
|
245
|
+
return applyPublicHeaders(await override.render(buildCtx(c, runtime, site, locale, homeSeo(site, locale, false))), liveDev);
|
|
162
246
|
}
|
|
163
247
|
if (c.req.query("preview") === "1") {
|
|
164
248
|
const denied = await assertStaffSession(ref, c.req.raw);
|
|
165
249
|
if (denied)
|
|
166
250
|
return denied;
|
|
167
251
|
const site = await loadSite();
|
|
252
|
+
const seoRoute = entrySeoRoute(site, route, locale, slug, contentLocale(runtime, route.collection, locale) === null);
|
|
168
253
|
const html = await runtime.previewEntry.execute({
|
|
169
254
|
collection: route.collection,
|
|
170
255
|
slug,
|
|
171
256
|
locale,
|
|
257
|
+
contentLocale: contentLocale(runtime, route.collection, locale),
|
|
172
258
|
site,
|
|
259
|
+
...seoRoute,
|
|
173
260
|
});
|
|
174
261
|
if (html === null)
|
|
175
262
|
return notFound();
|
|
176
263
|
return new Response(html, { status: 200, headers: HTML_NO_STORE });
|
|
177
264
|
}
|
|
265
|
+
const site = await loadSite();
|
|
178
266
|
const html = await runtime.renderEntryLive.execute({
|
|
179
267
|
collection: route.collection,
|
|
180
268
|
slug,
|
|
181
269
|
locale,
|
|
182
|
-
|
|
270
|
+
contentLocale: contentLocale(runtime, route.collection, locale),
|
|
271
|
+
site,
|
|
272
|
+
...entrySeoRoute(site, route, locale, slug, contentLocale(runtime, route.collection, locale) === null),
|
|
183
273
|
});
|
|
184
274
|
if (html === null)
|
|
185
275
|
return notFound();
|
|
@@ -227,24 +317,115 @@ async function assertStaffSession(ref, req) {
|
|
|
227
317
|
* header in localized mode, so the sections self-separate without a
|
|
228
318
|
* custom delimiter.
|
|
229
319
|
*/
|
|
230
|
-
async function composeRootLlmsTxt(runtime, site) {
|
|
320
|
+
async function composeRootLlmsTxt(runtime, site, options) {
|
|
321
|
+
const parts = [];
|
|
231
322
|
if (site.locales.length === 0) {
|
|
232
323
|
return runtime.composeLlmsTxt.execute({ site, locale: null });
|
|
233
324
|
}
|
|
234
|
-
const parts = [];
|
|
235
325
|
for (const locale of site.locales) {
|
|
236
|
-
const body = await runtime
|
|
237
|
-
if (body
|
|
326
|
+
const body = await composeLocaleLlmsTxt(runtime, site, locale, options);
|
|
327
|
+
if (body)
|
|
238
328
|
parts.push(body);
|
|
239
329
|
}
|
|
240
|
-
return parts.length > 0 ? parts.join("\n---\n\n") :
|
|
330
|
+
return parts.length > 0 ? parts.join("\n---\n\n") : null;
|
|
331
|
+
}
|
|
332
|
+
async function composeLocaleLlmsTxt(runtime, site, locale, options) {
|
|
333
|
+
const parts = [];
|
|
334
|
+
if (options.homeMarkdown) {
|
|
335
|
+
if (await options.homeMarkdown({ runtime, site, locale })) {
|
|
336
|
+
parts.push(`# ${site.title}\n\nLocale: ${locale}\n\n## Pages\n\n- [${site.title}](${absoluteUrl(site.origin, `${localizedPath(locale)}.md`)})\n`);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
const entries = await runtime.composeLlmsTxt.execute({
|
|
340
|
+
site,
|
|
341
|
+
locale,
|
|
342
|
+
includeUnlocalized: true,
|
|
343
|
+
pathFor: (entry) => {
|
|
344
|
+
const route = options.collectionRoutes.find((candidate) => candidate.collection === entry.collection);
|
|
345
|
+
const slug = typeof entry.data["slug"] === "string" ? entry.data["slug"] : entry.id;
|
|
346
|
+
if (options.homeMarkdown && route?.homeSlug === slug)
|
|
347
|
+
return null;
|
|
348
|
+
return entryPathForLocale(runtime, options.collectionRoutes, entry, locale);
|
|
349
|
+
},
|
|
350
|
+
});
|
|
351
|
+
if (entries)
|
|
352
|
+
parts.push(entries);
|
|
353
|
+
return parts.length > 0 ? parts.join("\n---\n\n") : null;
|
|
241
354
|
}
|
|
242
355
|
function lazySite(runtime) {
|
|
243
356
|
let pending;
|
|
244
357
|
return () => pending ??= runtime.siteConfig.load();
|
|
245
358
|
}
|
|
246
|
-
function buildCtx(c, runtime, site, locale) {
|
|
247
|
-
return { c, runtime, site, locale };
|
|
359
|
+
function buildCtx(c, runtime, site, locale, seo) {
|
|
360
|
+
return { c, runtime, site, locale, seo };
|
|
361
|
+
}
|
|
362
|
+
function homeSeo(site, locale, markdown) {
|
|
363
|
+
return composePageSeoMeta({
|
|
364
|
+
site,
|
|
365
|
+
locale,
|
|
366
|
+
publicPath: localizedPath(locale),
|
|
367
|
+
markdown,
|
|
368
|
+
pathForLocale: (candidate) => localizedPath(candidate),
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
function localizedPath(locale, segment) {
|
|
372
|
+
return `/${toUrlLocale(locale)}${segment ? `/${segment}` : ""}`;
|
|
373
|
+
}
|
|
374
|
+
function entrySeoRoute(site, route, locale, slug, sharedAcrossLocales) {
|
|
375
|
+
const suffix = route.segment ? `${route.segment}/${slug}` : slug;
|
|
376
|
+
return {
|
|
377
|
+
publicPath: localizedPath(locale, suffix),
|
|
378
|
+
publicLocale: locale,
|
|
379
|
+
...(!sharedAcrossLocales || !site.locales.some((candidate) => candidate.toLowerCase() !== locale.toLowerCase())
|
|
380
|
+
? {}
|
|
381
|
+
: {
|
|
382
|
+
siblings: site.locales
|
|
383
|
+
.filter((candidate) => candidate.toLowerCase() !== locale.toLowerCase())
|
|
384
|
+
.map((candidate) => ({ locale: candidate, publicPath: localizedPath(candidate, suffix) })),
|
|
385
|
+
}),
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
function contentLocale(runtime, collection, locale) {
|
|
389
|
+
return runtime.schemasByName.get(collection)?.spec.localized ? locale : null;
|
|
390
|
+
}
|
|
391
|
+
function entryPathForLocale(runtime, routes, entry, locale) {
|
|
392
|
+
if (entry.locale)
|
|
393
|
+
return runtime.publicPathResolver?.forEntry(entry) ?? null;
|
|
394
|
+
const route = routes.find((candidate) => candidate.collection === entry.collection);
|
|
395
|
+
if (!route)
|
|
396
|
+
return runtime.publicPathResolver?.forEntry(entry) ?? null;
|
|
397
|
+
const slug = typeof entry.data["slug"] === "string" ? entry.data["slug"] : entry.id;
|
|
398
|
+
return localizedPath(locale, route.homeSlug === slug
|
|
399
|
+
? ""
|
|
400
|
+
: route.segment ? `${route.segment}/${slug}` : slug);
|
|
401
|
+
}
|
|
402
|
+
async function readHomeMarkdown(runtime, routes, locale) {
|
|
403
|
+
const route = routes.find((candidate) => candidate.homeSlug && candidate.markdownMirror !== false);
|
|
404
|
+
if (!route?.homeSlug)
|
|
405
|
+
return null;
|
|
406
|
+
const entry = await runtime.entryReader.readBySlug({
|
|
407
|
+
collection: route.collection,
|
|
408
|
+
slug: route.homeSlug,
|
|
409
|
+
locale: contentLocale(runtime, route.collection, locale),
|
|
410
|
+
status: "published",
|
|
411
|
+
});
|
|
412
|
+
return entry ? serializeEntryAsMarkdown(entry) : null;
|
|
413
|
+
}
|
|
414
|
+
function applyPublicHeaders(response, noStore) {
|
|
415
|
+
const headers = new Headers(response.headers);
|
|
416
|
+
headers.set("cache-control", noStore ? PRIVATE_CACHE_CONTROL : PUBLIC_CACHE_CONTROL);
|
|
417
|
+
if (noStore)
|
|
418
|
+
headers.delete("cache-tag");
|
|
419
|
+
else
|
|
420
|
+
headers.set("cache-tag", PUBLIC_CACHE_TAG);
|
|
421
|
+
return new Response(response.body, {
|
|
422
|
+
status: response.status,
|
|
423
|
+
statusText: response.statusText,
|
|
424
|
+
headers,
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
function textNotFound() {
|
|
428
|
+
return new Response("not found", { status: 404, headers: TEXT_NO_STORE });
|
|
248
429
|
}
|
|
249
430
|
function canonicalLocaleParam(locale, locales) {
|
|
250
431
|
const target = locale.toLowerCase();
|