blume 1.7.0 → 1.7.2
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/CHANGELOG.md +18 -0
- package/dist/cli/{chunk-9qs6acpw.js → chunk-12dxjqk7.js} +32 -43
- package/dist/cli/{chunk-9qs6acpw.js.map → chunk-12dxjqk7.js.map} +2 -2
- package/dist/cli/{chunk-s5dsk8bj.js → chunk-196vjxp9.js} +65 -64
- package/dist/cli/chunk-196vjxp9.js.map +13 -0
- package/dist/cli/{chunk-27gtm2ym.js → chunk-2mzebbbz.js} +1 -1
- package/dist/cli/{chunk-s5e5jt53.js → chunk-2z47ypj8.js} +1 -1
- package/dist/cli/{chunk-tnskyrej.js → chunk-30e87n55.js} +15 -24
- package/dist/cli/{chunk-tnskyrej.js.map → chunk-30e87n55.js.map} +2 -2
- package/dist/cli/{chunk-4trphnvy.js → chunk-3w7b2vcx.js} +10 -13
- package/dist/cli/{chunk-4trphnvy.js.map → chunk-3w7b2vcx.js.map} +2 -2
- package/dist/cli/{chunk-v5mm027v.js → chunk-450a7rcr.js} +8 -8
- package/dist/cli/{chunk-v5mm027v.js.map → chunk-450a7rcr.js.map} +1 -1
- package/dist/cli/{chunk-5d4q7121.js → chunk-5n7t497w.js} +142 -134
- package/dist/cli/{chunk-5d4q7121.js.map → chunk-5n7t497w.js.map} +5 -5
- package/dist/cli/{chunk-xv91q4nm.js → chunk-61j18dwk.js} +37 -9
- package/dist/cli/{chunk-xv91q4nm.js.map → chunk-61j18dwk.js.map} +3 -3
- package/dist/cli/{chunk-3r94j3tc.js → chunk-688e0dde.js} +2 -2
- package/dist/cli/{chunk-vxv4x1n8.js → chunk-88by27n5.js} +2 -2
- package/dist/cli/{chunk-cfw6x4rm.js → chunk-8cd8tj54.js} +28 -35
- package/dist/cli/{chunk-cfw6x4rm.js.map → chunk-8cd8tj54.js.map} +2 -2
- package/dist/cli/{chunk-8gnpdsn1.js → chunk-9bkjd11x.js} +2 -2
- package/dist/cli/{chunk-qq9nm3qd.js → chunk-9he6crym.js} +21 -28
- package/dist/cli/{chunk-qq9nm3qd.js.map → chunk-9he6crym.js.map} +2 -2
- package/dist/cli/{chunk-jk1zwka1.js → chunk-aztttvb3.js} +27 -33
- package/dist/cli/{chunk-jk1zwka1.js.map → chunk-aztttvb3.js.map} +2 -2
- package/dist/cli/{chunk-ckh3a410.js → chunk-cvky9gb2.js} +18 -24
- package/dist/cli/{chunk-ckh3a410.js.map → chunk-cvky9gb2.js.map} +2 -2
- package/dist/cli/{chunk-kwx90v78.js → chunk-eevwt1sc.js} +23 -32
- package/dist/cli/{chunk-kwx90v78.js.map → chunk-eevwt1sc.js.map} +2 -2
- package/dist/cli/{chunk-agy5rzxy.js → chunk-ejjx8znq.js} +126 -77
- package/dist/cli/chunk-ejjx8znq.js.map +15 -0
- package/dist/cli/{chunk-ye9zdkgv.js → chunk-exeeb35e.js} +3 -3
- package/dist/cli/{chunk-v2ymm99c.js → chunk-fmceyezb.js} +41 -50
- package/dist/cli/{chunk-v2ymm99c.js.map → chunk-fmceyezb.js.map} +2 -2
- package/dist/cli/{chunk-s102bysw.js → chunk-hdm2dkd2.js} +209 -97
- package/dist/cli/{chunk-s102bysw.js.map → chunk-hdm2dkd2.js.map} +6 -5
- package/dist/cli/{chunk-zr3ygrq3.js → chunk-hs3gbh8p.js} +10 -15
- package/dist/cli/{chunk-zr3ygrq3.js.map → chunk-hs3gbh8p.js.map} +2 -2
- package/dist/cli/{chunk-n0nyat6g.js → chunk-jbj4qhfw.js} +3 -3
- package/dist/cli/{chunk-ev67ycx0.js → chunk-jq5n4avg.js} +1 -1
- package/dist/cli/{chunk-jxkxjsc1.js → chunk-mqb2ka8m.js} +22 -30
- package/dist/cli/{chunk-jxkxjsc1.js.map → chunk-mqb2ka8m.js.map} +2 -2
- package/dist/cli/{chunk-drke6t0h.js → chunk-mt76t7dj.js} +26 -34
- package/dist/cli/{chunk-drke6t0h.js.map → chunk-mt76t7dj.js.map} +2 -2
- package/dist/cli/{chunk-jtb45atp.js → chunk-n9sra6sy.js} +13 -13
- package/dist/cli/{chunk-jtb45atp.js.map → chunk-n9sra6sy.js.map} +1 -1
- package/dist/cli/{chunk-x66c5yjn.js → chunk-ppfvdcd4.js} +2 -2
- package/dist/cli/{chunk-wd27zjcz.js → chunk-q4rae3bg.js} +1 -1
- package/dist/cli/{chunk-pxj10x8y.js → chunk-ra1v2nc2.js} +1 -1
- package/dist/cli/{chunk-y3g15rvv.js → chunk-t3tj0dgr.js} +26 -33
- package/dist/cli/{chunk-y3g15rvv.js.map → chunk-t3tj0dgr.js.map} +2 -2
- package/dist/cli/{chunk-j6pxe0dt.js → chunk-tqa1s0k8.js} +4 -4
- package/dist/cli/{chunk-cbjnx4s8.js → chunk-vh9w1sgp.js} +1 -1
- package/dist/cli/{chunk-0qhq7b8q.js → chunk-vkrsvbr5.js} +13 -17
- package/dist/cli/{chunk-0qhq7b8q.js.map → chunk-vkrsvbr5.js.map} +2 -2
- package/dist/cli/{chunk-sbdqrjbb.js → chunk-vrfp10qk.js} +1 -1
- package/dist/cli/{chunk-ynacq3ev.js → chunk-wjt80jps.js} +27 -40
- package/dist/cli/{chunk-ynacq3ev.js.map → chunk-wjt80jps.js.map} +3 -4
- package/dist/cli/{chunk-18tjv4f7.js → chunk-xhtpx3ff.js} +21 -30
- package/dist/cli/{chunk-18tjv4f7.js.map → chunk-xhtpx3ff.js.map} +2 -2
- package/dist/cli/{chunk-5hs6gb7n.js → chunk-yzhm0j9q.js} +1 -1
- package/dist/cli/index.js +397 -34
- package/dist/cli/index.js.map +12 -4
- package/dist/types/components/layout/nav-utils.d.ts +15 -2
- package/dist/types/core/config-input.d.ts +33 -0
- package/dist/types/core/schema.d.ts +29 -3
- package/docs/08-faq.mdx +21 -0
- package/docs/configuration/ask-ai.mdx +63 -0
- package/docs/content/sources.mdx +2 -2
- package/package.json +1 -1
- package/src/ai/ask.ts +31 -4
- package/src/ai/cors.ts +87 -0
- package/src/astro/generate.ts +3 -0
- package/src/astro/templates.ts +192 -83
- package/src/components/content/YouTube.astro +1 -1
- package/src/components/layout/Header.astro +1 -1
- package/src/components/layout/NavTree.astro +75 -66
- package/src/components/layout/RootLayout.astro +4 -1
- package/src/components/layout/Search.astro +11 -0
- package/src/components/layout/nav-utils.ts +47 -15
- package/src/core/adapter.ts +61 -0
- package/src/core/config-input.ts +33 -0
- package/src/core/schema.ts +61 -0
- package/src/core/sources/assets.ts +162 -26
- package/src/core/sources/notion.ts +60 -1
- package/src/registry/eject.ts +3 -0
- package/src/theme/entry.ts +9 -0
- package/dist/cli/chunk-2aj8ddew.js +0 -72
- package/dist/cli/chunk-2aj8ddew.js.map +0 -10
- package/dist/cli/chunk-4xyggvgf.js +0 -21
- package/dist/cli/chunk-4xyggvgf.js.map +0 -10
- package/dist/cli/chunk-6kzzpsx8.js +0 -26
- package/dist/cli/chunk-6kzzpsx8.js.map +0 -10
- package/dist/cli/chunk-agy5rzxy.js.map +0 -15
- package/dist/cli/chunk-bcy492zc.js +0 -16
- package/dist/cli/chunk-bcy492zc.js.map +0 -10
- package/dist/cli/chunk-btfr9yvw.js +0 -41
- package/dist/cli/chunk-btfr9yvw.js.map +0 -10
- package/dist/cli/chunk-ey89bjj1.js +0 -209
- package/dist/cli/chunk-ey89bjj1.js.map +0 -11
- package/dist/cli/chunk-s5dsk8bj.js.map +0 -13
- package/dist/cli/chunk-vt8fgygt.js +0 -23
- package/dist/cli/chunk-vt8fgygt.js.map +0 -10
- /package/dist/cli/{chunk-27gtm2ym.js.map → chunk-2mzebbbz.js.map} +0 -0
- /package/dist/cli/{chunk-s5e5jt53.js.map → chunk-2z47ypj8.js.map} +0 -0
- /package/dist/cli/{chunk-3r94j3tc.js.map → chunk-688e0dde.js.map} +0 -0
- /package/dist/cli/{chunk-vxv4x1n8.js.map → chunk-88by27n5.js.map} +0 -0
- /package/dist/cli/{chunk-8gnpdsn1.js.map → chunk-9bkjd11x.js.map} +0 -0
- /package/dist/cli/{chunk-ye9zdkgv.js.map → chunk-exeeb35e.js.map} +0 -0
- /package/dist/cli/{chunk-n0nyat6g.js.map → chunk-jbj4qhfw.js.map} +0 -0
- /package/dist/cli/{chunk-ev67ycx0.js.map → chunk-jq5n4avg.js.map} +0 -0
- /package/dist/cli/{chunk-x66c5yjn.js.map → chunk-ppfvdcd4.js.map} +0 -0
- /package/dist/cli/{chunk-wd27zjcz.js.map → chunk-q4rae3bg.js.map} +0 -0
- /package/dist/cli/{chunk-pxj10x8y.js.map → chunk-ra1v2nc2.js.map} +0 -0
- /package/dist/cli/{chunk-j6pxe0dt.js.map → chunk-tqa1s0k8.js.map} +0 -0
- /package/dist/cli/{chunk-cbjnx4s8.js.map → chunk-vh9w1sgp.js.map} +0 -0
- /package/dist/cli/{chunk-sbdqrjbb.js.map → chunk-vrfp10qk.js.map} +0 -0
- /package/dist/cli/{chunk-5hs6gb7n.js.map → chunk-yzhm0j9q.js.map} +0 -0
package/src/astro/templates.ts
CHANGED
|
@@ -9,7 +9,7 @@ import type { AskBackend } from "../ai/ask.ts";
|
|
|
9
9
|
import { buildHomeLinkHeader } from "../ai/link-headers.ts";
|
|
10
10
|
import { normalizeBasePath } from "../core/base-path.ts";
|
|
11
11
|
import { TOC_HIDDEN_KEY } from "../core/heading-markers.ts";
|
|
12
|
-
import type { ResolvedConfig } from "../core/schema.ts";
|
|
12
|
+
import type { AskReasoning, ResolvedConfig } from "../core/schema.ts";
|
|
13
13
|
import { BLUME_IGNORE_DIRS } from "../core/sources/watch.ts";
|
|
14
14
|
import { trimChar } from "../core/trim.ts";
|
|
15
15
|
import type { ProjectContext } from "../core/types.ts";
|
|
@@ -1040,12 +1040,58 @@ const ASK_FALLBACK_PROMPT =
|
|
|
1040
1040
|
|
|
1041
1041
|
/** The `ai.ask` values the generated endpoint has to carry with it. */
|
|
1042
1042
|
export interface AskEndpointOptions {
|
|
1043
|
+
/** `ai.ask.cors` — origins allowed to call the route from another site. */
|
|
1044
|
+
cors?: string[];
|
|
1043
1045
|
/** `ai.ask.instructions` — extra system-prompt text. */
|
|
1044
1046
|
instructions?: string;
|
|
1047
|
+
/**
|
|
1048
|
+
* `ai.ask.reasoning` — how much the model reasons before answering, sent
|
|
1049
|
+
* as the backend's own reasoning-effort control.
|
|
1050
|
+
*/
|
|
1051
|
+
reasoning?: AskReasoning;
|
|
1045
1052
|
/** `ai.ask.retrieval` — how much documentation each question carries. */
|
|
1046
1053
|
retrieval?: AskRetrievalOptions;
|
|
1047
1054
|
}
|
|
1048
1055
|
|
|
1056
|
+
/** The pieces `askEndpointTemplate` splices in for `ai.ask.cors`. */
|
|
1057
|
+
interface AskCorsTemplate {
|
|
1058
|
+
/** The route's closing token: `});` when the POST is wrapped, `};` otherwise. */
|
|
1059
|
+
close: string;
|
|
1060
|
+
/** The runtime import, when anything is listed. */
|
|
1061
|
+
imports: string[];
|
|
1062
|
+
/** The opening of `export const POST: APIRoute = `. */
|
|
1063
|
+
open: string;
|
|
1064
|
+
/** The allow list and the `OPTIONS` handler, spliced after the provider setup. */
|
|
1065
|
+
setup: string;
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
/**
|
|
1069
|
+
* `ai.ask.cors`: a browser only lets another origin read the stream when the
|
|
1070
|
+
* response names that origin, and a JSON POST preflights first, so the route
|
|
1071
|
+
* answers `OPTIONS` and wraps the `POST` in `withCors`, which stamps a listed
|
|
1072
|
+
* origin on every response — errors included, so a cross-origin caller can
|
|
1073
|
+
* tell a 400 from a 500 — without each `return` having to remember to.
|
|
1074
|
+
* Unlisted origins get no allow header and stay subject to the same-origin
|
|
1075
|
+
* rule. Left out entirely when nothing is listed, so the default route is
|
|
1076
|
+
* unchanged.
|
|
1077
|
+
*/
|
|
1078
|
+
const askCorsTemplate = (cors: readonly string[] = []): AskCorsTemplate =>
|
|
1079
|
+
cors.length > 0
|
|
1080
|
+
? {
|
|
1081
|
+
close: "});",
|
|
1082
|
+
imports: [
|
|
1083
|
+
'import { preflightResponse, withCors } from "blume/ai/cors.ts";',
|
|
1084
|
+
],
|
|
1085
|
+
open: "withCors(ALLOWED_ORIGINS, async ({ request }) => {",
|
|
1086
|
+
setup: `
|
|
1087
|
+
const ALLOWED_ORIGINS = ${JSON.stringify(cors)};
|
|
1088
|
+
|
|
1089
|
+
export const OPTIONS: APIRoute = ({ request }) =>
|
|
1090
|
+
preflightResponse(request, ALLOWED_ORIGINS);
|
|
1091
|
+
`,
|
|
1092
|
+
}
|
|
1093
|
+
: { close: "};", imports: [], open: "async ({ request }) => {", setup: "" };
|
|
1094
|
+
|
|
1049
1095
|
/**
|
|
1050
1096
|
* Generate the Ask AI server endpoint (`.blume/src/pages/api/ask.ts`).
|
|
1051
1097
|
*
|
|
@@ -1053,15 +1099,24 @@ export interface AskEndpointOptions {
|
|
|
1053
1099
|
* built-in prompt on every path: the grounded prompt via `createAskContext`,
|
|
1054
1100
|
* and the plain fallback here. `options.retrieval` (the `ai.ask.retrieval`
|
|
1055
1101
|
* config) is forwarded to `createAskContext` on the grounded path, where it
|
|
1056
|
-
* sizes retrieval.
|
|
1057
|
-
*
|
|
1102
|
+
* sizes retrieval. `options.reasoning` (the `ai.ask.reasoning` config)
|
|
1103
|
+
* reaches the model call on both paths. `options.cors` (the `ai.ask.cors`
|
|
1104
|
+
* config) adds a preflight handler and wraps the `POST` so every response
|
|
1105
|
+
* names a listed origin. All four travel in one options object so a new call
|
|
1106
|
+
* site can't silently drop one of them.
|
|
1058
1107
|
*/
|
|
1059
1108
|
export const askEndpointTemplate = (
|
|
1060
1109
|
backend: AskBackend,
|
|
1061
1110
|
grounded: boolean,
|
|
1062
1111
|
options?: AskEndpointOptions
|
|
1063
1112
|
): string => {
|
|
1064
|
-
const instructions = options
|
|
1113
|
+
const { instructions, reasoning, retrieval } = options ?? {};
|
|
1114
|
+
// `ai.ask.reasoning`. The gateway and OpenAI-compatible providers take it
|
|
1115
|
+
// from `streamText`'s top-level `reasoning` (the gateway maps it to the
|
|
1116
|
+
// model's own control, the OpenAI-compatible provider sends it as
|
|
1117
|
+
// `reasoning_effort`). OpenRouter's provider ignores that call option and
|
|
1118
|
+
// only reads its own model setting, so there the level rides on the model
|
|
1119
|
+
// as `reasoning.effort`. Omitted keeps the provider default on every path.
|
|
1065
1120
|
const fallbackPrompt = instructions
|
|
1066
1121
|
? `${ASK_FALLBACK_PROMPT}\n\n${instructions}`
|
|
1067
1122
|
: ASK_FALLBACK_PROMPT;
|
|
@@ -1080,24 +1135,34 @@ export const askEndpointTemplate = (
|
|
|
1080
1135
|
];
|
|
1081
1136
|
let setup = "";
|
|
1082
1137
|
let modelExpr = JSON.stringify(backend.model);
|
|
1138
|
+
// `ai.ask.headers`, inlined as literals: every provider factory below takes
|
|
1139
|
+
// the same `headers` option, so one line serves all three.
|
|
1140
|
+
const headersField = backend.headers
|
|
1141
|
+
? `\n headers: ${JSON.stringify(backend.headers)},`
|
|
1142
|
+
: "";
|
|
1083
1143
|
if (backend.kind === "gateway") {
|
|
1084
|
-
setup = `\nconst gateway = createGateway({
|
|
1144
|
+
setup = `\nconst gateway = createGateway({
|
|
1145
|
+
apiKey: getSecret("AI_GATEWAY_API_KEY"),${headersField}
|
|
1146
|
+
});\n`;
|
|
1085
1147
|
modelExpr = `gateway(${JSON.stringify(backend.model)})`;
|
|
1086
1148
|
} else if (backend.kind === "openrouter") {
|
|
1087
1149
|
imports.push(
|
|
1088
1150
|
'import { createOpenRouter } from "@openrouter/ai-sdk-provider";'
|
|
1089
1151
|
);
|
|
1090
|
-
setup = `\nconst openrouter = createOpenRouter({
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1152
|
+
setup = `\nconst openrouter = createOpenRouter({
|
|
1153
|
+
apiKey: getSecret(${JSON.stringify(backend.apiKeyEnv)}),${headersField}
|
|
1154
|
+
});\n`;
|
|
1155
|
+
const settings = reasoning
|
|
1156
|
+
? `, { reasoning: { effort: ${JSON.stringify(reasoning)} } }`
|
|
1157
|
+
: "";
|
|
1158
|
+
modelExpr = `openrouter(${JSON.stringify(backend.model)}${settings})`;
|
|
1094
1159
|
} else if (backend.kind === "openai-compatible") {
|
|
1095
1160
|
imports.push(
|
|
1096
1161
|
'import { createOpenAICompatible } from "@ai-sdk/openai-compatible";'
|
|
1097
1162
|
);
|
|
1098
1163
|
setup = `\nconst provider = createOpenAICompatible({
|
|
1099
1164
|
apiKey: getSecret(${JSON.stringify(backend.apiKeyEnv)}),
|
|
1100
|
-
baseURL: ${JSON.stringify(backend.baseUrl)}
|
|
1165
|
+
baseURL: ${JSON.stringify(backend.baseUrl)},${headersField}
|
|
1101
1166
|
name: ${JSON.stringify(backend.name)},
|
|
1102
1167
|
});\n`;
|
|
1103
1168
|
modelExpr = `provider(${JSON.stringify(backend.model)})`;
|
|
@@ -1113,13 +1178,15 @@ export const askEndpointTemplate = (
|
|
|
1113
1178
|
if (instructions) {
|
|
1114
1179
|
groundFields.push(`instructions: ${JSON.stringify(instructions)}`);
|
|
1115
1180
|
}
|
|
1116
|
-
if (
|
|
1117
|
-
groundFields.push(`retrieval: ${JSON.stringify(
|
|
1181
|
+
if (retrieval) {
|
|
1182
|
+
groundFields.push(`retrieval: ${JSON.stringify(retrieval)}`);
|
|
1118
1183
|
}
|
|
1119
1184
|
const groundOptions =
|
|
1120
1185
|
groundFields.length > 0 ? `, { ${groundFields.join(", ")} }` : "";
|
|
1121
1186
|
setup += `\nconst ground = createAskContext(askData${groundOptions});\n`;
|
|
1122
1187
|
}
|
|
1188
|
+
const cors = askCorsTemplate(options?.cors);
|
|
1189
|
+
imports.push(...cors.imports);
|
|
1123
1190
|
// Validate the client-supplied body and cap its size. The endpoint is
|
|
1124
1191
|
// unauthenticated, so bounding message count/length limits how much a caller
|
|
1125
1192
|
// can spend against the model per request, and restricting roles to
|
|
@@ -1176,24 +1243,29 @@ export const askEndpointTemplate = (
|
|
|
1176
1243
|
const onError = ` onError({ error }) {
|
|
1177
1244
|
console.error("Ask AI provider error:", error);
|
|
1178
1245
|
},`;
|
|
1246
|
+
// The `streamText` argument list, built once so the grounded and plain
|
|
1247
|
+
// paths can't drift: they differ only in where the instructions come from.
|
|
1248
|
+
const streamFields = [
|
|
1249
|
+
`model: ${modelExpr}`,
|
|
1250
|
+
grounded
|
|
1251
|
+
? "instructions"
|
|
1252
|
+
: `instructions:\n ${JSON.stringify(fallbackPrompt)}`,
|
|
1253
|
+
"messages",
|
|
1254
|
+
];
|
|
1255
|
+
if (reasoning && backend.kind !== "openrouter") {
|
|
1256
|
+
streamFields.push(`reasoning: ${JSON.stringify(reasoning)}`);
|
|
1257
|
+
}
|
|
1258
|
+
const call = ` const result = streamText({
|
|
1259
|
+
${streamFields.join(",\n ")},
|
|
1260
|
+
${onError}
|
|
1261
|
+
});`;
|
|
1179
1262
|
const stream = grounded
|
|
1180
1263
|
? ` const instructions =
|
|
1181
1264
|
(await ground(messages, body.page)) ??
|
|
1182
1265
|
${JSON.stringify(fallbackPrompt)};
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
messages,
|
|
1187
|
-
${onError}
|
|
1188
|
-
});`
|
|
1189
|
-
: ` const result = streamText({
|
|
1190
|
-
model: ${modelExpr},
|
|
1191
|
-
instructions:
|
|
1192
|
-
${JSON.stringify(fallbackPrompt)},
|
|
1193
|
-
messages,
|
|
1194
|
-
${onError}
|
|
1195
|
-
});`;
|
|
1196
|
-
const handler = `export const POST: APIRoute = async ({ request }) => {
|
|
1266
|
+
${call}`
|
|
1267
|
+
: call;
|
|
1268
|
+
const handler = `export const POST: APIRoute = ${cors.open}
|
|
1197
1269
|
${validate}
|
|
1198
1270
|
${keyCheck}
|
|
1199
1271
|
try {
|
|
@@ -1202,12 +1274,12 @@ ${stream}
|
|
|
1202
1274
|
} catch {
|
|
1203
1275
|
return new Response("Failed to generate a response.", { status: 500 });
|
|
1204
1276
|
}
|
|
1205
|
-
}
|
|
1277
|
+
${cors.close}`;
|
|
1206
1278
|
return `// Generated by Blume. Do not edit.
|
|
1207
1279
|
${imports.join("\n")}
|
|
1208
1280
|
|
|
1209
1281
|
export const prerender = false;
|
|
1210
|
-
${setup}
|
|
1282
|
+
${setup}${cors.setup}
|
|
1211
1283
|
${handler}
|
|
1212
1284
|
`;
|
|
1213
1285
|
};
|
|
@@ -1692,15 +1764,22 @@ export function GET({ props }: { props: { section: string } }) {
|
|
|
1692
1764
|
* every collapsed section on every page. Ids are `navGroupIds` over the full
|
|
1693
1765
|
* tree, the same ids the pages' sidebars use; the `version`/`locale`
|
|
1694
1766
|
* segments pick the navigation variant (`current`/`default` for the
|
|
1695
|
-
* unversioned, unlocalized tree
|
|
1696
|
-
*
|
|
1767
|
+
* unversioned, unlocalized tree, and `default` again for the default locale
|
|
1768
|
+
* while its URL prefix is hidden: Astro's i18n routing 404s a page URL that
|
|
1769
|
+
* carries the default locale's code as a segment). Rendered with no current
|
|
1770
|
+
* route — a section that isn't on the active path has no active row by
|
|
1771
|
+
* definition.
|
|
1697
1772
|
*/
|
|
1698
1773
|
export const navFragmentTemplate = (): string =>
|
|
1699
1774
|
`---
|
|
1700
1775
|
// Generated by Blume. Do not edit.
|
|
1701
1776
|
import NavTree from "blume/components/layout/NavTree.astro";
|
|
1702
1777
|
import { withBase } from "blume/components/islands/base-path.ts";
|
|
1703
|
-
import {
|
|
1778
|
+
import {
|
|
1779
|
+
hiddenDefaultLocale,
|
|
1780
|
+
navGroupIds,
|
|
1781
|
+
navVariants,
|
|
1782
|
+
} from "blume/components/layout/nav-utils.ts";
|
|
1704
1783
|
import type { NavNode } from "blume/core/types.ts";
|
|
1705
1784
|
import data from "blume:data";
|
|
1706
1785
|
|
|
@@ -1711,15 +1790,16 @@ export const partial = true;
|
|
|
1711
1790
|
// Only imports are in scope here (Astro hoists getStaticPaths), which is why
|
|
1712
1791
|
// the variant walk lives in nav-utils.
|
|
1713
1792
|
export function getStaticPaths() {
|
|
1714
|
-
return navVariants(data).flatMap(
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1793
|
+
return navVariants(data, hiddenDefaultLocale(data.config.i18n)).flatMap(
|
|
1794
|
+
({ locale, navigation, version }) =>
|
|
1795
|
+
[...navGroupIds(navigation.sidebar)]
|
|
1796
|
+
.filter(([node]) => (node.display ?? "flat") !== "flat")
|
|
1797
|
+
.map(([, id]) => ({ params: { id, locale, version } }))
|
|
1718
1798
|
);
|
|
1719
1799
|
}
|
|
1720
1800
|
|
|
1721
1801
|
const { id, locale, version } = Astro.params;
|
|
1722
|
-
const variant = navVariants(data).find(
|
|
1802
|
+
const variant = navVariants(data, hiddenDefaultLocale(data.config.i18n)).find(
|
|
1723
1803
|
(entry) => entry.version === version && entry.locale === locale
|
|
1724
1804
|
);
|
|
1725
1805
|
const ids = variant
|
|
@@ -1933,37 +2013,22 @@ const htmlLang = i18n ? i18n.defaultLocale : "en";
|
|
|
1933
2013
|
</ReferenceLayout>
|
|
1934
2014
|
`;
|
|
1935
2015
|
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
needsReact: boolean;
|
|
1948
|
-
}): string => {
|
|
1949
|
-
const mathImport = options.mathEnabled
|
|
2016
|
+
/**
|
|
2017
|
+
* The component map every rendered entry body receives via
|
|
2018
|
+
* `<Content components={...} />`: Blume's content components, `<Math>` when
|
|
2019
|
+
* math is enabled, the island wrappers, and the user's `mdxComponents`
|
|
2020
|
+
* overrides. Shared by the catch-all page and the changelog index so a
|
|
2021
|
+
* `:::` directive (compiled to `<Callout>`) or an explicit `<Steps>` renders
|
|
2022
|
+
* wherever an entry is rendered — an MDX body throws "Expected component
|
|
2023
|
+
* `X` to be defined" the moment a page renders it without this map.
|
|
2024
|
+
*/
|
|
2025
|
+
const contentComponentsSource = (mathEnabled: boolean) => {
|
|
2026
|
+
const mathImport = mathEnabled
|
|
1950
2027
|
? 'import Math from "blume/components/content/Math.astro";\n'
|
|
1951
2028
|
: "";
|
|
1952
|
-
const mathEntry =
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
? "\n clientData={{ config: data.config, navigation, page: { route, title: seo.title ?? title } }}"
|
|
1956
|
-
: "";
|
|
1957
|
-
|
|
1958
|
-
return `---
|
|
1959
|
-
// Generated by Blume. Do not edit.
|
|
1960
|
-
import { getEntry, render } from "astro:content";
|
|
1961
|
-
import type { CollectionKey } from "astro:content";
|
|
1962
|
-
import RootLayout from "blume/components/layout/RootLayout.astro";
|
|
1963
|
-
import { withBase } from "blume/components/islands/base-path.ts";
|
|
1964
|
-
import { stripBasePath, withBasePath } from "blume/core/base-path.ts";
|
|
1965
|
-
import { resolveSlot } from "blume/components/layout/overrides.ts";
|
|
1966
|
-
import Accordion from "blume/components/content/Accordion.astro";
|
|
2029
|
+
const mathEntry = mathEnabled ? "Math,\n " : "";
|
|
2030
|
+
return {
|
|
2031
|
+
imports: `import Accordion from "blume/components/content/Accordion.astro";
|
|
1967
2032
|
import AccordionItem from "blume/components/content/AccordionItem.astro";
|
|
1968
2033
|
import AutoTypeTable from "blume/components/content/AutoTypeTable.astro";
|
|
1969
2034
|
import Badge from "blume/components/content/Badge.astro";
|
|
@@ -2003,19 +2068,8 @@ import ApiOverview from "blume/components/openapi/ApiOverview.astro";
|
|
|
2003
2068
|
import ApiTagOperations from "blume/components/openapi/ApiTagOperations.astro";
|
|
2004
2069
|
import Operation from "blume/components/openapi/Operation.astro";
|
|
2005
2070
|
${mathImport}import { mdxComponents as userMdx, layoutOverrides } from "../generated/components.ts";
|
|
2006
|
-
import { islandComponents } from "../generated/islands.ts"
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
const Color = Object.assign(ColorRoot, { Item: ColorItem, Row: ColorRow });
|
|
2010
|
-
const Tree = Object.assign(TreeRoot, { File: TreeFile, Folder: TreeFolder });
|
|
2011
|
-
|
|
2012
|
-
// Docs content is file-based and always prerendered, even in server output
|
|
2013
|
-
// (where only endpoints like /api/ask render on demand). Without this, server
|
|
2014
|
-
// builds would render this route on demand and ignore getStaticPaths, leaving
|
|
2015
|
-
// the entry id undefined.
|
|
2016
|
-
export const prerender = true;
|
|
2017
|
-
|
|
2018
|
-
const components = {
|
|
2071
|
+
import { islandComponents } from "../generated/islands.ts";`,
|
|
2072
|
+
map: `{
|
|
2019
2073
|
Accordion,
|
|
2020
2074
|
AccordionItem,
|
|
2021
2075
|
ApiOverview,
|
|
@@ -2052,8 +2106,52 @@ const components = {
|
|
|
2052
2106
|
YouTube,
|
|
2053
2107
|
${mathEntry}...islandComponents,
|
|
2054
2108
|
...userMdx,
|
|
2109
|
+
}`,
|
|
2110
|
+
};
|
|
2055
2111
|
};
|
|
2056
2112
|
|
|
2113
|
+
export const catchAllPageTemplate = (options: {
|
|
2114
|
+
exportEpub: boolean;
|
|
2115
|
+
exportPdf: boolean;
|
|
2116
|
+
mathEnabled: boolean;
|
|
2117
|
+
/**
|
|
2118
|
+
* Whether the sidebar defers collapsed sections to prerendered fragments
|
|
2119
|
+
* (see `navFragmentTemplate`); true when some group renders as a
|
|
2120
|
+
* disclosure or a drill-in panel.
|
|
2121
|
+
*/
|
|
2122
|
+
navFragments?: boolean;
|
|
2123
|
+
/** Serialize the island-hooks snapshot; only needed when React is enabled. */
|
|
2124
|
+
needsReact: boolean;
|
|
2125
|
+
}): string => {
|
|
2126
|
+
const { imports: componentImports, map: componentMap } =
|
|
2127
|
+
contentComponentsSource(options.mathEnabled);
|
|
2128
|
+
// The island-hooks snapshot (config + navigation + page) for `blume/hooks`.
|
|
2129
|
+
const clientData = options.needsReact
|
|
2130
|
+
? "\n clientData={{ config: data.config, navigation, page: { route, title: seo.title ?? title } }}"
|
|
2131
|
+
: "";
|
|
2132
|
+
|
|
2133
|
+
return `---
|
|
2134
|
+
// Generated by Blume. Do not edit.
|
|
2135
|
+
import { getEntry, render } from "astro:content";
|
|
2136
|
+
import type { CollectionKey } from "astro:content";
|
|
2137
|
+
import RootLayout from "blume/components/layout/RootLayout.astro";
|
|
2138
|
+
import { withBase } from "blume/components/islands/base-path.ts";
|
|
2139
|
+
import { stripBasePath, withBasePath } from "blume/core/base-path.ts";
|
|
2140
|
+
import { resolveSlot } from "blume/components/layout/overrides.ts";
|
|
2141
|
+
${componentImports}
|
|
2142
|
+
import data from "blume:data";
|
|
2143
|
+
|
|
2144
|
+
const Color = Object.assign(ColorRoot, { Item: ColorItem, Row: ColorRow });
|
|
2145
|
+
const Tree = Object.assign(TreeRoot, { File: TreeFile, Folder: TreeFolder });
|
|
2146
|
+
|
|
2147
|
+
// Docs content is file-based and always prerendered, even in server output
|
|
2148
|
+
// (where only endpoints like /api/ask render on demand). Without this, server
|
|
2149
|
+
// builds would render this route on demand and ignore getStaticPaths, leaving
|
|
2150
|
+
// the entry id undefined.
|
|
2151
|
+
export const prerender = true;
|
|
2152
|
+
|
|
2153
|
+
const components = ${componentMap};
|
|
2154
|
+
|
|
2057
2155
|
export function getStaticPaths() {
|
|
2058
2156
|
return data.routes.map((route) => ({
|
|
2059
2157
|
params: { slug: route.path === "/" ? undefined : route.path.slice(1) },
|
|
@@ -2324,7 +2422,7 @@ const LayoutComponent = resolveSlot(layoutOverrides.Layout, RootLayout);
|
|
|
2324
2422
|
exportEpub={${options.exportEpub}}${
|
|
2325
2423
|
options.navFragments
|
|
2326
2424
|
? `
|
|
2327
|
-
navFragmentBase={withBase(\`/blume-nav/\${version || "current"}/\${i18n ? locale : "default"}\`)}`
|
|
2425
|
+
navFragmentBase={withBase(\`/blume-nav/\${version || "current"}/\${i18n && localePrefix(locale) ? locale : "default"}\`)}`
|
|
2328
2426
|
: ""
|
|
2329
2427
|
}
|
|
2330
2428
|
openInChat={data.config.openInChat}
|
|
@@ -2355,6 +2453,7 @@ const LayoutComponent = resolveSlot(layoutOverrides.Layout, RootLayout);
|
|
|
2355
2453
|
export const changelogIndexTemplate = (options: {
|
|
2356
2454
|
exportEpub: boolean;
|
|
2357
2455
|
exportPdf: boolean;
|
|
2456
|
+
mathEnabled: boolean;
|
|
2358
2457
|
/** Serialize the island-hooks snapshot; only needed when React is enabled. */
|
|
2359
2458
|
needsReact: boolean;
|
|
2360
2459
|
/** Whether a `staged` collection exists (non-filesystem changelog sources). */
|
|
@@ -2368,6 +2467,8 @@ export const changelogIndexTemplate = (options: {
|
|
|
2368
2467
|
const stagedSpread = options.staged
|
|
2369
2468
|
? '\n ...(await getCollection("staged")),'
|
|
2370
2469
|
: "";
|
|
2470
|
+
const { imports: componentImports, map: componentMap } =
|
|
2471
|
+
contentComponentsSource(options.mathEnabled);
|
|
2371
2472
|
|
|
2372
2473
|
return `---
|
|
2373
2474
|
// Generated by Blume. Do not edit.
|
|
@@ -2377,11 +2478,19 @@ import Update from "blume/components/content/Update.astro";
|
|
|
2377
2478
|
import { withBase } from "blume/components/islands/base-path.ts";
|
|
2378
2479
|
import { resolveSlot } from "blume/components/layout/overrides.ts";
|
|
2379
2480
|
import { resolveDateFormatOptions } from "blume/core/date-format.ts";
|
|
2380
|
-
|
|
2481
|
+
${componentImports}
|
|
2381
2482
|
import data from "blume:data";
|
|
2382
2483
|
|
|
2484
|
+
const Color = Object.assign(ColorRoot, { Item: ColorItem, Row: ColorRow });
|
|
2485
|
+
const Tree = Object.assign(TreeRoot, { File: TreeFile, Folder: TreeFolder });
|
|
2486
|
+
|
|
2383
2487
|
export const prerender = true;
|
|
2384
2488
|
|
|
2489
|
+
// Entry bodies are MDX rendered outside the catch-all, so they need the same
|
|
2490
|
+
// component map: a \`:::\` callout or \`<Steps>\` in a release note otherwise
|
|
2491
|
+
// throws "Expected component ... to be defined" at build time.
|
|
2492
|
+
const components = ${componentMap};
|
|
2493
|
+
|
|
2385
2494
|
const entryDate = (entry: {
|
|
2386
2495
|
data: { date?: string | null; changelog?: { date?: string | null } | null };
|
|
2387
2496
|
}) => entry.data.date ?? entry.data.changelog?.date ?? null;
|
|
@@ -2580,7 +2689,7 @@ const LayoutComponent = resolveSlot(layoutOverrides.Layout, RootLayout);
|
|
|
2580
2689
|
>
|
|
2581
2690
|
{majorGroups[0].items.map(({ Content, href, id, label, date, tags }) => (
|
|
2582
2691
|
<Update description={date} href={href} id={id} label={label} tags={tags}>
|
|
2583
|
-
<Content />
|
|
2692
|
+
<Content components={components} />
|
|
2584
2693
|
</Update>
|
|
2585
2694
|
))}
|
|
2586
2695
|
{majorGroups.slice(1).map((group) => (
|
|
@@ -2591,7 +2700,7 @@ const LayoutComponent = resolveSlot(layoutOverrides.Layout, RootLayout);
|
|
|
2591
2700
|
>
|
|
2592
2701
|
{group.items.map(({ Content, href, id, label, date, tags }) => (
|
|
2593
2702
|
<Update description={date} href={href} id={id} label={label} tags={tags}>
|
|
2594
|
-
<Content />
|
|
2703
|
+
<Content components={components} />
|
|
2595
2704
|
</Update>
|
|
2596
2705
|
))}
|
|
2597
2706
|
</section>
|
|
@@ -2611,7 +2720,7 @@ const LayoutComponent = resolveSlot(layoutOverrides.Layout, RootLayout);
|
|
|
2611
2720
|
<div class="not-prose mt-8">
|
|
2612
2721
|
{items.map(({ Content, href, id, label, date, tags }) => (
|
|
2613
2722
|
<Update description={date} href={href} id={id} label={label} tags={tags}>
|
|
2614
|
-
<Content />
|
|
2723
|
+
<Content components={components} />
|
|
2615
2724
|
</Update>
|
|
2616
2725
|
))}
|
|
2617
2726
|
</div>
|
|
@@ -20,7 +20,7 @@ const src = videoId ? youtubeEmbedUrl(videoId, { start }) : null;
|
|
|
20
20
|
|
|
21
21
|
{
|
|
22
22
|
src && (
|
|
23
|
-
<div class="not-prose my-6 aspect-video overflow-hidden rounded-blume border border-border bg-muted/30">
|
|
23
|
+
<div class="not-prose my-6 aspect-video w-full overflow-hidden rounded-blume border border-border bg-muted/30">
|
|
24
24
|
<iframe
|
|
25
25
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
|
26
26
|
allowfullscreen
|
|
@@ -150,7 +150,7 @@ const ctaClass = [
|
|
|
150
150
|
// so the offset can't drift while open, and re-measure when a banner dismiss or
|
|
151
151
|
// a resize/rotation moves the header. Crossing into the desktop breakpoint
|
|
152
152
|
// (64rem, Tailwind `lg`) closes the drawer so the scroll lock can't outlive it.
|
|
153
|
-
const clickScript = `(()=>{const dr=()=>{const h=document.querySelector("[data-blume-header]");if(h){document.documentElement.style.setProperty("--blume-drawer-top",h.getBoundingClientRect().bottom+"px");}};document.addEventListener("click",(e)=>{const t=e.target.closest("[data-blume-theme-toggle]");if(t){const d=document.documentElement;const next=d.dataset.theme==="dark"?"light":"dark";const s=document.createElement("style");s.appendChild(document.createTextNode("*,*::before,*::after{transition:none!important}"));document.head.appendChild(s);d.dataset.theme=next;localStorage.setItem("blume-theme",next);window.getComputedStyle(d).opacity;setTimeout(()=>document.head.removeChild(s),1);}const n=e.target.closest("[data-blume-nav-toggle]");if(n){const d=document.documentElement;const open=d.toggleAttribute("data-blume-nav-open");if(open){dr();}
|
|
153
|
+
const clickScript = `(()=>{const dr=()=>{const h=document.querySelector("[data-blume-header]");if(h){document.documentElement.style.setProperty("--blume-drawer-top",h.getBoundingClientRect().bottom+"px");}};document.addEventListener("click",(e)=>{const t=e.target.closest("[data-blume-theme-toggle]");if(t){const d=document.documentElement;const next=d.dataset.theme==="dark"?"light":"dark";const s=document.createElement("style");s.appendChild(document.createTextNode("*,*::before,*::after{transition:none!important}"));document.head.appendChild(s);d.dataset.theme=next;localStorage.setItem("blume-theme",next);window.getComputedStyle(d).opacity;setTimeout(()=>document.head.removeChild(s),1);}const n=e.target.closest("[data-blume-nav-toggle]");if(n){const d=document.documentElement;const open=d.toggleAttribute("data-blume-nav-open");if(open){dr();}}const b=e.target.closest("[data-blume-banner-dismiss]");if(b){const bar=b.closest("[data-blume-banner]");const k=bar&&bar.getAttribute("data-banner-key");if(k){localStorage.setItem("blume-banner:"+k,"1");}document.documentElement.setAttribute("data-blume-banner-hidden","");requestAnimationFrame(dr);}});addEventListener("resize",()=>{const d=document.documentElement;if(!d.hasAttribute("data-blume-nav-open")){return;}if(matchMedia("(min-width:64rem)").matches){d.removeAttribute("data-blume-nav-open");}else{dr();}});})();`;
|
|
154
154
|
---
|
|
155
155
|
|
|
156
156
|
<header
|