blume 2.0.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +28 -0
  3. package/README.md +2 -2
  4. package/dist/cli/{chunk-f7t03s3g.js → chunk-27g6wdth.js} +2 -2
  5. package/dist/cli/{chunk-mnqj32sj.js → chunk-2hn4b8z7.js} +13 -13
  6. package/dist/cli/chunk-2hn4b8z7.js.map +12 -0
  7. package/dist/cli/{chunk-by2290sx.js → chunk-5shv93fd.js} +2 -2
  8. package/dist/cli/{chunk-a9kptbw5.js → chunk-6crbhc3x.js} +3 -3
  9. package/dist/cli/{chunk-a9kptbw5.js.map → chunk-6crbhc3x.js.map} +1 -1
  10. package/dist/cli/{chunk-mwt1k8n7.js → chunk-6hsn950k.js} +20 -20
  11. package/dist/cli/chunk-6hsn950k.js.map +10 -0
  12. package/dist/cli/{chunk-11j0384y.js → chunk-6vm74dry.js} +13 -13
  13. package/dist/cli/{chunk-11j0384y.js.map → chunk-6vm74dry.js.map} +3 -3
  14. package/dist/cli/{chunk-j8mw0za6.js → chunk-79jhk4py.js} +8 -8
  15. package/dist/cli/{chunk-j8mw0za6.js.map → chunk-79jhk4py.js.map} +2 -2
  16. package/dist/cli/{chunk-nk3ts2xk.js → chunk-82bbrxdn.js} +2 -2
  17. package/dist/cli/{chunk-2q1dwty4.js → chunk-ah61y8py.js} +8 -8
  18. package/dist/cli/{chunk-2q1dwty4.js.map → chunk-ah61y8py.js.map} +4 -4
  19. package/dist/cli/{chunk-zxccj738.js → chunk-ce574jw2.js} +1 -1
  20. package/dist/cli/{chunk-y3e45rc8.js → chunk-ch6g3ar0.js} +3 -3
  21. package/dist/cli/{chunk-beat36xx.js → chunk-dh8cwk36.js} +5 -5
  22. package/dist/cli/{chunk-beat36xx.js.map → chunk-dh8cwk36.js.map} +2 -2
  23. package/dist/cli/{chunk-1w8dp3qb.js → chunk-epjnccmv.js} +13 -13
  24. package/dist/cli/{chunk-ernrthtr.js → chunk-f2z5v128.js} +13 -13
  25. package/dist/cli/{chunk-zg2gtj10.js → chunk-fs23ddbb.js} +2 -2
  26. package/dist/cli/{chunk-7ez8ny0t.js → chunk-fxypxtvm.js} +2 -2
  27. package/dist/cli/{chunk-tzne8qfq.js → chunk-fz5wtpmh.js} +13 -13
  28. package/dist/cli/{chunk-b5aj94ah.js → chunk-hdpx1tax.js} +4 -4
  29. package/dist/cli/{chunk-d80hr03s.js → chunk-jwyddg7y.js} +9 -9
  30. package/dist/cli/{chunk-d80hr03s.js.map → chunk-jwyddg7y.js.map} +2 -2
  31. package/dist/cli/{chunk-fh5hj5jt.js → chunk-kdp5q7ke.js} +15 -15
  32. package/dist/cli/{chunk-6k8vp3ta.js → chunk-kpf8rrjc.js} +9 -9
  33. package/dist/cli/{chunk-6k8vp3ta.js.map → chunk-kpf8rrjc.js.map} +3 -3
  34. package/dist/cli/{chunk-5a2z0198.js → chunk-m3vmjgmq.js} +9 -9
  35. package/dist/cli/{chunk-5a2z0198.js.map → chunk-m3vmjgmq.js.map} +2 -2
  36. package/dist/cli/{chunk-bctazmbk.js → chunk-mb2919y2.js} +4 -4
  37. package/dist/cli/{chunk-79njf86q.js → chunk-q5163e60.js} +13 -13
  38. package/dist/cli/{chunk-xaz13gwg.js → chunk-qkqwkpte.js} +196 -208
  39. package/dist/cli/{chunk-xaz13gwg.js.map → chunk-qkqwkpte.js.map} +48 -48
  40. package/dist/cli/{chunk-sqn5t4q0.js → chunk-qs4q5p4e.js} +3 -3
  41. package/dist/cli/{chunk-bw22s759.js → chunk-qwsrynx5.js} +1 -1
  42. package/dist/cli/{chunk-z01ze5c1.js → chunk-s1p84fyh.js} +15 -15
  43. package/dist/cli/{chunk-pnnvybbk.js → chunk-s6jhgk0q.js} +5 -5
  44. package/dist/cli/{chunk-pnnvybbk.js.map → chunk-s6jhgk0q.js.map} +2 -2
  45. package/dist/cli/{chunk-f2972sbt.js → chunk-vtk4a6dg.js} +1 -1
  46. package/dist/cli/{chunk-z1f5arsg.js → chunk-wgm7m9qk.js} +21 -21
  47. package/dist/cli/{chunk-z1f5arsg.js.map → chunk-wgm7m9qk.js.map} +4 -4
  48. package/dist/cli/{chunk-bnbmcwfb.js → chunk-wm7js3j9.js} +5 -5
  49. package/dist/cli/{chunk-bnbmcwfb.js.map → chunk-wm7js3j9.js.map} +3 -3
  50. package/dist/cli/{chunk-d1tadaw7.js → chunk-yt5n7ppj.js} +3 -3
  51. package/dist/cli/{chunk-pat2zzwc.js → chunk-yw7dm696.js} +1 -1
  52. package/dist/cli/{chunk-pat2zzwc.js.map → chunk-yw7dm696.js.map} +1 -1
  53. package/dist/cli/{chunk-88cpgt6h.js → chunk-zxcczpyx.js} +1 -1
  54. package/dist/cli/{chunk-41za066z.js → chunk-zxh4d9vy.js} +4 -4
  55. package/dist/cli/index.js +17 -17
  56. package/dist/types/ai/agent-readability.d.ts +1 -1
  57. package/dist/types/ai/api/paths.d.ts +1 -1
  58. package/dist/types/ai/ask-context.d.ts +7 -7
  59. package/dist/types/ai/ask.d.ts +43 -43
  60. package/dist/types/ai/index.d.ts +3 -3
  61. package/dist/types/ai/openapi-components.d.ts +1 -1
  62. package/dist/types/ai/serializers.d.ts +1 -1
  63. package/dist/types/ai/visibility.d.ts +1 -1
  64. package/dist/types/core/config-input.d.ts +17 -17
  65. package/dist/types/core/config.d.ts +3 -3
  66. package/dist/types/core/data.d.ts +3 -3
  67. package/dist/types/core/i18n-ui.d.ts +6 -8
  68. package/dist/types/core/schema.d.ts +5 -5
  69. package/dist/types/core/unrecognized-keys.d.ts +1 -1
  70. package/dist/types/search/documents.d.ts +1 -1
  71. package/dist/types/search/orama-index.d.ts +1 -1
  72. package/docs/02-deployment.mdx +4 -4
  73. package/docs/03-upgrading.mdx +22 -9
  74. package/docs/04-migrating.mdx +4 -4
  75. package/docs/08-faq.mdx +3 -3
  76. package/docs/advanced/custom-pages.mdx +2 -2
  77. package/docs/advanced/skills.mdx +1 -1
  78. package/docs/cli/audit.mdx +3 -3
  79. package/docs/cli/doctor.mdx +3 -3
  80. package/docs/cli/evals.mdx +7 -7
  81. package/docs/cli/index.mdx +2 -2
  82. package/docs/cli/translate.mdx +8 -8
  83. package/docs/configuration/{ask-ai.mdx → assistant.mdx} +19 -19
  84. package/docs/configuration/customization.mdx +2 -2
  85. package/docs/configuration/index.mdx +2 -2
  86. package/docs/configuration/meta.ts +1 -1
  87. package/docs/configuration/search.mdx +1 -1
  88. package/docs/content/i18n.mdx +2 -2
  89. package/docs/content/islands.mdx +1 -1
  90. package/docs/discoverability/agent-discovery.mdx +1 -1
  91. package/docs/discoverability/index.mdx +1 -1
  92. package/docs/index.mdx +2 -2
  93. package/package.json +1 -1
  94. package/skills/blume/SKILL.md +5 -5
  95. package/skills/blume-migrate/SKILL.md +2 -2
  96. package/src/ai/agent-readability.ts +4 -4
  97. package/src/ai/api/paths.ts +1 -1
  98. package/src/ai/ask-context.ts +7 -7
  99. package/src/ai/ask-data.ts +2 -2
  100. package/src/ai/ask.ts +84 -71
  101. package/src/ai/cors.ts +3 -3
  102. package/src/ai/index.ts +16 -16
  103. package/src/ai/openapi-components.ts +1 -1
  104. package/src/ai/serializers.ts +1 -1
  105. package/src/ai/visibility.ts +1 -1
  106. package/src/astro/generate.ts +19 -18
  107. package/src/astro/module-types.ts +1 -1
  108. package/src/astro/runtime-deps.ts +6 -6
  109. package/src/astro/templates.ts +26 -26
  110. package/src/blume-modules.d.ts +2 -2
  111. package/src/cli/commands/audit.ts +1 -1
  112. package/src/cli/commands/doctor.ts +7 -5
  113. package/src/cli/commands/eval.ts +3 -3
  114. package/src/cli/commands/migrate.ts +2 -2
  115. package/src/cli/commands/translate.ts +3 -3
  116. package/src/cli/commands/upgrade.ts +2 -2
  117. package/src/cli/required-secrets.ts +3 -3
  118. package/src/components/copy-feedback.ts +1 -1
  119. package/src/components/islands/{AskAI.astro → Assistant.astro} +10 -10
  120. package/src/components/islands/{ask-ai.tsx → assistant.tsx} +23 -23
  121. package/src/components/islands/hooks.ts +14 -12
  122. package/src/components/layout/Header.astro +10 -10
  123. package/src/components/layout/PageLayout.astro +6 -6
  124. package/src/components/layout/Pagination.astro +7 -7
  125. package/src/components/layout/ReferenceLayout.astro +1 -1
  126. package/src/components/layout/RootLayout.astro +6 -6
  127. package/src/components/layout/Search.astro +13 -13
  128. package/src/components/layout/analytics-client.ts +1 -1
  129. package/src/components/layout/drawer-inert.ts +1 -1
  130. package/src/components/openapi/description.ts +2 -2
  131. package/src/core/code-fences.ts +1 -1
  132. package/src/core/config-input.ts +19 -19
  133. package/src/core/config.ts +3 -3
  134. package/src/core/data.ts +3 -3
  135. package/src/core/i18n-ui.ts +35 -9
  136. package/src/core/request-body.ts +1 -1
  137. package/src/core/schema.ts +25 -20
  138. package/src/core/server-features.ts +2 -2
  139. package/src/core/ui-packs/ar.ts +4 -5
  140. package/src/core/ui-packs/bg.ts +4 -5
  141. package/src/core/ui-packs/bn.ts +4 -5
  142. package/src/core/ui-packs/ca.ts +4 -5
  143. package/src/core/ui-packs/cs.ts +4 -5
  144. package/src/core/ui-packs/da.ts +4 -5
  145. package/src/core/ui-packs/de.ts +4 -5
  146. package/src/core/ui-packs/el.ts +4 -5
  147. package/src/core/ui-packs/es.ts +4 -5
  148. package/src/core/ui-packs/fa.ts +4 -5
  149. package/src/core/ui-packs/fi.ts +4 -5
  150. package/src/core/ui-packs/fr.ts +4 -5
  151. package/src/core/ui-packs/he.ts +4 -5
  152. package/src/core/ui-packs/hi.ts +4 -5
  153. package/src/core/ui-packs/hr.ts +4 -5
  154. package/src/core/ui-packs/hu.ts +4 -5
  155. package/src/core/ui-packs/id.ts +4 -5
  156. package/src/core/ui-packs/it.ts +4 -5
  157. package/src/core/ui-packs/ja.ts +4 -5
  158. package/src/core/ui-packs/ko.ts +4 -5
  159. package/src/core/ui-packs/nl.ts +4 -5
  160. package/src/core/ui-packs/no.ts +4 -5
  161. package/src/core/ui-packs/pl.ts +4 -5
  162. package/src/core/ui-packs/pt-br.ts +4 -5
  163. package/src/core/ui-packs/pt.ts +4 -5
  164. package/src/core/ui-packs/ro.ts +4 -5
  165. package/src/core/ui-packs/ru.ts +4 -5
  166. package/src/core/ui-packs/sk.ts +4 -5
  167. package/src/core/ui-packs/sr.ts +4 -5
  168. package/src/core/ui-packs/sv.ts +4 -5
  169. package/src/core/ui-packs/th.ts +4 -5
  170. package/src/core/ui-packs/tr.ts +4 -5
  171. package/src/core/ui-packs/uk.ts +4 -5
  172. package/src/core/ui-packs/vi.ts +4 -5
  173. package/src/core/ui-packs/zh-tw.ts +4 -5
  174. package/src/core/ui-packs/zh.ts +4 -5
  175. package/src/core/unrecognized-keys.ts +1 -1
  176. package/src/registry/eject.ts +19 -18
  177. package/src/search/documents.ts +2 -2
  178. package/src/search/orama-index.ts +1 -1
  179. package/src/translate/report.ts +1 -1
  180. package/src/upgrade/upgrade.ts +1 -1
  181. package/dist/cli/chunk-mnqj32sj.js.map +0 -12
  182. package/dist/cli/chunk-mwt1k8n7.js.map +0 -10
  183. /package/dist/cli/{chunk-f7t03s3g.js.map → chunk-27g6wdth.js.map} +0 -0
  184. /package/dist/cli/{chunk-by2290sx.js.map → chunk-5shv93fd.js.map} +0 -0
  185. /package/dist/cli/{chunk-nk3ts2xk.js.map → chunk-82bbrxdn.js.map} +0 -0
  186. /package/dist/cli/{chunk-zxccj738.js.map → chunk-ce574jw2.js.map} +0 -0
  187. /package/dist/cli/{chunk-y3e45rc8.js.map → chunk-ch6g3ar0.js.map} +0 -0
  188. /package/dist/cli/{chunk-1w8dp3qb.js.map → chunk-epjnccmv.js.map} +0 -0
  189. /package/dist/cli/{chunk-ernrthtr.js.map → chunk-f2z5v128.js.map} +0 -0
  190. /package/dist/cli/{chunk-zg2gtj10.js.map → chunk-fs23ddbb.js.map} +0 -0
  191. /package/dist/cli/{chunk-7ez8ny0t.js.map → chunk-fxypxtvm.js.map} +0 -0
  192. /package/dist/cli/{chunk-tzne8qfq.js.map → chunk-fz5wtpmh.js.map} +0 -0
  193. /package/dist/cli/{chunk-b5aj94ah.js.map → chunk-hdpx1tax.js.map} +0 -0
  194. /package/dist/cli/{chunk-fh5hj5jt.js.map → chunk-kdp5q7ke.js.map} +0 -0
  195. /package/dist/cli/{chunk-bctazmbk.js.map → chunk-mb2919y2.js.map} +0 -0
  196. /package/dist/cli/{chunk-79njf86q.js.map → chunk-q5163e60.js.map} +0 -0
  197. /package/dist/cli/{chunk-sqn5t4q0.js.map → chunk-qs4q5p4e.js.map} +0 -0
  198. /package/dist/cli/{chunk-bw22s759.js.map → chunk-qwsrynx5.js.map} +0 -0
  199. /package/dist/cli/{chunk-z01ze5c1.js.map → chunk-s1p84fyh.js.map} +0 -0
  200. /package/dist/cli/{chunk-f2972sbt.js.map → chunk-vtk4a6dg.js.map} +0 -0
  201. /package/dist/cli/{chunk-d1tadaw7.js.map → chunk-yt5n7ppj.js.map} +0 -0
  202. /package/dist/cli/{chunk-88cpgt6h.js.map → chunk-zxcczpyx.js.map} +0 -0
  203. /package/dist/cli/{chunk-41za066z.js.map → chunk-zxh4d9vy.js.map} +0 -0
@@ -1,12 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/astro/component-slots.ts", "../src/astro/runtime-modules.ts", "../src/astro/templates.ts"],
4
- "sourcesContent": [
5
- "import { isAbsolute, join, relative } from \"pathe\";\n\nimport type {\n ComponentOverrideAnalysis,\n NormalizedOverride,\n OverrideFramework,\n} from \"../core/component-overrides.ts\";\nimport type { IslandSpec } from \"./islands.ts\";\n\n/**\n * Turn the project's MDX/layout overrides into the generated `components.ts`\n * module plus per-override hydration wrappers.\n *\n * Every override is imported statically so Vite can see it: a static override\n * imports its file directly, while a hydrated one gets a wrapper `.astro` that\n * applies the `client:*` directive (Astro directives must be written\n * statically). The `islands/` folder convention feeds the same plan — each\n * discovered island is an `mdx` entry with its client mode — so a\n * `components.ts` `mdx` entry of the same name replaces the convention island.\n */\n\nexport interface ComponentSlotWrapper {\n content: string;\n /** File name (no extension) under `.blume/src/generated/component-slots/`. */\n name: string;\n}\n\nexport interface ComponentSlotOptions {\n /**\n * The directory the generated `components.ts` is written to. Set by\n * `blume eject`, whose output has to build from any checkout: every\n * absolute source path is then imported relative to the file importing it.\n * The hidden runtime leaves it unset and keeps absolute paths, since it is\n * regenerated on every run.\n */\n relativeTo?: string;\n}\n\nexport interface ComponentSlotPlan {\n /** Frameworks used by resolved overrides; enable the matching Astro renderer. */\n frameworks: Set<OverrideFramework>;\n /** Contents of `.blume/src/generated/components.ts`. */\n module: string;\n wrappers: ComponentSlotWrapper[];\n}\n\n// A user-supplied attribute value interpolated into a generated .astro tag: a\n// stray quote or newline would produce a malformed component and an opaque\n// Astro parse error pointing at the generated file, not the user's config.\nconst attributeValue = (value: string): string =>\n value.replaceAll(/[\"\\n\\r]/gu, \" \").trim();\n\nconst CLIENT_LOAD = \"client:load\";\n\n/** Astro client directive for a hydrated override. */\nconst directiveFor = (override: NormalizedOverride): string => {\n const { framework } = override.source;\n switch (override.client) {\n case \"idle\": {\n return \"client:idle\";\n }\n case \"visible\": {\n return \"client:visible\";\n }\n case \"media\": {\n return override.media\n ? `client:media=\"${attributeValue(override.media)}\"`\n : CLIENT_LOAD;\n }\n case \"only\": {\n return framework\n ? `client:only=\"${attributeValue(framework)}\"`\n : CLIENT_LOAD;\n }\n default: {\n return CLIENT_LOAD;\n }\n }\n};\n\n/**\n * The specifier a generated file in `fromDir` imports `path` by: relative to\n * that file when `fromDir` is set and the path is absolute, so the module\n * resolves wherever the project is checked out; unchanged otherwise (a bare\n * package specifier, or the hidden runtime's absolute paths).\n */\nexport const importSpecifier = (path: string, fromDir?: string): string => {\n if (fromDir === undefined || !isAbsolute(path)) {\n return path;\n }\n const specifier = relative(fromDir, path);\n // A path on another Windows drive has no relative form; keep it absolute.\n if (isAbsolute(specifier) || specifier.startsWith(\"../\")) {\n return specifier;\n }\n return `./${specifier}`;\n};\n\nconst importClause = (variable: string, name: string, path: string): string =>\n name === \"default\"\n ? `import ${variable} from ${JSON.stringify(path)};`\n : `import { ${name} as ${variable} } from ${JSON.stringify(path)};`;\n\n/**\n * Frontmatter `Props` alias mirroring the wrapped component's own props, so\n * `{...Astro.props}` satisfies required props under `astro check` (the spread\n * of an untyped `Astro.props` contributes nothing to the JSX props type).\n * `infer P extends object` rather than `Record<string, unknown>` because\n * interfaces have no implicit index signature and would miss the narrower\n * constraint. Non-function component types (Vue/Svelte ambient modules) fall\n * back to an open record, keeping the untyped permissiveness they had.\n */\nexport const wrapperPropsType = (name: string): string =>\n `type Props = typeof ${name} extends (\n props: infer P extends object,\n ...rest: never[]\n) => unknown\n ? P\n : Record<string, unknown>;`;\n\n/**\n * A wrapper `.astro` that statically imports a component and hydrates it.\n * `fromDir` is the wrapper's own directory when imports must be relative.\n */\nconst wrapperContent = (\n override: NormalizedOverride,\n fromDir: string | undefined\n): string => `---\n// Generated by Blume. Do not edit.\n${importClause(\"Component\", override.source.name, importSpecifier(override.source.path, fromDir))}\n${wrapperPropsType(\"Component\")}\n---\n<Component ${directiveFor(override)} {...Astro.props}><slot /></Component>\n`;\n\n/**\n * A filesystem-safe, injective token for an override key. Distinct keys must\n * never share a wrapper file (\"Foo.Bar\" vs \"Foo_Bar\" used to collide, racing\n * the same temp file and silently rendering the wrong component), so every\n * non-alphanumeric character is hex-escaped rather than collapsed — the same\n * hardening as `exampleSlug` in templates.ts.\n */\nconst sanitize = (value: string): string =>\n value.replaceAll(\n /[^A-Za-z0-9]/gu,\n (char) => `_${(char.codePointAt(0) ?? 0).toString(16)}_`\n );\n\n/** A convention island as the `mdx` override it is equivalent to. */\nconst islandOverride = (island: IslandSpec): NormalizedOverride => ({\n client: island.client,\n key: island.name,\n source: { framework: island.framework, name: \"default\", path: island.file },\n});\n\n/** An object-literal source for a group's `key: variable` entries. */\nconst record = (entries: string[]): string =>\n entries.length ? `{ ${entries.join(\", \")} }` : \"{}\";\n\n/**\n * Plan the generated components module for the `islands/` convention plus the\n * analyzed `components.ts` (empty when the project has none). Within a group\n * the last entry for a key wins, so `components.ts` overrides a convention\n * island.\n */\nexport const planComponentSlots = (\n islands: IslandSpec[],\n analysis: ComponentOverrideAnalysis,\n options: ComponentSlotOptions = {}\n): ComponentSlotPlan => {\n const { relativeTo } = options;\n const wrapperDir =\n relativeTo === undefined ? undefined : join(relativeTo, \"component-slots\");\n const frameworks = new Set<OverrideFramework>();\n const wrappers: ComponentSlotWrapper[] = [];\n const importLines: string[] = [];\n let counter = 0;\n\n const plan = (\n overrides: NormalizedOverride[],\n group: \"mdx\" | \"layout\"\n ): string[] => {\n const byKey = new Map<string, NormalizedOverride>();\n for (const override of overrides) {\n byKey.set(override.key, override);\n }\n const entries: string[] = [];\n for (const override of byKey.values()) {\n const { source } = override;\n if (source.framework) {\n frameworks.add(source.framework);\n }\n const variable = `__blumeSlot${counter}`;\n counter += 1;\n if (override.client) {\n const name = `${group}-${sanitize(override.key)}`;\n wrappers.push({ content: wrapperContent(override, wrapperDir), name });\n importLines.push(\n importClause(variable, \"default\", `./component-slots/${name}.astro`)\n );\n } else {\n importLines.push(\n importClause(\n variable,\n source.name,\n importSpecifier(source.path, relativeTo)\n )\n );\n }\n entries.push(`${JSON.stringify(override.key)}: ${variable}`);\n }\n return entries;\n };\n\n const mdxEntries = plan(\n [...islands.map(islandOverride), ...analysis.mdx],\n \"mdx\"\n );\n const layoutEntries = plan(analysis.layout, \"layout\");\n\n const module = `// Generated by Blume. Do not edit.\nimport type { ComponentOverride } from \"blume/core/define-components.ts\";\n${importLines.join(\"\\n\")}${importLines.length ? \"\\n\" : \"\"}export const mdxComponents: Record<string, ComponentOverride> = ${record(mdxEntries)};\nexport const layoutOverrides: Record<string, ComponentOverride> = ${record(layoutEntries)};\n`;\n\n return { frameworks, module, wrappers };\n};\n",
6
- "/**\n * In-memory runtime data modules.\n *\n * The generated runtime's data snapshots — the page data behind `blume:data`,\n * the parsed API specs, the static search index, the raw-Markdown and\n * content-asset maps, the MCP and Ask corpora, and the rendered RSS feeds —\n * used to be written under `.blume/src/generated/*.json` and imported by the\n * generated pages through aliases or relative paths. `generateRuntime` now\n * publishes them here and `runtimeModulesPlugin` serves them to Vite as\n * virtual modules, so a regeneration never round-trips the disk or waits on a\n * file watcher: publishing invalidates the changed modules in every live dev\n * server (Vite walks the importers, so the pages that render them re-evaluate\n * on the next request) and asks the browser to reload — the same effect a JSON\n * file change used to reach through the watcher, minus the write and the\n * watch debounce.\n *\n * The registry hangs off `globalThis`, not module state. On a published\n * install the CLI bundle (`dist/cli`) carries its own copy of this module,\n * separate from the one Vite loads from `blume/astro` for the generated\n * config, and both must see the same map. `blume dev`, `blume build`, and\n * `blume check` all run Astro in-process, so the map the CLI fills is the map\n * the plugin reads.\n *\n * `blume eject` keeps the file form: the ejected project has no CLI to\n * publish, so its `astro.config.mjs` aliases each id to the JSON file eject\n * writes under `src/generated/` — the names in {@link RUNTIME_MODULE_FILES}.\n */\n\nexport type RuntimeModuleId =\n | \"blume:ask-data\"\n | \"blume:content-assets\"\n | \"blume:data\"\n | \"blume:mcp-data\"\n | \"blume:openapi\"\n | \"blume:raw-markdown\"\n | \"blume:rss\"\n | \"blume:search-index\";\n\n/** Virtual module id → the `src/generated` JSON file eject writes for it. */\nexport const RUNTIME_MODULE_FILES: ReadonlyMap<RuntimeModuleId, string> =\n new Map([\n [\"blume:ask-data\", \"ask-data.json\"],\n [\"blume:content-assets\", \"content-assets.json\"],\n [\"blume:data\", \"data.json\"],\n [\"blume:mcp-data\", \"mcp-data.json\"],\n [\"blume:openapi\", \"openapi.json\"],\n [\"blume:raw-markdown\", \"raw-markdown.json\"],\n [\"blume:rss\", \"rss.json\"],\n [\"blume:search-index\", \"search.json\"],\n ]);\n\nconst RUNTIME_MODULE_IDS: ReadonlySet<string> = new Set(\n RUNTIME_MODULE_FILES.keys()\n);\n\n/** Rollup's virtual-module convention: `\\0` keeps other plugins off the id. */\nconst RESOLVED_PREFIX = \"\\0\";\n\n/** A node in Vite's module graph; only its identity matters here. */\ninterface RuntimeModuleNode {\n id: string | null;\n}\n\n/**\n * The Vite dev-server slice the registry touches (structurally typed, like\n * every Blume-authored Vite plugin — see `includeHmrPlugin`).\n */\nexport interface RuntimeModuleServer {\n httpServer?: {\n once: (event: \"close\", listener: () => void) => void;\n } | null;\n moduleGraph: {\n getModuleById: (id: string) => RuntimeModuleNode | undefined;\n invalidateModule: (mod: RuntimeModuleNode) => void;\n };\n ws: { send: (payload: { type: \"full-reload\" }) => void };\n}\n\ninterface RuntimeModuleRegistry {\n /** Published JSON text by module id. */\n modules: Map<string, string>;\n /** Live dev servers to invalidate on publish. */\n servers: Set<RuntimeModuleServer>;\n}\n\nconst REGISTRY_KEY = Symbol.for(\"blume.runtime-modules\");\n\ntype RegistryHost = typeof globalThis & {\n [REGISTRY_KEY]?: RuntimeModuleRegistry;\n};\n\nconst registry = (): RuntimeModuleRegistry => {\n // SAFETY: the registry is stashed on globalThis under a well-known symbol so\n // every copy of this module in the process shares it; the intersection only\n // names that slot.\n const host = globalThis as RegistryHost;\n host[REGISTRY_KEY] ??= { modules: new Map(), servers: new Set() };\n return host[REGISTRY_KEY];\n};\n\n/** The published JSON text for a module, if any (tests and diagnostics). */\nexport const readRuntimeModule = (id: RuntimeModuleId): string | undefined =>\n registry().modules.get(id);\n\n/**\n * Replace the published snapshot set with `modules` (an id absent from the map\n * is unpublished — its feature was switched off). Returns the ids whose text\n * changed; each is invalidated in every live dev server, followed by one\n * full-reload per server. Nothing is sent when nothing changed, so a\n * regeneration triggered by an unrelated edit stays quiet — the same contract\n * `writeIfChanged` gave the file form.\n */\nexport const publishRuntimeModules = (\n modules: ReadonlyMap<RuntimeModuleId, string>\n): RuntimeModuleId[] => {\n const { modules: current, servers } = registry();\n const changed: RuntimeModuleId[] = [];\n for (const id of RUNTIME_MODULE_FILES.keys()) {\n const next = modules.get(id);\n if (current.get(id) === next) {\n continue;\n }\n if (next === undefined) {\n current.delete(id);\n } else {\n current.set(id, next);\n }\n changed.push(id);\n }\n if (changed.length === 0) {\n return changed;\n }\n for (const server of servers) {\n for (const id of changed) {\n const mod = server.moduleGraph.getModuleById(`${RESOLVED_PREFIX}${id}`);\n if (mod) {\n server.moduleGraph.invalidateModule(mod);\n }\n }\n server.ws.send({ type: \"full-reload\" });\n }\n return changed;\n};\n\nexport interface RuntimeModulesPlugin {\n configureServer: (server: RuntimeModuleServer) => void;\n enforce: \"pre\";\n load: (id: string) => string | undefined;\n name: string;\n resolveId: (id: string) => string | undefined;\n}\n\n/**\n * Serve the published runtime modules to Vite. `enforce: \"pre\"` claims the\n * `blume:*` ids before Vite's resolver would try (and fail) to find them as\n * packages. Loading an unpublished id is a hard error rather than an empty\n * module: the generated pages only import a module when its data exists, so a\n * miss means the config was run outside the CLI that publishes.\n */\nexport const runtimeModulesPlugin = (): RuntimeModulesPlugin => ({\n configureServer(server) {\n const { servers } = registry();\n servers.add(server);\n // Astro restarts the dev container in place on a config change (and the\n // CLI restarts it on a route-set change): the old Vite server closes and\n // a new one registers, so drop the stale handle rather than invalidating\n // into a dead graph.\n server.httpServer?.once(\"close\", () => {\n servers.delete(server);\n });\n },\n enforce: \"pre\",\n load(id) {\n if (!id.startsWith(RESOLVED_PREFIX)) {\n return;\n }\n const moduleId = id.slice(RESOLVED_PREFIX.length);\n if (!RUNTIME_MODULE_IDS.has(moduleId)) {\n return;\n }\n const text = registry().modules.get(moduleId);\n if (text === undefined) {\n throw new Error(\n `Blume runtime module \"${moduleId}\" was requested before it was published. Run the generated project through the Blume CLI (blume dev / blume build), which publishes the runtime data before starting Astro.`\n );\n }\n // A JSON.parse over a string literal evaluates faster than an equivalent\n // object literal for large snapshots (V8's guidance; Vite's own JSON\n // plugin does the same past a size threshold).\n return `export default JSON.parse(${JSON.stringify(text)});\\n`;\n },\n name: \"blume:runtime-modules\",\n resolveId(id) {\n return RUNTIME_MODULE_IDS.has(id) ? `${RESOLVED_PREFIX}${id}` : undefined;\n },\n});\n",
7
- "import { existsSync, readFileSync } from \"node:fs\";\nimport { pathToFileURL } from \"node:url\";\n\nimport { dirname, isAbsolute, join, relative } from \"pathe\";\n\nimport type { AskRetrievalOptions } from \"../ai/ask-context.ts\";\nimport type { AskBackend } from \"../ai/ask.ts\";\nimport { buildHomeLinkHeader } from \"../ai/link-headers.ts\";\nimport { normalizeBasePath } from \"../core/base-path.ts\";\nimport { TOC_HIDDEN_KEY } from \"../core/heading-markers.ts\";\nimport type { ResolvedConfig } from \"../core/schema.ts\";\nimport { resolveDocsCollection } from \"../core/sources/collection.ts\";\nimport { BLUME_IGNORE_DIRS } from \"../core/sources/watch.ts\";\nimport { trimChar } from \"../core/trim.ts\";\nimport type { ProjectContext } from \"../core/types.ts\";\nimport { deployPassthrough } from \"../deploy/adapters/types.ts\";\nimport { SVG_ASSET_POLICY } from \"../deploy/headers.ts\";\nimport { deployPlatform } from \"../deploy/platforms/index.ts\";\nimport { adapterRoot, distDir } from \"../deploy/platforms/paths.ts\";\nimport { applyBaseToAstroRedirects } from \"../deploy/redirects.ts\";\nimport type { OgCache } from \"../og/cache.ts\";\nimport type { OgFont, OgFontFamilies } from \"../og/card.ts\";\nimport type { MixedbreadOptions } from \"../search/adapters/mixedbread.ts\";\nimport type { ResolvedSearchAdapter } from \"../search/adapters/registry.ts\";\nimport { buildFontEntries, fontLocaleCodes } from \"../theme/fonts.ts\";\nimport { importSpecifier, wrapperPropsType } from \"./component-slots.ts\";\nimport type { ExampleSpec } from \"./examples.ts\";\nimport type { BlumeIntegrationOptions, BlumePageRoute } from \"./integration.ts\";\nimport type { OgCustomRoute } from \"./pages.ts\";\nimport { RUNTIME_MODULE_FILES } from \"./runtime-modules.ts\";\n\nconst WORKSPACE_MARKERS = [\n \".git\",\n \"bun.lock\",\n \"bun.lockb\",\n \"package-lock.json\",\n \"pnpm-lock.yaml\",\n \"pnpm-workspace.yaml\",\n \"yarn.lock\",\n];\n\n/** True when the package.json at this path declares a `workspaces` field. */\nconst hasWorkspacesField = (pkgPath: string): boolean => {\n if (!existsSync(pkgPath)) {\n return false;\n }\n try {\n // SAFETY: parsed from the user's own package.json; only the presence of a\n // `workspaces` field is read, so this loose shape is all the cast claims.\n const pkg = JSON.parse(readFileSync(pkgPath, \"utf-8\")) as {\n workspaces?: unknown;\n };\n return pkg.workspaces !== undefined;\n } catch {\n return false;\n }\n};\n\n/** True when a directory looks like a workspace/monorepo root. */\nconst hasWorkspaceMarker = (dir: string): boolean =>\n hasWorkspacesField(join(dir, \"package.json\")) ||\n WORKSPACE_MARKERS.some((marker) => existsSync(join(dir, marker)));\n\n/**\n * Walk up from the project root to the workspace/monorepo root so Vite's\n * `fs.allow` can reach hoisted dependencies — e.g. KaTeX fonts that resolve to\n * a monorepo root `node_modules` outside the project directory. Falls back to\n * the project root when no workspace markers are found.\n */\nconst findWorkspaceRoot = (start: string): string => {\n let dir = start;\n for (;;) {\n if (hasWorkspaceMarker(dir)) {\n return dir;\n }\n const parent = dirname(dir);\n if (parent === dir) {\n return start;\n }\n dir = parent;\n }\n};\n\n/**\n * The `adapter:` entry of the generated config plus the import that backs it,\n * for a server build. The platform (see `deploy/platforms/*`) names the\n * `@astrojs/*` package and Blume's own constructor options; the descriptor's\n * passthrough options are spread over those, verbatim, so anything the\n * adapter was given reaches the real adapter. A platform that resolves its\n * output against Astro's root is handed the project root instead of the\n * hidden runtime (`withAdapterRoot`).\n */\ninterface AstroAdapterRender {\n /** Extra top-level `defineConfig` entries the adapter needs. */\n configEntries: string;\n importLine: string;\n /** The `adapter:` entry, or empty for a static build. */\n option: string;\n}\n\nconst renderAstroAdapter = (\n deployment: ResolvedConfig[\"deployment\"],\n context: ProjectContext,\n ejected: boolean\n): AstroAdapterRender => {\n const platform = deployPlatform(deployment);\n if (deployment.options.output !== \"server\" || !platform.astro) {\n return { configEntries: \"\", importLine: \"\", option: \"\" };\n }\n const { astro } = platform;\n const args = {\n ...astro.options(context),\n ...deployPassthrough(deployment.options),\n };\n const argsLiteral = Object.keys(args).length > 0 ? JSON.stringify(args) : \"\";\n const construct = `adapter(${argsLiteral})`;\n // An ejected app's Astro root is the project root already, so its adapter\n // needs no redirect (and the config no machine-specific path).\n const expression =\n platform.hiddenRuntime.showProjectRoot && !ejected\n ? `withAdapterRoot(${construct}, ${JSON.stringify(adapterRoot(context))})`\n : construct;\n return {\n configEntries: Object.entries(astro.config)\n .map(([key, value]) => `\\n ${key}: ${JSON.stringify(value)},`)\n .join(\"\"),\n importLine: `import adapter from \"${astro.package}\";\\n`,\n option: `\\n adapter: ${expression},`,\n };\n};\n\n/**\n * A font weight as Astro's Fonts API spells it: a variable range is\n * `\"100 900\"` there, where Blume's config (and Takumi's Google Fonts helper,\n * which the OG cards use) write `\"100..900\"`. Astro treats the dotted form as\n * an unknown discrete weight and loads nothing for it.\n */\nconst astroFontWeights = (weights: (number | string)[]): string =>\n JSON.stringify(weights).replaceAll(\n /(?<min>\\d+)\\.\\.(?<max>\\d+)/gu,\n \"$<min> $<max>\"\n );\n\n/** The named imports the generated config pulls from `astro/config`. */\nconst astroConfigImportLine = (options: { hasFonts: boolean }): string => {\n const names = [\"defineConfig\"];\n if (options.hasFonts) {\n names.push(\"fontProviders\");\n }\n return `import { ${names.join(\", \")} } from \"astro/config\";`;\n};\n\n/**\n * Integration packages the generated runtime imports. Declaring them in\n * `.blume/package.json` lets Astro's framework-package crawl discover and bundle\n * them — notably the React renderer's server entry, which imports the\n * `astro:react:opts` virtual module and must not be externalized (this applies\n * across the `ssr`, `prerender`, and `client` Vite environments).\n */\nexport const runtimeDependencies = (options: {\n config: ResolvedConfig;\n needsReact: boolean;\n needsVue?: boolean;\n needsSvelte?: boolean;\n}): string[] => {\n const { config, needsReact, needsSvelte, needsVue } = options;\n const deps = [\"@astrojs/mdx\"];\n if (needsReact) {\n deps.push(\"@astrojs/react\");\n }\n if (needsVue) {\n deps.push(\"@astrojs/vue\");\n }\n if (needsSvelte) {\n deps.push(\"@astrojs/svelte\");\n }\n // Each reference adapter declares what it needs: Blume's own renderer\n // parses at generate time and needs nothing, while `scalar()` declares\n // `@scalar/astro` so the framework crawl bundles the embed (two Scalar\n // adapters declare it twice, hence the set). Only the configured\n // search adapter's SDK is declared, so a project pulls in (and the user\n // installs) exactly the backend it uses — nothing more. Each analytics\n // adapter declares what it needs the same way; the built-ins need nothing\n // beyond Blume's own deps, so their share is usually empty.\n deps.push(\n ...new Set(config.reference.flatMap((adapter) => adapter.runtimeDeps)),\n ...config.search.provider.runtimeDeps,\n ...config.analytics.flatMap((adapter) => adapter.runtimeDeps)\n );\n // Each content source adapter declares the SDK its fetch imports (Notion,\n // Sanity); the descriptor is the one place that knows.\n for (const source of config.content.sources) {\n for (const dep of source.runtimeDeps) {\n if (!deps.includes(dep)) {\n deps.push(dep);\n }\n }\n }\n // Ask AI's provider SDK, as its adapter declares it (the gateway needs\n // nothing beyond core `ai`, so it declares none).\n if (config.ai.ask?.enabled && !config.ai.ask.endpoint) {\n deps.push(...config.ai.ask.provider.runtimeDeps);\n }\n // The deployment adapter's `@astrojs/*` package, for a server build; the\n // descriptor declares it (and nothing for a static build).\n deps.push(...config.deployment.runtimeDeps);\n return deps;\n};\n\n/** Generate `.blume/astro.config.mjs`. */\n/**\n * Render project tsconfig path aliases as `vite.resolve.alias` object entries.\n * Longest find first, so a more specific prefix (`@components`) is matched\n * before a broader one (`@`); these follow Blume's `blume:*` aliases, which\n * never overlap with a project's.\n */\n/**\n * Blume's render-time dependencies, forced external on the build's SSR and\n * static-prerender Vite environments.\n *\n * Two reasons a dep lands here:\n * - `takumi-js` (OG image rendering) loads `@takumi-rs/core`, a native NAPI\n * addon that finds its platform-specific `.node` binding via\n * `createRequire(import.meta.url)`. Bundling it relocates `import.meta.url`\n * and breaks the binding lookup (\"Cannot find native binding\") on other\n * platforms (e.g. the Linux CI runner), so it must resolve from\n * `node_modules` at runtime instead. The prerender env matches these by\n * exact specifier, so every entry point Blume imports has to be listed:\n * the bare `takumi-js` (render) plus `takumi-js/helpers` (the `googleFonts`\n * OG-font loader). The `@takumi-rs/*` packages are listed too so the native\n * backend is never pulled into a chunk down any transitive path.\n * - The rest are pure-JS packages kept external so an isolated linker (Bun's\n * `isolated` mode, pnpm) doesn't bundle their symlinked store copies. When\n * Vite bundles such a package but leaves its own `node_modules` child\n * external, that child surfaces as an unresolvable bare import in the\n * prerender chunk (e.g. `batchwork` via `@astrojs/markdown-satteri`). Kept\n * external, each package's transitive imports resolve relative to its real\n * store location — reachable through the `node_modules` junction {@link\n * prerenderDepsPlugin} drops beside the prerender bundle.\n *\n * Astro 7 configures externalization per Vite environment, so this must be\n * applied to both `prerender` (static) and `ssr` (server) — a top-level\n * `ssr.external` only reaches the latter.\n */\nconst RENDER_EXTERNAL_DEPS = [\n \"@astrojs/markdown-satteri\",\n \"@pierre/diffs\",\n \"@shikijs/transformers\",\n \"@takumi-rs/core\",\n \"@takumi-rs/helpers\",\n \"@takumi-rs/wasm\",\n \"github-slugger\",\n \"katex\",\n \"shiki\",\n \"simple-icons\",\n \"takumi-js\",\n \"takumi-js/helpers\",\n \"zod\",\n];\n\nconst renderUserAliases = (\n aliases: Record<string, string> | undefined\n): string =>\n Object.entries(aliases ?? {})\n .toSorted(([a], [b]) => b.length - a.length)\n .map(\n ([find, replacement]) =>\n `\\n ${JSON.stringify(find)}: ${JSON.stringify(replacement)},`\n )\n .join(\"\");\n\n/**\n * Excludes Vite's pre-bundled dep cache from @vitejs/plugin-react. Astro's\n * react() replaces the plugin's default `/node_modules/` exclude with just\n * `/\\.astro$/`, so without this Babel re-parses every optimized dep chunk\n * served from `.vite/deps` — a 500KB+ vendor bundle per chunk, re-done on each\n * re-optimization. A blanket `/node_modules/` exclude would instead switch the\n * React Compiler off for Blume's own components in published installs (they\n * resolve under `node_modules/blume/src`, and exclude beats include in the\n * plugin's filter), so only the pre-bundle cache is excluded. The hidden runtime\n * relocates that cache to `<runtime>/.cache/vite` (see `cacheOptions`), so both\n * the default and the relocated path are excluded.\n */\nconst REACT_EXCLUDE = String.raw`exclude: [/\\/node_modules\\/\\.vite\\//, /\\/\\.cache\\/vite\\//]`;\n\n/**\n * The `cacheDir` entries for the generated config's top level and its `vite`\n * block. The hidden runtime's `node_modules` is a junction into Blume's own\n * package directory, and Astro (`node_modules/.astro`: the content data store,\n * the fonts cache) and Vite (`node_modules/.vite`: pre-bundled deps) both\n * default their caches under the project's `node_modules`. Two Blume projects\n * that resolve the same package (a monorepo building docs and a sandbox in\n * parallel) would therefore share one data store, and each build would serve\n * the other's content — or 404 on entries the other cleared. Keep every cache\n * inside the runtime dir instead. An ejected project (`generatedModulesDir`\n * set) has real `node_modules`, so it keeps the defaults.\n */\nconst runtimeCacheOptions = (\n context: ProjectContext,\n generatedModulesDir: string | undefined\n) => {\n if (generatedModulesDir !== undefined) {\n return { astro: \"\", vite: \"\" };\n }\n return {\n astro: `\n cacheDir: ${JSON.stringify(`${context.outDir}/.cache/astro`)},`,\n vite: `\n cacheDir: ${JSON.stringify(`${context.outDir}/.cache/vite`)},`,\n };\n};\n\n/**\n * The `react()` integration call. When `compilerPath` is set (the resolved\n * absolute path to `babel-plugin-react-compiler`), react() carries the compiler\n * as the first babel plugin — an absolute path, because @vitejs/plugin-react\n * resolves babel plugins from the *project* root, not `.blume/`, so a bare\n * specifier wouldn't resolve in a user project. `target: \"19\"` matches Blume's\n * React pin. `null`/`undefined` (compiler off or unresolvable) omits the babel\n * block. Both variants carry the pre-bundle exclude above.\n */\nconst reactIntegration = (compilerPath: string | null | undefined): string =>\n compilerPath\n ? `react({ babel: { plugins: [[${JSON.stringify(compilerPath)}, { target: \"19\" }]] }, ${REACT_EXCLUDE} })`\n : `react({ ${REACT_EXCLUDE} })`;\n\ninterface IntegrationBridgeOptions {\n /** Config path relative to the generated Astro config. */\n configFile: string;\n /** SHA-256 used to invalidate Astro's generated config. */\n sourceHash?: string;\n}\n\nconst renderIntegrationBridge = (\n bridge: IntegrationBridgeOptions | undefined\n) => {\n if (!bridge) {\n return {\n configSourceMarker: \"\",\n userConfigImports: \"\",\n userConfigSetup: \"\",\n userIntegrationSpread: \"\",\n };\n }\n return {\n configSourceMarker: bridge.sourceHash\n ? `// Blume config source SHA-256: ${bridge.sourceHash}\\n`\n : \"\",\n userConfigImports: `import { dirname, resolve } from \"node:path\";\\nimport { fileURLToPath } from \"node:url\";\\nimport { createModuleLoader } from \"blume/core/load-module.ts\";\\n`,\n userConfigSetup: `const loadBlumeConfig = createModuleLoader();\\nconst blumeConfig = await loadBlumeConfig(resolve(dirname(fileURLToPath(import.meta.url)), ${JSON.stringify(\n bridge.configFile\n )}));\\n\\n`,\n userIntegrationSpread: \", ...(blumeConfig?.integrations ?? [])\",\n };\n};\n\n/**\n * The generated config's `image` block. Remote images are only optimized when\n * their host is authorized; local (relative-path) images need no\n * configuration. Emitted only when set, so the generated config stays minimal.\n */\nconst renderImageOption = (config: ResolvedConfig): string =>\n config.image.domains.length > 0 || config.image.remotePatterns.length > 0\n ? `\\n image: ${JSON.stringify(config.image)},`\n : \"\";\n\n/** What `resolveOptimizeDeps` feeds the generated `optimizeDeps` block. */\ninterface OptimizeDepsConfig {\n optimizeDepsEntries: string[];\n optimizeDepsInclude: string[];\n}\n\n/**\n * Startup-scan entry points and forced includes for the dev dep optimizer:\n * the Vite root is the generated runtime, so user pages, convention islands,\n * and alias-reachable components all live outside it and are otherwise only\n * crawled when first requested. The compiler runtime rides the include list\n * because it is Babel-injected and no source scan can see it. @vitejs/plugin-react\n * would add it itself, but only when the babel plugin is passed by its bare\n * name (`getReactCompilerPlugin` is an exact string match) — Blume passes an\n * absolute path (see `reactIntegration`), which that check never matches. See\n * the optimizeDeps comment in the generated config for the failure this prevents.\n */\n/**\n * The client-side libraries a site needs, decided at generation time. A\n * feature no page uses is left out of the module graph entirely (see\n * {@link featuresTemplate}), so its library never enters the client bundle —\n * Mermaid alone is over 3 MB of chunks (ELK, Cytoscape, KaTeX, every diagram\n * type) and most of the build's client-bundle memory.\n */\nexport interface ClientFeatures {\n /** `export.epub` is on: the EPUB generator's browser bundle is needed. */\n epub: boolean;\n /** Some page has a mermaid fence: the `<blume-mermaid>` element is needed. */\n mermaid: boolean;\n}\n\n/** Every feature on: what a checkout that predates the detection shipped. */\nconst ALL_CLIENT_FEATURES: ClientFeatures = { epub: true, mermaid: true };\n\nconst resolveOptimizeDeps = (options: {\n aliases: Record<string, string> | undefined;\n context: ProjectContext;\n features: ClientFeatures;\n needsReact: boolean;\n reactCompilerPath: string | null | undefined;\n}): OptimizeDepsConfig => {\n const { context, features } = options;\n const optimizeDepsEntries = [\n ...(context.pagesRoot ? [`${context.pagesRoot}/**/*.astro`] : []),\n `${context.root}/islands/**/*.{jsx,svelte,tsx,vue}`,\n ...[...new Set(Object.values(options.aliases ?? {}))]\n .toSorted()\n .map((dir) => `${dir}/**/*.{astro,jsx,svelte,tsx,vue}`),\n ];\n const optimizeDepsInclude = [\n // Only the libraries the site's features actually import: pre-bundling\n // Mermaid costs the dev server seconds at startup for nothing otherwise.\n ...(features.mermaid ? [\"blume > mermaid\"] : []),\n ...(features.epub ? [\"blume > epub-gen-memory/bundle\"] : []),\n // Astro's own client-router/prefetch virtual modules are deliberately NOT\n // forced in here: they read Vite `define`-injected constants\n // (__PREFETCH_PREFETCH_ALL__ and friends) that a pre-bundled copy loses,\n // throwing ReferenceError on every page. Astro manages their optimization\n // itself, without a mid-session reload.\n ...(options.needsReact && options.reactCompilerPath\n ? [\"react/compiler-runtime\"]\n : []),\n ];\n return { optimizeDepsEntries, optimizeDepsInclude };\n};\n\n/**\n * A path the generated config hands Vite or a plugin. The ejected config\n * resolves it against the config file itself when it loads, so the project\n * builds from any checkout and working directory, and Vite gets the absolute\n * alias target it expects (it warns on every relative one). The hidden\n * runtime's paths are absolute already.\n */\nconst configPath = (path: string, ejected: boolean): string =>\n ejected\n ? `fileURLToPath(new URL(${JSON.stringify(path)}, import.meta.url))`\n : JSON.stringify(path);\n\n/**\n * The first line of an ejected `astro.config.mjs`, which also marks the\n * project as ejected for the CLI (`blume dev`/`build` step aside, and a second\n * `blume eject` refuses to overwrite the app).\n */\nexport const EJECTED_CONFIG_HEADER =\n \"// Written by `blume eject`. This is your Astro config now: edit it freely.\";\n\n/**\n * The generated config's first line. The hidden runtime's config is rewritten\n * on every run; an ejected one belongs to the project from then on.\n */\nconst astroConfigHeader = (ejected: boolean): string =>\n ejected\n ? EJECTED_CONFIG_HEADER\n : \"// Generated by Blume. Do not edit; this file is recreated on each run.\";\n\n/**\n * The client build's chunk-size warning threshold (kB) on a site with Mermaid\n * diagrams. Mermaid's ELK layout engine (about 1.5 MB) and its core (about\n * 650 kB) land in their own chunks, which the `blume:features` loader fetches\n * only on a page with a diagram — so Vite's default 500 kB warning fired on\n * every such build without anything to fix. A chunk past this still warns.\n */\nconst MERMAID_CHUNK_LIMIT = \"\\n chunkSizeWarningLimit: 2048,\";\n\n/**\n * Rolldown's log hook for the build. Astro's content-assets plugin opens each\n * page's `?astroPropagatedAssets` module with a `\"use astro:head-inject\"`\n * directive that nothing reads once bundled (head propagation rides on the\n * module's `__astroPropagation` export), and Rolldown 1.2.10+ warns about it\n * in nine lines per page, on every build. That one warning is dropped; every\n * other log goes to Vite's default handler.\n */\nconst ROLLDOWN_ON_LOG = `onLog(level, log, handler) {\n if (log.code === \"MODULE_LEVEL_DIRECTIVE\" && String(log.message).includes(\"use astro:head-inject\")) {\n return;\n }\n handler(level, log);\n }`;\n\n/**\n * How the generated config reaches the runtime data modules (`blume:data`,\n * the search index, …): served from memory by `runtimeModulesPlugin` in the\n * hidden runtime, or aliased to JSON files under `generatedModulesDir` for an\n * ejected project, which has no CLI to publish them (see `runtime-modules.ts`).\n */\ninterface RuntimeModuleWiring {\n /** `resolve.alias` entries (one per module), empty in the in-memory form. */\n aliasLines: string;\n /** Extra `blume/astro` imports the wiring needs. */\n imports: string[];\n /** Leading `vite.plugins` entry, empty in the file-alias form. */\n pluginEntry: string;\n}\n\nconst renderRuntimeModuleWiring = (\n generatedModulesDir: string | undefined\n): RuntimeModuleWiring => {\n if (generatedModulesDir === undefined) {\n return {\n aliasLines: \"\",\n imports: [\"runtimeModulesPlugin\"],\n pluginEntry: \"runtimeModulesPlugin(), \",\n };\n }\n const aliasLines = [...RUNTIME_MODULE_FILES]\n .map(\n ([id, file]) =>\n `\\n ${JSON.stringify(id)}: ${configPath(`${generatedModulesDir}/${file}`, true)},`\n )\n .join(\"\");\n return { aliasLines, imports: [], pluginEntry: \"\" };\n};\n\n/**\n * The options baked into the generated config's `blumeIntegration(...)` call.\n * The hidden runtime gets the content routes and homepage `Link` header from\n * the CLI in memory (`publishDevNegotiation`, republished on every\n * regeneration), so a content-route change never rewrites the config — Astro\n * restarts the dev server in place on a config change — and its scanned\n * project from `blume build` for the deploy artifacts. An ejected project has\n * no CLI, so the negotiation inputs are baked in and `astro:build:done` scans\n * the project root (the Astro root, after eject) for the artifacts.\n */\nconst blumeIntegrationOptions = (options: {\n config: ResolvedConfig;\n contentRoutes: string[];\n ejected: boolean;\n pages: BlumePageRoute[];\n}): BlumeIntegrationOptions =>\n options.ejected\n ? {\n buildArtifactsRoot: \".\",\n contentRoutes: options.contentRoutes,\n homeLinkHeader:\n buildHomeLinkHeader(options.config, options.contentRoutes) ??\n undefined,\n pages: options.pages,\n }\n : { pages: options.pages };\n\nexport const astroConfigTemplate = (options: {\n context: ProjectContext;\n config: ResolvedConfig;\n needsReact: boolean;\n needsVue?: boolean;\n needsSvelte?: boolean;\n pages: BlumePageRoute[];\n contentRoutes: string[];\n /** The generated Ask trigger (`blume:ask`); renders nothing when Ask is off. */\n askPath: string;\n examplesPath: string;\n /** The example-preview Tailwind entry (`blume:examples-theme`). */\n examplesThemePath: string;\n themePath: string;\n searchClientPath: string;\n /** The generated client-feature loaders (`blume:features`). */\n featuresPath: string;\n /** Which client features the site uses; every feature on when omitted. */\n features?: ClientFeatures;\n /**\n * Where the runtime data modules (`blume:data`, the search index, …) live as\n * JSON files, for a project with no CLI to publish them in memory (eject):\n * each id is aliased to its file under this directory. Absent, the modules\n * are served from memory by `runtimeModulesPlugin` — the hidden runtime.\n */\n generatedModulesDir?: string;\n /**\n * Absolute path to `babel-plugin-react-compiler` when the React Compiler is\n * enabled (resolved from Blume's package root by the caller); null/absent\n * disables the compiler and emits a bare `react()`.\n */\n reactCompilerPath?: string | null;\n /** Project tsconfig path aliases (`find` -> absolute dir), e.g. `@` -> src. */\n aliases?: Record<string, string>;\n /**\n * The docs collection's content root; bounds `<include>` resolution in the\n * processors and locates the include graph for dev-server invalidation.\n */\n contentRoot?: string;\n /** Bridge used to load configured integrations without serializing them. */\n integrationBridge?: IntegrationBridgeOptions;\n}): string => {\n const { context, config, needsReact, pages, themePath } = options;\n\n const { astro: cacheOptions, vite: viteCacheOption } = runtimeCacheOptions(\n context,\n options.generatedModulesDir\n );\n const {\n askPath,\n contentRoutes,\n examplesPath,\n examplesThemePath,\n features = ALL_CLIENT_FEATURES,\n featuresPath,\n generatedModulesDir,\n needsSvelte,\n needsVue,\n searchClientPath,\n } = options;\n const ejected = generatedModulesDir !== undefined;\n const {\n aliasLines: runtimeModuleAliasLines,\n imports: runtimeModuleImports,\n pluginEntry: runtimeModulesPluginEntry,\n } = renderRuntimeModuleWiring(generatedModulesDir);\n const { deployment } = config;\n const userAliasLines = renderUserAliases(options.aliases);\n\n // The project root plus the workspace root, so hoisted dependencies (e.g.\n // KaTeX fonts under a monorepo's root node_modules) stay servable in dev.\n const fsAllow = [...new Set([findWorkspaceRoot(context.root), context.root])];\n\n const { optimizeDepsEntries, optimizeDepsInclude } = resolveOptimizeDeps({\n aliases: options.aliases,\n context,\n features,\n needsReact,\n reactCompilerPath: options.reactCompilerPath,\n });\n\n const {\n configEntries: adapterConfigEntries,\n importLine: adapterImport,\n option: adapterOption,\n } = renderAstroAdapter(deployment, context, ejected);\n\n const siteOption = deployment.options.site\n ? `\\n site: ${JSON.stringify(deployment.options.site)},`\n : \"\";\n const baseOption = deployment.options.base\n ? `\\n base: ${JSON.stringify(deployment.options.base)},`\n : \"\";\n const imageOption = renderImageOption(config);\n\n // Astro's native i18n resolves `Astro.currentLocale` from the URL, which the\n // document shells fall back to for `<html lang>`/`dir` on pages the content\n // catch-all doesn't drive (custom pages, the 404, the reference shell).\n // Blume owns getStaticPaths and materializes fallback routes in the manifest,\n // so we deliberately omit Astro's `fallback` to keep one source of routing.\n const i18nOption = config.i18n\n ? `\\n i18n: ${JSON.stringify({\n defaultLocale: config.i18n.defaultLocale,\n locales: config.i18n.locales.map((locale) => locale.code),\n routing: {\n prefixDefaultLocale: !config.i18n.hideDefaultLocalePrefix,\n },\n })},`\n : \"\";\n\n // Base the redirect paths the same way routes are based, so a redirect lands\n // under `basePath` too. Astro layers its own `base` (deployment.base) onto\n // `from` when matching, but never onto `to` — see applyBaseToAstroRedirects.\n const basedRedirects = applyBaseToAstroRedirects(\n config.redirects,\n config.basePath,\n deployment.options.base ?? \"\"\n );\n const redirectsOption =\n basedRedirects.length > 0\n ? `\\n redirects: ${JSON.stringify(\n Object.fromEntries(\n basedRedirects.map((redirect) => [\n redirect.from,\n { destination: redirect.to, status: redirect.status },\n ])\n )\n )},`\n : \"\";\n\n // Self-hosted fonts via Astro's Fonts API, derived from theme.fonts.\n // `fontProviders` is only imported when at least one font is configured.\n // Local variant sources are emitted as absolute paths (the Astro root is\n // `.blume/`, not the user's project, so root-relative paths would miss).\n // Subsets follow the configured locales (a Vietnamese site loads the\n // `vietnamese` faces) unless a family pins its own.\n const fontEntries = buildFontEntries(\n config.theme.fonts,\n fontLocaleCodes(config.i18n)\n );\n const fontsOption = fontEntries.length\n ? `\\n fonts: [${fontEntries\n .map((font) =>\n font.kind === \"local\"\n ? `{ provider: fontProviders.local(), name: ${JSON.stringify(\n font.name\n )}, cssVariable: ${JSON.stringify(\n font.cssVariable\n )}, fallbacks: ${JSON.stringify(\n font.fallbacks\n )}, options: { variants: ${JSON.stringify(\n font.variants.map((variant) => {\n const face: Pick<typeof variant, \"style\" | \"weight\"> = {};\n if (variant.weight !== undefined) {\n face.weight = variant.weight;\n }\n if (variant.style !== undefined) {\n face.style = variant.style;\n }\n return {\n ...face,\n src: [\n isAbsolute(variant.src)\n ? variant.src\n : join(context.root, variant.src),\n ],\n };\n })\n )} } }`\n : `{ provider: fontProviders.${font.provider}(), name: ${JSON.stringify(\n font.name\n )}, cssVariable: ${JSON.stringify(\n font.cssVariable\n )}, weights: ${astroFontWeights(font.weights)}, subsets: ${JSON.stringify(\n font.subsets\n )}, fallbacks: ${JSON.stringify(font.fallbacks)} }`\n )\n .join(\", \")}],`\n : \"\";\n const defineConfigImport = astroConfigImportLine({\n hasFonts: fontEntries.length > 0,\n });\n\n // Framework renderers are only wired in when an island (or Ask AI, for React)\n // needs them. The core theme is Astro-first and ships no client JS.\n const reactImport = needsReact ? `import react from \"@astrojs/react\";\\n` : \"\";\n const vueImport = needsVue ? `import vue from \"@astrojs/vue\";\\n` : \"\";\n const svelteImport = needsSvelte\n ? `import svelte from \"@astrojs/svelte\";\\n`\n : \"\";\n const blumeImports = [\n \"blumeIntegration\",\n \"includeHmrPlugin\",\n \"prerenderDepsPlugin\",\n ...runtimeModuleImports,\n ...(adapterOption.includes(\"withAdapterRoot\") ? [\"withAdapterRoot\"] : []),\n ];\n const blumeImport = `import { ${blumeImports.join(\", \")} } from \"blume/astro\";\\n`;\n\n // Twoslash runs first, before the always-on transformers, but only on fences\n // with the `twoslash` meta (explicitTrigger) — so it's opt-in per block with\n // no config flag; the TypeScript compiler only spins up when a block uses it.\n // Blume's preconfigured transformer compiles with the package's own pinned\n // classic TypeScript, so the user's project can be on any version (see\n // markdown/twoslash.ts).\n const twoslashTransformer = \"blumeTwoslashTransformer(), \";\n\n // Content links are rewritten to their real served URL: the `deployment.base`\n // subdirectory (Astro doesn't rewrite `<a href>`) layered over the site-wide\n // `basePath` baked into routes. The layers are passed separately so a\n // hand-written `basePath` link (`/docs/x`) isn't double-prefixed (see\n // `withComposedBasePath`). The link checker validates the base-less authored\n // path against `basePath` routes separately.\n const deployBase = normalizeBasePath(deployment.options.base);\n // Both processors take the same options. An ejected app has no CLI to\n // publish the `blume:data` snapshot that relative page links resolve\n // through, so its processors read the snapshot file instead, resolved\n // against the config file like the module aliases.\n const processorLiteral = JSON.stringify({\n basePath: config.basePath,\n codeThemes: config.markdown.code.theme,\n contentRoot: options.contentRoot,\n deployBase,\n externalLinks: config.markdown.externalLinks,\n headingAnchors: config.markdown.headingAnchors,\n });\n const processorOptions =\n options.generatedModulesDir === undefined\n ? processorLiteral\n : `{ ...${processorLiteral}, dataFile: ${configPath(`${options.generatedModulesDir}/data.json`, true)} }`;\n\n const integrations = [\n `mdx({ processor: blumeMdxProcessor(${processorOptions}) })`,\n ];\n if (needsReact) {\n integrations.push(reactIntegration(options.reactCompilerPath));\n }\n if (needsVue) {\n integrations.push(\"vue()\");\n }\n if (needsSvelte) {\n integrations.push(\"svelte()\");\n }\n // Always mounted: injects user pages (a no-op when there are none) and wires\n // up dev-server `Accept: text/markdown` negotiation over the content routes,\n // plus the homepage agent-discovery `Link` header.\n integrations.push(\n `blumeIntegration(${JSON.stringify(\n blumeIntegrationOptions({\n config,\n contentRoutes,\n ejected,\n pages,\n })\n )})`\n );\n\n const {\n configSourceMarker,\n userConfigImports,\n userConfigSetup,\n userIntegrationSpread,\n } = renderIntegrationBridge(options.integrationBridge);\n\n const fileUrlImport = ejected\n ? `import { fileURLToPath } from \"node:url\";\\n`\n : \"\";\n\n return `${astroConfigHeader(ejected)}\n${configSourceMarker}${userConfigImports}import { availableParallelism } from \"node:os\";\n${fileUrlImport}${defineConfigImport}\nimport mdx from \"@astrojs/mdx\";\nimport tailwindcss from \"@tailwindcss/vite\";\nimport { blumeMarkdownProcessor, blumeMdxProcessor, blumeShikiTransformers, blumeTwoslashTransformer } from \"blume/markdown\";\n${reactImport}${vueImport}${svelteImport}${blumeImport}${adapterImport}\n${userConfigSetup}export default defineConfig({\n root: ${JSON.stringify(context.outDir)},\n srcDir: ${JSON.stringify(`${context.outDir}/src`)},\n outDir: ${JSON.stringify(distDir(context))},\n publicDir: ${JSON.stringify(`${context.root}/public`)},${cacheOptions}\n output: ${JSON.stringify(deployment.options.output)},${adapterOption}${adapterConfigEntries}${siteOption}${baseOption}${imageOption}${redirectsOption}${i18nOption}${fontsOption}\n integrations: [${integrations.join(\", \")}${userIntegrationSpread}],\n markdown: {\n processor: blumeMarkdownProcessor(${processorOptions}),\n shikiConfig: {\n themes: {\n light: ${JSON.stringify(config.markdown.code.theme.light)},\n dark: ${JSON.stringify(config.markdown.code.theme.dark)},\n },\n defaultColor: false,\n transformers: [${twoslashTransformer}...blumeShikiTransformers(${JSON.stringify(\n { icons: config.markdown.code.icons }\n )})],\n },\n },\n devToolbar: { enabled: false },\n // One canonical URL per page: canonicals, the sitemap, and hreflang all use\n // the slashless form, so the slashed spelling is not a second address. Astro\n // applies this itself — its dev server answers a slashed URL with a 404 that\n // names the setting, an on-demand route redirects — and the Vercel adapter\n // turns it into the platform's 308 route, so the Build Output config needs\n // no hand-spliced redirect (see deploy/vercel-negotiation.ts). Static hosts\n // serve the \\`index.html\\` directory layout as they always did.\n trailingSlash: \"never\",\n // The layouts render Astro's <ClientRouter />, and its in-place swaps read\n // from the prefetch cache — fetching every link on hover/viewport hides the\n // request latency behind the user's intent, so most navigations swap\n // instantly.\n prefetch: { prefetchAll: true },\n // Prerender several pages at once. Rendering is single-threaded, but a\n // page's OG card renders on a native thread and its HTML is written\n // asynchronously, so the main thread would otherwise idle behind each\n // page's off-thread work. Capped at 8: the gain flattens there and beyond it\n // the overlap only adds memory.\n build: { concurrency: Math.min(8, availableParallelism()) },\n vite: {${viteCacheOption}\n plugins: [${runtimeModulesPluginEntry}tailwindcss(), includeHmrPlugin(${configPath(\n `${context.outDir}/src/generated/includes.json`,\n ejected\n )}), prerenderDepsPlugin()],\n build: {${features.mermaid ? MERMAID_CHUNK_LIMIT : \"\"}\n rolldownOptions: {\n ${ROLLDOWN_ON_LOG},\n },\n },\n // Everything hydration can reach must be part of the dev dep optimizer's\n // FIRST run. The Vite root is the generated runtime, so user pages,\n // islands, and aliased components live outside it and are only crawled\n // when first requested — and \\`react/compiler-runtime\\` is Babel-injected,\n // so no source scan can ever see it. A dependency discovered after\n // hydration begins triggers a mid-session re-optimization whose new\n // generation imports React through new \\`?v=\\` URLs; the browser then\n // evaluates a second React copy and every island tears down with\n // \"Invalid hook call\" (#157). \\`entries\\` points the startup scanner at\n // the user's files (the scanner follows their imports, so their deps land\n // in the initial optimization); the compiler runtime rides the include\n // list because only the transform pipeline knows it exists.\n //\n // The mermaid/epub includes fix CJS interop instead: both lazy client-side\n // imports land on CJS/UMD files (mermaid statically imports dayjs as CJS,\n // epub-gen-memory's browser bundle is a browserified UMD) that break when\n // served as raw ESM — mermaid throws on load and the EPUB export throws\n // \\`epub is not a function\\`. They resolve through the \\`blume\\` package\n // (they aren't direct deps of the generated project), so the nested\n // \\`blume > x\\` form is required, and epub-gen-memory must name the\n // \\`/bundle\\` subpath that is actually imported: optimizing the package\n // root leaves that entry out. Production (Rollup) already handles the\n // interop, so all of this only affects dev.\n optimizeDeps: {\n entries: ${JSON.stringify(optimizeDepsEntries)},\n include: ${JSON.stringify(optimizeDepsInclude)},\n },\n // Blume's render-time deps are forced external on both build environments so\n // native bindings resolve at runtime and isolated linkers don't bundle\n // symlinked store copies (which would surface their children as unresolvable\n // imports). See RENDER_EXTERNAL_DEPS / prerenderDepsPlugin.\n //\n // The SSR externals go through the legacy \\`ssr.external\\` key rather than\n // \\`environments.ssr\\`: defining a user-owned \\`environments.ssr\\` block\n // collides with the internal environment Astro 7 builds the server under and\n // detaches the adapter's server entrypoint from the rolldown input, so the\n // SSR entry is emitted as \\`index.mjs\\` instead of the \\`entry.mjs\\` the\n // Vercel adapter's \\`astro:build:done\\` hook then fails to find. \\`prerender\\`\n // is Astro-only and has no legacy equivalent, so it stays under \\`environments\\`.\n ssr: { external: ${JSON.stringify(RENDER_EXTERNAL_DEPS)} },\n environments: {\n prerender: { resolve: { external: ${JSON.stringify(RENDER_EXTERNAL_DEPS)} } },\n },\n resolve: {\n alias: {\n \"blume:ask\": ${configPath(askPath, ejected)},\n \"blume:examples\": ${configPath(examplesPath, ejected)},\n \"blume:examples-theme\": ${configPath(examplesThemePath, ejected)},\n \"blume:features\": ${configPath(featuresPath, ejected)},\n \"blume:search-client\": ${configPath(searchClientPath, ejected)},\n \"blume:theme\": ${configPath(themePath, ejected)},${runtimeModuleAliasLines}${userAliasLines}\n },\n },\n server: {\n fs: {\n allow: ${JSON.stringify(fsAllow)},\n },\n },\n },\n});\n`;\n};\n\n/** The default staged-content base, relative to the runtime `outDir`. */\nexport const stagedContentDir = (outDir: string): string =>\n join(outDir, \"content\");\n\n/**\n * The runtime dir relative to the docs collection `base` when it sits inside\n * it (a migrated, `content.root: \".\"` project) — null when it lives elsewhere.\n * Drives the collection's negative glob in `contentConfigTemplate`, which both\n * keeps runtime-dir files out of the collection and (Astro's watcher honors\n * negated patterns) keeps the content watcher off Astro's own `.astro` writes.\n */\nconst runtimeDirWithin = (base: string, outDir: string): string | null => {\n const rel = relative(base, outDir);\n return rel && !rel.startsWith(\"..\") && !isAbsolute(rel) ? rel : null;\n};\n\n/**\n * Astro's glob loader resolves `base` with `new URL(base, config.root)`. On\n * Windows an absolute path like `C:\\\\docs\\\\content` makes `new URL` parse the\n * drive letter as a URL scheme, so the result isn't a `file:` URL and Astro's\n * subsequent `fileURLToPath` throws \"The URL must be of scheme file\". Emit an\n * absolute base as a proper `file://` URL so the drive letter can't be mistaken\n * for a scheme; relative bases resolve against `config.root` unchanged.\n */\nconst astroGlobBase = (base: string): string =>\n isAbsolute(base) ? pathToFileURL(base).href : base;\n\n/** Generate `.blume/src/content.config.ts`. */\nexport const contentConfigTemplate = (options: {\n context: ProjectContext;\n config: ResolvedConfig;\n /** Whether any non-filesystem source materialized MDX into the staged dir. */\n staged?: boolean;\n /** Base dir for the staged collection; defaults to `<outDir>/content`. */\n stagedBase?: string;\n /**\n * The `docs` collection's base + include/exclude globs. Defaults to what\n * `resolveDocsCollection` derives from the filesystem sources: the collection\n * roots at the first one so entry ids resolve.\n */\n collection?: { base: string; include: string[]; exclude: string[] };\n /**\n * Whether any filesystem (non-staged) source feeds the `docs` collection.\n * When false (e.g. an all-staged project where every page is materialized by\n * a non-filesystem source), the collection globs nothing — see below.\n */\n filesystem?: boolean;\n}): string => {\n const { context, config } = options;\n const stagedBase = options.stagedBase ?? stagedContentDir(context.outDir);\n const collection =\n options.collection ?? resolveDocsCollection(config, context.root);\n const collectionBase = collection.base;\n const includeGlobs = collection.include;\n const excludeGlobs = collection.exclude;\n\n // Fold the content excludes into the glob as negative patterns so the `docs`\n // collection doesn't ingest ignored trees (`node_modules`, `snippets`, the\n // staged bodies under `.blume/content`, …) as entries. This matters when\n // the collection base is the project root (a migrated `.`-rooted project).\n const outDirRel = runtimeDirWithin(collectionBase, context.outDir);\n const outDirIgnore = outDirRel ? [`!${outDirRel}/**`] : [];\n\n // With no filesystem source, no route renders through `docs`, so glob\n // nothing: an all-staged project roots the collection at the project dir,\n // and a patterned glob would scan (and watch) the whole project for nothing.\n // The collection is still declared below so `getCollection(\"docs\")` /\n // `getEntry` resolve (to empty).\n const filesystem = options.filesystem ?? true;\n const docsPattern = filesystem\n ? [\n ...includeGlobs,\n ...(excludeGlobs ?? []).map((pattern) => `!${pattern}`),\n // Mirror the filesystem scan's baseline ignores (see BLUME_IGNORE_DIRS):\n // Astro's content layer roots at the project dir, so a `.`-wide content\n // root would otherwise re-ingest dependency trees and build output —\n // e.g. a prior `dist/*.mdx` render — and crash the content-module graph.\n // The runtime dir (`.blume`, or a custom distDir) is excluded precisely\n // by `outDirIgnore` instead, so it's left out of this baseline.\n ...BLUME_IGNORE_DIRS.flatMap((dir) =>\n dir === \".blume\" ? [] : [`!**/${dir}/**`]\n ),\n ...outDirIgnore,\n ]\n : [];\n\n // Non-filesystem sources render through a parallel `staged` collection backed\n // by materialized MDX, so the filesystem `docs` collection stays untouched.\n const stagedBlock = options.staged\n ? `\nconst staged = defineCollection({\n loader: glob({\n pattern: [\"**/*.{md,mdx}\"],\n base: ${JSON.stringify(astroGlobBase(stagedBase))},\n generateId: ({ entry }) => entry,\n }),\n schema: pageCollectionSchema,\n});\n`\n : \"\";\n\n return `// Generated by Blume. Do not edit.\nimport { defineCollection } from \"astro:content\";\nimport { glob } from \"astro/loaders\";\nimport { withIncludeRefresh } from \"blume/astro\";\nimport { pageCollectionSchema } from \"blume/core/schema.ts\";\n\n// withIncludeRefresh keeps <include>-bearing pages fresh: plain .md entries\n// are rendered at sync time and digest-cached on the page file alone, so a\n// partial edit (or a warm-cache rebuild after one) would serve stale HTML.\n//\n// pageCollectionSchema is the scan's page front-matter schema, so entry.data\n// is typed and normalized the same way; it passes custom keys through and\n// falls back to defaults for a page the scan already dropped as invalid.\nconst docs = defineCollection({\n loader: withIncludeRefresh(glob({\n pattern: ${JSON.stringify(docsPattern)},\n base: ${JSON.stringify(astroGlobBase(collectionBase))},\n generateId: ({ entry }) => entry,\n }), ${JSON.stringify(`${context.outDir}/src/generated/includes.json`)}),\n schema: pageCollectionSchema,\n});\n${stagedBlock}\nexport const collections = { docs${options.staged ? \", staged\" : \"\"} };\n`;\n};\n\n/** Generate `.blume/src/pages/[...slug].astro`, the docs catch-all route. */\n/** The plain prompt used when there is no grounding context to inject. */\nconst ASK_FALLBACK_PROMPT =\n \"You are a helpful documentation assistant. Answer using the project's documentation.\";\n\n/** The `ai.ask` values the generated endpoint has to carry with it. */\nexport interface AskEndpointOptions {\n /** `ai.ask.cors` — origins allowed to call the route from another site. */\n cors?: string[];\n /** `ai.ask.instructions` — extra system-prompt text. */\n instructions?: string;\n /** `ai.ask.retrieval` — how much documentation each question carries. */\n retrieval?: AskRetrievalOptions;\n}\n\n/** The pieces `askEndpointTemplate` splices in for `ai.ask.cors`. */\ninterface AskCorsTemplate {\n /** The route's closing token: `});` when the POST is wrapped, `};` otherwise. */\n close: string;\n /** The runtime import, when anything is listed. */\n imports: string[];\n /** The opening of `export const POST: APIRoute = `. */\n open: string;\n /** The allow list and the `OPTIONS` handler, spliced after the provider setup. */\n setup: string;\n}\n\n/**\n * `ai.ask.cors`: a browser only lets another origin read the stream when the\n * response names that origin, and a JSON POST preflights first, so the route\n * answers `OPTIONS` and wraps the `POST` in `withCors`, which stamps a listed\n * origin on every response — errors included, so a cross-origin caller can\n * tell a 400 from a 500 — without each `return` having to remember to.\n * Unlisted origins get no allow header and stay subject to the same-origin\n * rule. Left out entirely when nothing is listed, so the default route is\n * unchanged.\n */\nconst askCorsTemplate = (cors: readonly string[] = []): AskCorsTemplate =>\n cors.length > 0\n ? {\n close: \"});\",\n imports: [\n 'import { preflightResponse, withCors } from \"blume/ai/cors.ts\";',\n ],\n open: \"withCors(ALLOWED_ORIGINS, async ({ request }) => {\",\n setup: `\nconst ALLOWED_ORIGINS = ${JSON.stringify(cors)};\n\nexport const OPTIONS: APIRoute = ({ request }) =>\n preflightResponse(request, ALLOWED_ORIGINS);\n`,\n }\n : { close: \"};\", imports: [], open: \"async ({ request }) => {\", setup: \"\" };\n\n/**\n * Largest request body the Ask AI route reads: 64 KB, well above the\n * 24,000-character message budget it validates next, so a real conversation\n * never meets it.\n */\nconst ASK_BODY_LIMIT_BYTES = 65_536;\n\n/**\n * Generate the Ask AI server endpoint (`.blume/src/pages/api/ask.ts`).\n *\n * The provider-specific pieces — imports, the provider factory call, the model\n * expression, the credential guard, and the adapter's reasoning mapping and\n * `providerOptions` — come from the resolved `backend` descriptor, inlined as\n * literals so the route imports the provider SDK by bare name and never\n * `blume.config.ts`. `backend.grounded` decides whether answers are grounded\n * in the retrieved docs (every adapter but Inkeep, which retrieves itself).\n *\n * `options.instructions` (the `ai.ask.instructions` config) is appended to the\n * built-in prompt on every path: the grounded prompt via `createAskContext`,\n * and the plain fallback here. `options.retrieval` (the `ai.ask.retrieval`\n * config) is forwarded to `createAskContext` on the grounded path, where it\n * sizes retrieval. `options.cors` (the `ai.ask.cors` config) adds a preflight\n * handler and wraps the `POST` so every response names a listed origin. All\n * three travel in one options object so a new call site can't silently drop\n * one of them.\n */\nexport const askEndpointTemplate = (\n backend: AskBackend,\n options?: AskEndpointOptions\n): string => {\n const { instructions, retrieval } = options ?? {};\n const { grounded } = backend;\n const fallbackPrompt = instructions\n ? `${ASK_FALLBACK_PROMPT}\\n\\n${instructions}`\n : ASK_FALLBACK_PROMPT;\n // Secrets go through Astro's `getSecret` rather than `process.env`, so each\n // adapter supplies them its own way (Cloudflare from the Worker's bindings,\n // Node and Vercel from the environment).\n const imports = [\n 'import type { APIRoute } from \"astro\";',\n 'import { getSecret } from \"astro:env/server\";',\n 'import { readCappedText } from \"blume/core/request-body.ts\";',\n ...backend.template.imports,\n ];\n let { setup } = backend.template;\n // Ground the answer in retrieved docs, except for RAG-native backends (Inkeep),\n // which run their own retrieval and would conflict with injected context.\n if (grounded) {\n imports.push(\n 'import { createAskContext } from \"blume/ai/ask-context.ts\";',\n 'import askData from \"blume:ask-data\";'\n );\n const groundFields: string[] = [];\n if (instructions) {\n groundFields.push(`instructions: ${JSON.stringify(instructions)}`);\n }\n if (retrieval) {\n groundFields.push(`retrieval: ${JSON.stringify(retrieval)}`);\n }\n const groundOptions =\n groundFields.length > 0 ? `, { ${groundFields.join(\", \")} }` : \"\";\n setup += `\\nconst ground = createAskContext(askData${groundOptions});\\n`;\n }\n const cors = askCorsTemplate(options?.cors);\n imports.push(...cors.imports);\n // Validate the client-supplied body and cap its size. The endpoint is\n // unauthenticated, so bounding message count/length limits how much a caller\n // can spend against the model per request, and restricting roles to\n // user/assistant keeps callers from injecting their own system prompt and\n // repurposing the endpoint as a general LLM proxy; front it with a rate\n // limiter (or your provider's limits) for stronger protection. The body is\n // read under a 64 KB cap before any parsing, since a self-hosted Node server\n // would otherwise buffer an arbitrarily large POST in memory first.\n const validate = ` const text = await readCappedText(request, ${ASK_BODY_LIMIT_BYTES});\n if (text === undefined) {\n return new Response(\"Request too large: the body must be at most 64 KB.\", {\n status: 413,\n });\n }\n let body;\n try {\n body = JSON.parse(text);\n } catch {\n body = null;\n }\n const raw = body?.messages;\n const valid =\n Array.isArray(raw) &&\n raw.length > 0 &&\n raw.length <= 40 &&\n raw.every(\n (m: unknown) =>\n typeof m === \"object\" &&\n m !== null &&\n (\"role\" in m && (m.role === \"user\" || m.role === \"assistant\")) &&\n (\"content\" in m && typeof m.content === \"string\")\n ) &&\n JSON.stringify(raw).length <= 24_000;\n if (!valid) {\n return new Response(\n \"Invalid request: send 1-40 user/assistant messages with string content.\",\n { status: 400 }\n );\n }\n // Re-build the array so only role/content ever reach the model.\n const messages = raw.map((m: { role: \"user\" | \"assistant\"; content: string }) => ({\n content: m.content,\n role: m.role,\n }));`;\n // `streamText` returns synchronously and defers provider/auth/network errors\n // to stream consumption, so the handler's try/catch never sees them: without\n // these the client gets a 200 whose stream aborts mid-flight and nothing is\n // logged server-side. A missing credential is rejected up front with a 503\n // (the adapter's `keyCheck`); everything else is at least logged via\n // `onError`.\n const { keyCheck } = backend.template;\n // Provider errors surface mid-stream, after the 200 is committed; this is\n // the only place they can be observed server-side.\n const onError = ` onError({ error }) {\n console.error(\"Ask AI provider error:\", error);\n },`;\n // The `streamText` argument list, built once so the grounded and plain\n // paths can't drift: they differ only in where the instructions come from.\n // The adapter appends its own call-level fields (its reasoning mapping when\n // that is a call option, and the verbatim `providerOptions`).\n const streamFields = [\n `model: ${backend.template.model}`,\n grounded\n ? \"instructions\"\n : `instructions:\\n ${JSON.stringify(fallbackPrompt)}`,\n \"messages\",\n ...backend.template.fields,\n ];\n const call = ` const result = streamText({\n ${streamFields.join(\",\\n \")},\n${onError}\n });`;\n const stream = grounded\n ? ` const instructions =\n (await ground(messages, body.page)) ??\n ${JSON.stringify(fallbackPrompt)};\n${call}`\n : call;\n const handler = `export const POST: APIRoute = ${cors.open}\n${validate}\n${keyCheck}\n try {\n${stream}\n return createTextStreamResponse({\n stream: toTextStream({ stream: result.stream }),\n });\n } catch {\n return new Response(\"Failed to generate a response.\", { status: 500 });\n }\n${cors.close}`;\n return `// Generated by Blume. Do not edit.\n${imports.join(\"\\n\")}\n\nexport const prerender = false;\n${setup}${cors.setup}\n${handler}\n`;\n};\n\n/**\n * Generate `.blume/src/generated/Ask.astro` — the component behind the\n * `blume:ask` alias that the shared header renders in place of a per-page slot.\n *\n * The header can't import the Ask AI island directly: it's a React component, so\n * the import alone would drag the JSX renderer into the module graph of every\n * project — including the ones that never enable Ask AI and therefore have no\n * React integration wired into their generated Astro config (see `needsReact`).\n * Routing the import through a generated component keeps that dependency behind\n * the config switch: enabled projects get the island, disabled ones get a\n * component that renders nothing and imports no React.\n *\n * `strings` comes from the header (the active locale's dictionary); the empty-\n * state suggestions are read straight from the data snapshot, which is why no\n * page has to pass them.\n */\nexport const askComponentTemplate = (askEnabled: boolean): string =>\n askEnabled\n ? `---\n// Generated by Blume. Do not edit.\nimport AskAI from \"blume/components/islands/AskAI.astro\";\nimport data from \"blume:data\";\n\nconst { strings } = Astro.props;\n---\n\n<AskAI\n endpoint={data.config.ask?.endpoint ?? undefined}\n strings={strings ?? data.ui.ask}\n suggestions={data.config.ask?.suggestions ?? []}\n/>\n`\n : `---\n// Generated by Blume. Do not edit.\n// Ask AI is off (\\`ai.ask.enabled\\`), so the header's Ask trigger renders nothing.\n// Deliberately imports no React island, keeping the JSX renderer out of projects\n// that don't need it.\n---\n`;\n\n/** Generate the static search index endpoint (`/blume-search.json`). */\nexport const searchEndpointTemplate = (): string =>\n `// Generated by Blume. Do not edit.\nimport documents from \"blume:search-index\";\n\nexport const prerender = true;\n\nexport function GET() {\n return new Response(JSON.stringify(documents), {\n headers: { \"Content-Type\": \"application/json\" },\n });\n}\n`;\n\nconst SEARCH_CLIENT_HEADER = \"// Generated by Blume. Do not edit.\\n\";\n\n/** Import the chosen provider's `createSearch` from the Blume package. */\nconst searchClientImport = (module: string): string =>\n `import { createSearch as create } from \"blume/components/layout/search/${module}.ts\";\\n`;\n\n// Joins a base-relative path onto BASE_URL, which arrives with or without a\n// trailing slash (Astro normalizes `base` by `trailingSlash`, which the\n// generated config pins to \"never\" but an owned config may set either way —\n// naive concatenation of a bare `/docs` would yield `/docsblume-search.json`).\nconst SEARCH_BASE_IMPORT =\n 'import { joinBase } from \"blume/components/islands/base-path.ts\";\\n';\n\n/**\n * A client that loads a static `blume-search.json` index (Orama, FlexSearch).\n * `locale` (Orama only) is the site's `i18n.defaultLocale`, which selects a\n * word-segmenting tokenizer for every non-Latin script.\n */\nconst staticSearchClient = (module: string, locale?: string): string =>\n `${SEARCH_CLIENT_HEADER}${searchClientImport(module)}${SEARCH_BASE_IMPORT}\nconst indexUrl = joinBase(import.meta.env.BASE_URL, \"blume-search.json\");\n\nexport const createSearch = () => create({ indexUrl${\n locale ? `, locale: ${JSON.stringify(locale)}` : \"\"\n } });\n`;\n\n/**\n * A client that passes the adapter's options — public credentials, by\n * contract — straight to the provider SDK. The options are inlined verbatim\n * as a literal: Blume maps only the fields it names, and any extra option\n * the adapter was given rides along untouched.\n */\nconst hostedSearchClient = (\n provider: Extract<ResolvedSearchAdapter, { mode: \"hosted\" }>\n): string =>\n `${SEARCH_CLIENT_HEADER}${searchClientImport(provider.kind)}\nexport const createSearch = () => create(${JSON.stringify(provider.options)});\n`;\n\n/**\n * Generate `.blume/src/generated/features.ts` — the client-feature loaders\n * behind the `blume:features` alias. Each loader is a dynamic import when the\n * site uses the feature and `null` otherwise, so an unused library is absent\n * from the module graph (never bundled, never pre-bundled in dev) rather than\n * merely lazy. Regenerated on every pass: a page that gains a mermaid fence\n * turns the loader on.\n */\nexport const featuresTemplate = (features: ClientFeatures): string =>\n `// Generated by Blume. Do not edit.\n//\n// The client features this site uses. A feature no page needs stays out of\n// the module graph entirely: its loader is null and its library never enters\n// the client bundle.\n\n/** Registers the <blume-mermaid> element; null when no page has a mermaid fence. */\nexport const loadMermaid: (() => Promise<unknown>) | null = ${\n features.mermaid\n ? '() => import(\"blume/components/content/mermaid-element.ts\")'\n : \"null\"\n };\n\n/** The EPUB generator's browser bundle; null when export.epub is off. */\nexport const loadEpub:\n | (() => Promise<typeof import(\"epub-gen-memory/bundle\")>)\n | null = ${features.epub ? '() => import(\"epub-gen-memory/bundle\")' : \"null\"};\n`;\n\n/**\n * Generate `.blume/src/generated/search-client.ts` — the provider-specific\n * loader the `<Search>` component lazy-imports via the `blume:search-client`\n * alias. Only the configured provider's module (and therefore its SDK) is\n * referenced, so the build bundles exactly one backend. Public credentials are\n * baked in here; secret keys never reach the client.\n */\nexport const searchClientTemplate = (config: ResolvedConfig): string => {\n const { provider } = config.search;\n\n switch (provider.mode) {\n case \"static\": {\n // Only Orama derives a tokenizer from the locale; FlexSearch has no\n // equivalent hook, so its client keeps the bare index URL.\n const locale =\n provider.kind === \"orama\" ? config.i18n?.defaultLocale : undefined;\n return staticSearchClient(provider.kind, locale);\n }\n case \"hosted\": {\n return hostedSearchClient(provider);\n }\n case \"server\": {\n return `${SEARCH_CLIENT_HEADER}${searchClientImport(\"endpoint\")}${SEARCH_BASE_IMPORT}\nconst api = joinBase(import.meta.env.BASE_URL, \"api/search\");\n\nexport const createSearch = () => create({ api });\n`;\n }\n case \"pagefind\": {\n return `${SEARCH_CLIENT_HEADER}${searchClientImport(\"pagefind\")}${SEARCH_BASE_IMPORT}\nconst url = joinBase(import.meta.env.BASE_URL, \"pagefind/pagefind.js\");\n\nexport const createSearch = () => create({ url });\n`;\n }\n default: {\n // Search disabled: a no-op client so the alias always resolves.\n return `${SEARCH_CLIENT_HEADER}export const createSearch = () => () =>\n Promise.resolve({ hits: [], sections: [] });\n`;\n }\n }\n};\n\n/**\n * Generate the Mixedbread search endpoint (`/api/search`). It holds the secret\n * key server-side and proxies semantic queries to the configured store. The\n * adapter's options are inlined as a literal so the route never imports the\n * config. The result mapping is best-effort and may need tuning to how your\n * content was synced (see the Mixedbread sync step / \\`mxbai vs sync\\`).\n */\nexport const mixedbreadSearchEndpointTemplate = (\n options: MixedbreadOptions\n): string =>\n `// Generated by Blume. Do not edit.\nimport type { APIRoute } from \"astro\";\nimport { getSecret } from \"astro:env/server\";\nimport Mixedbread from \"@mixedbread/sdk\";\nimport { readCappedText } from \"blume/core/request-body.ts\";\n\nexport const prerender = false;\n\nconst client = new Mixedbread({ apiKey: getSecret(\"MIXEDBREAD_API_KEY\") ?? \"\" });\nconst OPTIONS = ${JSON.stringify(options)};\n// Every option besides the store reaches the search call verbatim.\nconst { storeId: STORE_ID, ...SEARCH_OPTIONS } = OPTIONS;\n\nexport const POST: APIRoute = async ({ request }) => {\n // A search body is one short query: read it under a 16 KB cap, so a\n // self-hosted server never buffers an arbitrarily large POST first.\n const text = await readCappedText(request, 16_384);\n if (text === undefined) {\n return new Response(\"Request too large: the body must be at most 16 KB.\", {\n status: 413,\n });\n }\n // The endpoint is public: a malformed body must 200-empty, not 500.\n let body = null;\n try {\n body = JSON.parse(text);\n } catch {\n body = null;\n }\n const query = body?.query;\n if (!query || typeof query !== \"string\") {\n return new Response(\"[]\", {\n headers: { \"Content-Type\": \"application/json\" },\n });\n }\n // \\`top_k\\` defaults to 8 unless an option sets it; the query and store\n // always come from the request and \\`storeId\\`.\n const response = await client.stores.search({\n top_k: 8,\n ...SEARCH_OPTIONS,\n query,\n store_identifiers: [STORE_ID],\n });\n const hits = (response.data ?? []).map((chunk) => {\n const meta = chunk.generated_metadata ?? {};\n // Only a text chunk carries \\`text\\`; image, audio, and video chunks fall\n // back to the excerpt the store generated for them.\n const text = \"text\" in chunk ? chunk.text : undefined;\n return {\n excerpt: text ?? meta.excerpt ?? \"\",\n title: meta.title ?? chunk.filename ?? \"\",\n url: meta.url ?? \"\",\n };\n });\n return new Response(JSON.stringify(hits), {\n headers: { \"Content-Type\": \"application/json\" },\n });\n};\n`;\n\n/**\n * Generate the raw-Markdown endpoints (`[...slug].md.ts` and `[...slug].mdx.ts`).\n * Both read `raw-markdown.json`, whose entries hold the verbatim source (`mdx`)\n * plus a component-downleveled variant (`md`) when the page uses components:\n * `/<route>.mdx` serves the source exactly as written, `/<route>.md` serves\n * plain Markdown with `<TypeTable>`-style components converted for consumers\n * that can't interpret JSX.\n */\nexport const rawMarkdownEndpointTemplate = (kind: \"md\" | \"mdx\"): string =>\n `// Generated by Blume. Do not edit.\nimport raw from \"blume:raw-markdown\";\n\nexport const prerender = true;\n\nexport function getStaticPaths() {\n return Object.keys(raw).map((route) => ({\n params: { slug: route === \"/\" ? \"index\" : route.slice(1) },\n props: { route },\n }));\n}\n\nexport function GET({ props }: { props: { route: string } }) {\n const entries = raw as Record<string, { md?: string; mdx?: string }>;\n const entry = entries[props.route];\n const body = entry ? ${\n kind === \"md\" ? '(entry.md ?? entry.mdx ?? \"\")' : '(entry.mdx ?? \"\")'\n } : \"\";\n return new Response(body, {\n headers: {\n \"Content-Type\": \"text/markdown; charset=utf-8\",\n // ~4 characters per token; keep in sync with markdownTokenCount.\n \"x-markdown-tokens\": String(Math.ceil(body.length / 4)),\n },\n });\n}\n`;\n\n/**\n * Generate the `/blume-assets/[...asset]` endpoint. It serves two families of\n * files that exist on disk but outside the public dir:\n * - `content/<project-relative path>` — colocated content images\n * (`![alt](./diagram.png)`), served as originals for the agent-facing\n * Markdown endpoints (the HTML render uses the `astro:assets`-optimized\n * copies instead). The mapping comes from `generated/content-assets.json`.\n * - `<source>/<hash>.<ext>` — remote-source images the scan pipeline\n * materializes under `.blume/public/blume-assets` (see\n * `core/sources/assets.ts`). That directory is NOT Astro's `publicDir`\n * (which points at the user project's `public/`), so without this endpoint\n * those references 404.\n * Prerendered: every asset becomes a static file in the build output; the dev\n * server renders on demand, so new images appear without a restart.\n */\nexport const contentAssetsEndpointTemplate = (\n stagedAssetsDir: string\n): string =>\n `// Generated by Blume. Do not edit.\nimport { existsSync } from \"node:fs\";\nimport { readdir, readFile } from \"node:fs/promises\";\nimport { isAbsolute, join, relative, resolve } from \"node:path\";\nimport type { APIRoute } from \"astro\";\nimport assets from \"blume:content-assets\";\n\nexport const prerender = true;\n\nconst files = assets as Record<string, string>;\nconst STAGED_DIR = ${JSON.stringify(stagedAssetsDir)};\n\nconst TYPES: Record<string, string> = {\n \".apng\": \"image/apng\",\n \".avif\": \"image/avif\",\n \".bmp\": \"image/bmp\",\n \".gif\": \"image/gif\",\n \".ico\": \"image/x-icon\",\n \".jpeg\": \"image/jpeg\",\n \".jpg\": \"image/jpeg\",\n \".png\": \"image/png\",\n \".svg\": \"image/svg+xml\",\n \".tiff\": \"image/tiff\",\n \".webp\": \"image/webp\",\n};\n\nconst contentType = (path: string): string =>\n TYPES[path.slice(path.lastIndexOf(\".\")).toLowerCase()] ??\n \"application/octet-stream\";\n\nconst stagedParams = async (): Promise<string[]> => {\n if (!existsSync(STAGED_DIR)) {\n return [];\n }\n const entries = await readdir(STAGED_DIR, {\n recursive: true,\n withFileTypes: true,\n });\n return entries\n .filter((entry) => entry.isFile())\n .map((entry) =>\n relative(STAGED_DIR, join(entry.parentPath, entry.name)).replaceAll(\n \"\\\\\\\\\",\n \"/\"\n )\n );\n};\n\nexport async function getStaticPaths() {\n const params = [\n ...Object.keys(files).map((key) => \"content/\" + key),\n ...(await stagedParams()),\n ];\n return params.map((asset) => ({ params: { asset } }));\n}\n\nconst resolveAsset = (asset: string): string | null => {\n if (asset.startsWith(\"content/\")) {\n return files[asset.slice(\"content/\".length)] ?? null;\n }\n const abs = resolve(STAGED_DIR, asset);\n // Traversal guard: the dev server renders on demand, so the param is\n // attacker-controlled there — never step outside the staged directory.\n // path.relative rather than a string-prefix test: STAGED_DIR is baked in\n // with forward slashes while resolve() answers in the platform's\n // separators, so on Windows the prefix test 404'd every legitimate staged\n // asset — and a bare prefix also admits a sibling directory that merely\n // shares the name.\n const rel = relative(STAGED_DIR, abs);\n if (rel === \"\" || rel.startsWith(\"..\") || isAbsolute(rel)) {\n return null;\n }\n return existsSync(abs) ? abs : null;\n};\n\nexport const GET: APIRoute = async ({ params }) => {\n const path = resolveAsset(String(params.asset ?? \"\"));\n if (!path) {\n return new Response(null, { status: 404 });\n }\n const body = await readFile(path);\n const type = contentType(path);\n // An SVG opened directly is a document that can run script; the sandbox\n // keeps an uploaded one inert on the docs origin. Static hosts get the same\n // header from the build's header rules.\n const headers: Record<string, string> =\n type === \"image/svg+xml\"\n ? { \"Content-Security-Policy\": ${JSON.stringify(SVG_ASSET_POLICY)}, \"Content-Type\": type }\n : { \"Content-Type\": type };\n return new Response(new Uint8Array(body), { headers });\n};\n`;\n\n/** The `src/pages` file that serves a route, e.g. `/mcp` -> `mcp.ts`. */\nexport const mcpPageFile = (route: string): string =>\n `${trimChar(route, \"/\")}.ts`;\n\n/**\n * Generate the hosted MCP server endpoint (e.g. `.blume/src/pages/mcp.ts`). A\n * thin wrapper around the shipped `createMcpFetchHandler`, served from the\n * generated data snapshot. Runs server-side (no prerender) so agents can query\n * the docs over Streamable HTTP.\n */\nexport const mcpEndpointTemplate = (): string =>\n `// Generated by Blume. Do not edit.\nimport type { APIRoute } from \"astro\";\nimport { createMcpFetchHandler } from \"blume/ai/mcp/server.ts\";\nimport data from \"blume:mcp-data\";\n\nexport const prerender = false;\n\nconst handler = createMcpFetchHandler(data);\n\nexport const ALL: APIRoute = ({ request }) => handler(request);\n`;\n\n/**\n * Generate the playground's CORS proxy endpoint\n * (`.blume/src/blume-openapi/api-proxy.ts`), behind\n * `playground: { proxy: true }` on an `openapi()` reference. A thin\n * server-rendered wrapper around the\n * shipped `createPlaygroundProxyHandler`; injected at `/_api-proxy` rather\n * than written under `pages/` because Astro treats `_`-prefixed page files as\n * private.\n *\n * `origins` — the origins of the servers the documented specs declare — is\n * baked in as the handler's allowlist. It cannot come from the request or from\n * client-side data: that is the whole trust boundary keeping the endpoint from\n * being an open proxy onto the deployment's own network.\n */\nexport const playgroundProxyTemplate = (origins: string[]): string =>\n `// Generated by Blume. Do not edit.\nimport type { APIRoute } from \"astro\";\nimport { createPlaygroundProxyHandler } from \"blume/openapi/proxy.ts\";\n\nexport const prerender = false;\n\nconst handler = createPlaygroundProxyHandler(${JSON.stringify(origins)});\n\nexport const ALL: APIRoute = ({ request }) => handler(request);\n`;\n\n/** Generate a prerendered endpoint that serves a fixed JSON payload. */\nexport const staticJsonEndpointTemplate = <Payload extends object>(\n payload: Payload\n): string =>\n `// Generated by Blume. Do not edit.\nexport const prerender = true;\n\nconst payload = ${JSON.stringify(payload, null, 2)};\n\nexport function GET() {\n return new Response(JSON.stringify(payload), {\n headers: { \"Content-Type\": \"application/json\" },\n });\n}\n`;\n\n/**\n * Generate the RSS endpoint (`[section]/rss.xml.ts`). One feed per content\n * type is served from the generated `rss.json`, e.g. `/blog/rss.xml`.\n */\nexport const rssEndpointTemplate = (): string =>\n `// Generated by Blume. Do not edit.\nimport feeds from \"blume:rss\";\n\nexport const prerender = true;\n\nexport function getStaticPaths() {\n return Object.keys(feeds).map((section) => ({\n params: { section },\n props: { section },\n }));\n}\n\nexport function GET({ props }: { props: { section: string } }) {\n const bySection = feeds as Record<string, string>;\n return new Response(bySection[props.section] ?? \"\", {\n headers: { \"Content-Type\": \"application/rss+xml; charset=utf-8\" },\n });\n}\n`;\n\n/**\n * Generate the deferred sidebar fragments page\n * (`.blume/src/pages/blume-nav/[version]/[locale]/[id].astro`): one\n * prerendered partial per collapsible or drill-in group, holding just that\n * group's rows, which the sidebar fetches on first open instead of carrying\n * every collapsed section on every page. Ids are `navGroupIds` over the full\n * tree, the same ids the pages' sidebars use; the `version`/`locale`\n * segments pick the navigation variant (`current`/`default` for the\n * unversioned, unlocalized tree, and `default` again for the default locale\n * while its URL prefix is hidden: Astro's i18n routing 404s a page URL that\n * carries the default locale's code as a segment). Rendered with no current\n * route — a section that isn't on the active path has no active row by\n * definition.\n */\nexport const navFragmentTemplate = (): string =>\n `---\n// Generated by Blume. Do not edit.\nimport NavTree from \"blume/components/layout/NavTree.astro\";\nimport { withBase } from \"blume/components/islands/base-path.ts\";\nimport {\n hiddenDefaultLocale,\n navGroupIds,\n navVariants,\n} from \"blume/components/layout/nav-utils.ts\";\nimport type { NavNode } from \"blume/core/types.ts\";\nimport data from \"blume:data\";\n\nexport const prerender = true;\n// A partial: no doctype, head, or script/style injection — the rows only.\nexport const partial = true;\n\n// Only imports are in scope here (Astro hoists getStaticPaths), which is why\n// the variant walk lives in nav-utils.\nexport function getStaticPaths() {\n return navVariants(data, hiddenDefaultLocale(data.config.i18n)).flatMap(\n ({ locale, navigation, version }) =>\n [...navGroupIds(navigation.sidebar)]\n .filter(\n ([node]) => node.kind === \"group\" && (node.display ?? \"flat\") !== \"flat\"\n )\n .map(([, id]) => ({ params: { id, locale, version } }))\n );\n}\n\nconst { id, locale, version } = Astro.params;\nconst variant = navVariants(data, hiddenDefaultLocale(data.config.i18n)).find(\n (entry) => entry.version === version && entry.locale === locale\n);\nconst ids = variant\n ? navGroupIds(variant.navigation.sidebar)\n : new Map<NavNode, string>();\nconst node = [...ids].find(([, groupId]) => groupId === id)?.[0];\nif (!(variant && node && node.kind === \"group\")) {\n return new Response(null, { status: 404 });\n}\nconst strings =\n locale === \"default\" ? data.ui.nav : (data.uiByLocale[locale] ?? data.ui).nav;\nconst fragmentBase = withBase(\\`/blume-nav/\\${version}/\\${locale}\\`);\n---\n\n<NavTree\n currentRoute=\"\"\n depth={1}\n fragmentBase={fragmentBase}\n idPrefix={id}\n ids={ids}\n items={node.children}\n root={false}\n strings={strings}\n/>\n`;\n\n/** Generate the OG image endpoint (`.blume/src/pages/og/[...slug].png.ts`). */\nexport const ogEndpointTemplate = (\n customRoutes: OgCustomRoute[] = [],\n og: {\n /**\n * The on-disk card cache (directory plus the rendering Blume version).\n * Absent, every card renders on every build.\n */\n cache?: OgCache;\n families?: OgFontFamilies;\n fonts?: OgFont[];\n /**\n * Whether a page's own description may replace the site-wide subtitle.\n * `false` when `seo.og.description` is `false`, which hides the subtitle\n * on every card, page text included. Defaults to `true`.\n */\n pageDescriptions?: boolean;\n } = {},\n includeChangelog = false\n): string =>\n `// Generated by Blume. Do not edit.\nimport { cachedOgImage } from \"blume/og\";\nimport type { OgCache, OgFont, OgFontFamilies } from \"blume/og\";\nimport data from \"blume:data\";\n\nexport const prerender = true;\n\n// Custom (non-content) pages opted into a generated card, baked in at build.\n// The annotation keeps the empty-array case from being an implicit any[]\n// (ts(7034)) under a strict tsconfig.\nconst customRoutes: { slug: string; title: string }[] = ${JSON.stringify(customRoutes)};\n\n// Card fonts, resolved at generation time (explicit seo.og.fonts, or derived\n// from theme.fonts). Local entries carry absolute build-machine paths, which\n// is why they are baked into this build-only endpoint instead of the runtime\n// data that pages serialize into HTML.\nconst fonts: OgFont[] = ${JSON.stringify(og.fonts ?? [])};\nconst families: OgFontFamilies | undefined = ${\n og.families ? JSON.stringify(og.families) : \"undefined\"\n };\n\n// Rendered cards are kept on disk between builds, keyed by everything that\n// decides their pixels, so a rebuild renders only the cards whose title,\n// description, branding, or fonts changed. The directory is a build-machine\n// path, baked in for the same reason as the local font paths above.\nconst cache: OgCache | undefined = ${\n og.cache ? JSON.stringify(og.cache) : \"undefined\"\n };\n\n// A page's own description (its \\`seo.description\\`, else \\`description\\`) is\n// the card subtitle, so the image says what the page's og:description says.\n// Pages without one fall back to the site-wide subtitle at render time.\n// \\`seo.og.description: false\\` hides the subtitle on every card, page text\n// included, which is what switches this off.\nconst pageDescriptions = ${og.pageDescriptions !== false};\n\ninterface CardProps {\n title: string;\n description: string | null;\n}\n\nexport function getStaticPaths() {\n const seen = new Set<string>();\n const paths: { params: { slug: string }; props: CardProps }[] = [];\n const add = (slug: string, title: string, description: string | null) => {\n if (seen.has(slug)) {\n return;\n }\n seen.add(slug);\n paths.push({\n params: { slug },\n props: { title, description: pageDescriptions ? description : null },\n });\n };\n // A custom page wins over a content route sharing its path, so add it first.\n // Its description is unknown at generate time, so it takes the site subtitle.\n for (const route of customRoutes) {\n add(route.slug, route.title, null);\n }\n for (const route of data.routes) {\n add(\n route.path === \"/\" ? \"index\" : route.path.slice(1),\n route.title,\n route.description\n );\n }${\n includeChangelog\n ? `\n // The generated changelog index is not a content route, so it needs its own\n // card. Added last: a custom page or content route owning /changelog wins.\n add(\n \"changelog\",\n data.ui.changelog?.title ?? \"Changelog\",\n data.ui.changelog?.description ?? null\n );`\n : \"\"\n }\n return paths;\n}\n\n// Footer branding shared by every card. The slug comes from the configured\n// repo rather than the URL, so an Enterprise host reads the same as github.com;\n// the site text (host plus deployment base) is resolved at generate time.\nconst repoSlug = data.config.github\n ? \\`\\${data.config.github.owner}/\\${data.config.github.repo}\\`\n : undefined;\n\nexport async function GET({ props }: { props: CardProps }) {\n const png = await cachedOgImage(cache, {\n accent: data.config.og.palette?.accent ?? data.config.theme.accent.light,\n brand: data.config.title,\n description: props.description ?? data.config.og.description,\n families,\n fonts,\n logo: data.config.og.logo,\n palette: data.config.og.palette,\n repo: repoSlug,\n site: data.config.og.site,\n title: props.title,\n });\n return new Response(new Uint8Array(png), {\n headers: {\n \"Cache-Control\": \"public, max-age=31536000, immutable\",\n \"Content-Type\": \"image/png\",\n },\n });\n}\n`;\n\n/**\n * Generate a Scalar API/AsyncAPI reference page (`.blume/src/pages/<route>.astro`).\n * The reference UI is owned by Scalar (its standalone bundle, loaded from a CDN)\n * but mounted inside Blume's {@link ReferenceLayout} so the page keeps Blume's\n * navbar on top. `renderMode: \"client\"` mounts the reference into a container\n * element (rather than emitting a full HTML document), which is what lets it\n * live inside our shell.\n */\nexport const scalarReferenceTemplate = <Configuration extends object>(options: {\n /** Scalar options forwarded verbatim (spec/theme config plus the author's `scalar` escape hatch). */\n configuration: Configuration;\n noindex?: boolean;\n route: string;\n title: string;\n}): string =>\n `---\n// Generated by Blume. Do not edit.\nimport { ScalarComponent } from \"@scalar/astro\";\nimport ReferenceLayout from \"blume/components/layout/ReferenceLayout.astro\";\nimport data from \"blume:data\";\n\nexport const prerender = true;\n\nconst configuration = ${JSON.stringify(options.configuration, null, 2)};\n\n// The reference is an unlocalized route, so its chrome renders in the default\n// locale's language and direction (\\`data.ui\\` is the default locale's resolved\n// dictionary), mirroring the changelog index's locale wiring.\nconst i18n = data.config.i18n;\nconst localeMeta = i18n\n ? i18n.locales.find((l) => l.code === i18n.defaultLocale)\n : null;\nconst dir = localeMeta?.dir ?? \"ltr\";\nconst htmlLang = i18n ? i18n.defaultLocale : \"en\";\n---\n\n<ReferenceLayout\n analytics={data.config.analytics}\n banner={data.config.banner}\n dir={dir}\n fontCssVars={data.fontCssVars}\n locale={htmlLang}\n logo={data.config.logo}\n favicon={data.config.favicon}\n appleIcon={data.config.appleIcon}\n navigation={data.navigation}\n noindex={${options.noindex === true}}\n pageTitle={${JSON.stringify(options.title)}}\n route={${JSON.stringify(options.route)}}\n searchEnabled={data.config.search.enabled}\n site={{ title: data.config.title, description: data.config.description }}\n themeMode={data.config.theme.mode}\n ui={data.ui}\n>\n <ScalarComponent configuration={configuration} renderMode=\"client\" />\n</ReferenceLayout>\n`;\n\n/**\n * The component map every rendered entry body receives via\n * `<Content components={...} />`: Blume's content components, `<Math>` when\n * math is enabled, the island wrappers, and the user's `mdxComponents`\n * overrides. Shared by the catch-all page and the changelog index so a\n * `:::` directive (compiled to `<Callout>`) or an explicit `<Steps>` renders\n * wherever an entry is rendered — an MDX body throws \"Expected component\n * `X` to be defined\" the moment a page renders it without this map.\n */\nconst contentComponentsSource = (mathEnabled: boolean) => {\n const mathImport = mathEnabled\n ? 'import Math from \"blume/components/content/Math.astro\";\\n'\n : \"\";\n const mathEntry = mathEnabled ? \"Math,\\n \" : \"\";\n return {\n imports: `import Accordion from \"blume/components/content/Accordion.astro\";\nimport AccordionItem from \"blume/components/content/AccordionItem.astro\";\nimport AutoTypeTable from \"blume/components/content/AutoTypeTable.astro\";\nimport Badge from \"blume/components/content/Badge.astro\";\nimport Callout from \"blume/components/content/Callout.astro\";\nimport Card from \"blume/components/content/Card.astro\";\nimport CardGroup from \"blume/components/content/CardGroup.astro\";\nimport CodeBlock from \"blume/components/content/CodeBlock.astro\";\nimport CodeGroup from \"blume/components/content/CodeGroup.astro\";\nimport ColorRoot from \"blume/components/content/Color.astro\";\nimport ColorItem from \"blume/components/content/ColorItem.astro\";\nimport ColorRow from \"blume/components/content/ColorRow.astro\";\nimport Column from \"blume/components/content/Column.astro\";\nimport Columns from \"blume/components/content/Columns.astro\";\nimport Component from \"blume/components/content/Component.astro\";\nimport Diff from \"blume/components/content/Diff.astro\";\nimport Expandable from \"blume/components/content/Expandable.astro\";\nimport FileTree from \"blume/components/content/FileTree.astro\";\nimport Frame from \"blume/components/content/Frame.astro\";\nimport GithubInfo from \"blume/components/content/GithubInfo.astro\";\nimport Panel from \"blume/components/content/Panel.astro\";\nimport Prompt from \"blume/components/content/Prompt.astro\";\nimport Step from \"blume/components/content/Step.astro\";\nimport Steps from \"blume/components/content/Steps.astro\";\nimport Tab from \"blume/components/content/Tab.astro\";\nimport Tabs from \"blume/components/content/Tabs.astro\";\nimport Tile from \"blume/components/content/Tile.astro\";\nimport Tooltip from \"blume/components/content/Tooltip.astro\";\nimport TreeRoot from \"blume/components/content/Tree.astro\";\nimport TreeFile from \"blume/components/content/TreeFile.astro\";\nimport TreeFolder from \"blume/components/content/TreeFolder.astro\";\nimport TypeTable from \"blume/components/content/TypeTable.astro\";\nimport Visibility from \"blume/components/content/Visibility.astro\";\nimport YouTube from \"blume/components/content/YouTube.astro\";\nimport Icon from \"blume/components/Icon.astro\";\nimport LocaleLinks from \"blume/components/layout/LocaleLinks.astro\";\nimport ApiOverview from \"blume/components/openapi/ApiOverview.astro\";\nimport ApiTagOperations from \"blume/components/openapi/ApiTagOperations.astro\";\nimport Operation from \"blume/components/openapi/Operation.astro\";\n${mathImport}import { mdxComponents as userMdx, layoutOverrides } from \"../generated/components.ts\";`,\n map: `{\n Accordion,\n AccordionItem,\n ApiOverview,\n ApiTagOperations,\n AutoTypeTable,\n Badge,\n Callout,\n Card,\n CardGroup,\n CodeBlock,\n CodeGroup,\n Color,\n Column,\n Columns,\n Component,\n Diff,\n Expandable,\n FileTree,\n Frame,\n GithubInfo,\n Icon,\n Operation,\n Panel,\n Prompt,\n Step,\n Steps,\n Tab,\n Tabs,\n Tile,\n Tooltip,\n Tree,\n TypeTable,\n Visibility,\n YouTube,\n ${mathEntry}...userMdx,\n}`,\n };\n};\n\nexport const catchAllPageTemplate = (options: {\n exportEpub: boolean;\n exportPdf: boolean;\n mathEnabled: boolean;\n /**\n * Whether the sidebar defers collapsed sections to prerendered fragments\n * (see `navFragmentTemplate`); true when some group renders as a\n * disclosure or a drill-in panel.\n */\n navFragments?: boolean;\n /** Serialize the island-hooks snapshot; only needed when React is enabled. */\n needsReact: boolean;\n}): string => {\n const { imports: componentImports, map: componentMap } =\n contentComponentsSource(options.mathEnabled);\n // The island-hooks snapshot (config + navigation + page) for `blume/hooks`.\n const clientData = options.needsReact\n ? \"\\n clientData={{ config: data.config, navigation, page: { route, title: seo.title ?? title } }}\"\n : \"\";\n\n return `---\n// Generated by Blume. Do not edit.\nimport { getEntry, render } from \"astro:content\";\nimport type { CollectionKey } from \"astro:content\";\nimport RootLayout from \"blume/components/layout/RootLayout.astro\";\nimport { withBase } from \"blume/components/islands/base-path.ts\";\nimport { stripBasePath, withBasePath } from \"blume/core/base-path.ts\";\nimport { resolveSlot } from \"blume/components/layout/overrides.ts\";\n${componentImports}\nimport data from \"blume:data\";\n\nconst Color = Object.assign(ColorRoot, { Item: ColorItem, Row: ColorRow });\nconst Tree = Object.assign(TreeRoot, { File: TreeFile, Folder: TreeFolder });\n\n// Docs content is file-based and always prerendered, even in server output\n// (where only endpoints like /api/ask render on demand). Without this, server\n// builds would render this route on demand and ignore getStaticPaths, leaving\n// the entry id undefined.\nexport const prerender = true;\n\nconst components = ${componentMap};\n\nexport function getStaticPaths() {\n return data.routes.map((route) => ({\n params: { slug: route.path === \"/\" ? undefined : route.path.slice(1) },\n props: {\n alternates: route.alternates,\n collection: route.collection,\n editUrl: route.editUrl,\n entryId: route.entryId,\n fallback: route.fallback,\n indexable: route.indexable,\n lastModified: route.lastModified,\n locale: route.locale,\n monolingual: route.monolingual,\n route: route.path,\n title: route.title,\n version: route.version,\n versionAlternates: route.versionAlternates,\n },\n }));\n}\n\nconst { entryId, collection, route, title, indexable, editUrl, lastModified, locale, alternates, fallback, monolingual, version, versionAlternates } = Astro.props;\nconst entry = await getEntry(collection as CollectionKey, entryId);\nif (!entry) {\n return new Response(null, { status: 404 });\n}\nconst { Content, headings: allHeadings, remarkPluginFrontmatter } = await render(entry);\n// \\`[!toc]\\`-marked headings render on the page but stay out of the table of\n// contents; the heading plugin reports their slugs through the render's\n// frontmatter (see markdown/heading-anchors.ts). Only the plugin's array\n// counts: \\`frontmatter.extend\\` can declare the same key, and on a page with\n// no headings that user-supplied value would pass straight through.\nconst tocHiddenRaw = remarkPluginFrontmatter?.${TOC_HIDDEN_KEY};\nconst tocHidden = new Set(Array.isArray(tocHiddenRaw) ? tocHiddenRaw : []);\nconst headings =\n tocHidden.size > 0\n ? allHeadings.filter((heading) => !tocHidden.has(heading.slug))\n : allHeadings;\nconst frontmatter = entry.data;\n\nconst seo = frontmatter.seo;\nconst base = data.config.site ? data.config.site.replace(/\\\\/$/, \"\") : null;\n\n// Percent-encode the route-derived path (the sitemap convention): a Unicode\n// slug (\\`/api/größe\\`) is not legal in a raw URI, and crawlers compare\n// canonical against the sitemap's encoded <loc> byte-for-byte.\nconst ogPath = data.config.og.enabled\n ? encodeURI(\\`/og/\\${route === \"/\" ? \"index\" : route.slice(1)}.png\\`)\n : null;\nconst ogRel = seo.image ?? ogPath;\n// Absolute URLs also carry the deployment base (the page is served under it):\n// \\`site + base + path\\`. Only absolutize root-relative paths: \\`seo.image\\` may be\n// an external URL, which passes through verbatim (mirrors PageLayout).\nconst ogImage =\n ogRel && base && ogRel.startsWith(\"/\") ? \\`\\${base}\\${withBase(ogRel)}\\` : ogRel;\n// Blume's generated card has known dimensions the layout can declare; a user's\n// \\`seo.image\\` could be any size or format, so it gets none.\nconst ogGenerated = !seo.image && Boolean(ogPath);\n\n// X attribution: the site's account, plus a creator the page can claim for\n// itself (a guest post crediting its own author) over the configured default.\nconst x = { ...data.config.x, ...(seo.x?.creator ? { creator: seo.x.creator } : {}) };\n\nconst basedRoute = withBase(route);\n\n// Locale resolution. With i18n on, pick the active locale's nav + dictionary,\n// build hreflang alternates, and derive the language-switcher targets.\nconst i18n = data.config.i18n;\nconst localePrefix = (codeArg: string) =>\n i18n && codeArg === i18n.defaultLocale && i18n.hideDefaultLocalePrefix\n ? \"\"\n : \\`/\\${codeArg}\\`;\nconst localizeRoute = (logical: string, codeArg: string) => {\n const prefix = localePrefix(codeArg);\n if (!prefix) {\n return logical;\n }\n return logical === \"/\" ? prefix : \\`\\${prefix}\\${logical}\\`;\n};\nconst stripLocale = (path: string, codeArg: string) => {\n const prefix = localePrefix(codeArg);\n return prefix && path.startsWith(prefix) ? path.slice(prefix.length) || \"/\" : path;\n};\n\n// Version resolution. An archived page renders its snapshot's navigation tree,\n// points its canonical at the latest equivalent (unless configured otherwise),\n// and shows the old-version notice.\nconst versionsConfig = data.config.versions;\nconst archived = versionsConfig && version\n ? (versionsConfig.archived.find((v) => v.id === version) ?? null)\n : null;\nconst latestVersionAlt = (versionAlternates ?? []).find((alt) => alt.version === \"\");\n\nconst navigation = version\n ? (data.navigationByVersion[version]?.[i18n ? locale : \"\"] ?? data.navigation)\n : i18n\n ? (data.navigationByLocale[locale] ?? data.navigation)\n : data.navigation;\nconst ui = i18n ? (data.uiByLocale[locale] ?? data.ui) : data.ui;\nconst localeMeta = i18n ? i18n.locales.find((l) => l.code === locale) : null;\nconst dir = localeMeta?.dir ?? \"ltr\";\nconst htmlLang = i18n ? locale : \"en\";\n// A fallback page renders the fallback locale's content, so its text direction\n// follows that language — not the (mirrored) page locale.\nconst contentLocale =\n fallback && i18n?.fallbackLocale ? i18n.fallbackLocale : locale;\nconst contentDir = i18n\n ? (i18n.locales.find((l) => l.code === contentLocale)?.dir ?? \"ltr\")\n : \"ltr\";\n// The root route keeps its trailing slash (\\`https://site/\\`) so canonical and\n// hreflang URLs byte-match the sitemap's <loc> for the home page.\nconst absolute = (path: string) => base + withBase(path);\n\n// A fallback page renders the fallback locale's page at this locale's URL, so\n// its canonical names the page it copies, and search engines never rank the\n// copy against the original.\nconst fallbackSource =\n fallback && i18n?.fallbackLocale\n ? (alternates ?? []).find((alt) => alt.locale === i18n.fallbackLocale)\n : undefined;\n// The page it copies, when that page is archived too, points on to the latest\n// docs; the copy names that final page directly, the fallback locale's\n// version of the latest one, so the canonical is never a chain.\nconst fallbackLatest =\n fallbackSource && archived && archived.canonical === \"latest\" && latestVersionAlt\n ? (data.routes.find((entry) => entry.path === latestVersionAlt.path)?.alternates ?? []).find(\n (alt) => alt.locale === i18n?.fallbackLocale\n )\n : undefined;\nconst fallbackTarget = fallbackLatest ?? fallbackSource;\n// An archived page defaults its canonical to the same page in the latest docs\n// when that page still exists — search engines treat the live page as\n// authoritative without deindexing version-only content. A page's own\n// \\`seo.canonical\\` always wins, and \\`canonical: \"self\"\\` keeps the default.\nconst canonical =\n seo.canonical ??\n (fallbackTarget && base\n ? absolute(fallbackTarget.path)\n : archived && archived.canonical === \"latest\" && latestVersionAlt && base\n ? absolute(latestVersionAlt.path)\n : base\n ? \\`\\${base}\\${basedRoute === \"/\" ? \"/\" : encodeURI(basedRoute)}\\`\n : null);\nconst effectiveNoindex = Boolean(seo.noindex) || (archived?.noindex ?? false);\n\nconst localeAlternates =\n i18n && base\n ? (alternates ?? []).map((alt) => ({ hreflang: alt.locale, href: absolute(alt.path) }))\n : [];\nconst defaultAlt = i18n ? (alternates ?? []).find((alt) => alt.locale === i18n.defaultLocale) : null;\nconst xDefault = defaultAlt && base ? absolute(defaultAlt.path) : null;\n\n// \\`route\\` arrives with \\`basePath\\` already applied, so the locale segment it\n// carries sits *after* the base (\\`/docs/ja/guide\\`), while \\`localePrefix\\` is\n// base-less (\\`/ja\\`). Stripping and re-adding a locale therefore happen in\n// base-less space, with the base re-applied at the end — the same\n// \\`withBasePath(basePath, localizeRoute(...))\\` composition the manifest uses to\n// build every real route. Done in based space, \\`stripLocale\\` matches nothing\n// and \\`localizeRoute\\` prepends a second prefix (\\`/ja/docs/ja/guide\\`). Only a\n// switcher entry for a locale with no real translation reaches this fallback:\n// a partially translated hand-written page for its missing locales, or a\n// generated reference (which has no \\`alternates\\` at all) for every locale\n// but its own.\nconst mountLocalized = (logical: string, codeArg: string) =>\n withBasePath(data.config.basePath, localizeRoute(logical, codeArg));\nconst logicalRoute = i18n\n ? stripLocale(stripBasePath(data.config.basePath, route), locale)\n : route;\n// A page from a one-language source (GitHub Releases) gets no switcher: every\n// other locale would only repeat the same text.\nconst localeSwitch = i18n && !monolingual\n ? i18n.locales.map((l) => {\n const alt = (alternates ?? []).find((x) => x.locale === l.code);\n return {\n code: l.code,\n current: l.code === locale,\n dir: l.dir,\n href: alt ? alt.path : mountLocalized(logicalRoute, l.code),\n label: l.label,\n untranslated: !alt,\n };\n })\n : [];\n\n// Version switcher + old-version notice. The switcher auto-populates from the\n// versions config as a \\`kind: \"version\"\\` selector; a user-declared version\n// selector in \\`navigation.selectors\\` suppresses it (theirs renders instead).\n// Fallback version roots compose like real routes — \\`{basePath}/{locale?}/{id}\\`\n// (manifest \\`versionAlternates\\` paths arrive with the base already applied).\nconst versionRootFor = (id: string) => {\n const logical = id ? \\`/\\${id}\\` : \"/\";\n return i18n\n ? mountLocalized(logical, locale)\n : withBasePath(data.config.basePath, logical);\n};\nconst samePageSwitch = versionsConfig\n ? versionsConfig.switcher.redirect === \"same-page\"\n : true;\nconst userHasVersionSelector = navigation.selectors.some(\n (selector) => selector.kind === \"version\"\n);\nconst versionSelector =\n versionsConfig && !userHasVersionSelector\n ? {\n items: [\n {\n id: \"\",\n label: versionsConfig.current.label,\n tag: versionsConfig.current.badge,\n },\n ...versionsConfig.archived.map((v) => ({\n id: v.id,\n label: v.label ?? v.id,\n tag: undefined,\n })),\n ].map((entry) => {\n const alt = (versionAlternates ?? []).find(\n (a) => a.version === entry.id\n );\n return {\n label: entry.label,\n path: samePageSwitch && alt ? alt.path : versionRootFor(entry.id),\n ...(entry.tag ? { tag: entry.tag } : {}),\n };\n }),\n kind: \"version\" as const,\n label: ui.versions.switcher,\n }\n : null;\n\nconst versionNotice =\n archived && archived.banner !== false\n ? {\n latestHref: latestVersionAlt\n ? latestVersionAlt.path\n : versionRootFor(\"\"),\n latestLabel: ui.versions.latest,\n message:\n typeof archived.banner === \"string\"\n ? archived.banner\n : ui.versions.notice.replace(\n \"{version}\",\n archived.label ?? archived.id\n ),\n }\n : null;\n\n// The whole page shell is overridable via \\`layout.Layout\\`; it receives the same\n// props as the built-in RootLayout, plus the \\`layout\\` map for its inner slots.\nconst LayoutComponent = resolveSlot(layoutOverrides.Layout, RootLayout);\n---\n\n<LayoutComponent\n site={{ title: data.config.title, description: data.config.description }}\n layout={layoutOverrides}${clientData}\n logo={data.config.logo}\n mcp={data.config.mcp}\n favicon={data.config.favicon}\n appleIcon={data.config.appleIcon}\n banner={data.config.banner}\n analytics={data.config.analytics}\n imageZoom={data.config.imageZoom}\n codeWrap={data.config.codeWrap}\n navigation={navigation}\n locale={htmlLang}\n dir={dir}\n contentDir={contentDir}\n contentLocale={contentLocale}\n ui={ui}\n localeAlternates={localeAlternates}\n xDefault={xDefault}\n localeSwitch={localeSwitch}\n versionSelector={versionSelector}\n versionNotice={versionNotice}\n searchVersion={versionsConfig ? version : null}\n page={{ title: seo.title ?? title, description: seo.description ?? frontmatter.description, route }}\n headings={headings}\n toc={data.config.toc}\n dateFormat={data.config.dateFormat}\n themeMode={data.config.theme.mode}\n fontCssVars={data.fontCssVars}\n searchEnabled={data.config.search.enabled}\n indexable={indexable}\n ogImage={ogImage}\n ogGenerated={ogGenerated}\n x={x}\n canonical={canonical}\n editUrl={editUrl}\n feedback={data.config.feedback}\n exportPdf={${options.exportPdf}}\n exportEpub={${options.exportEpub}}${\n options.navFragments\n ? `\n navFragmentBase={withBase(\\`/blume-nav/\\${version || \"current\"}/\\${i18n && localePrefix(locale) ? locale : \"default\"}\\`)}`\n : \"\"\n }\n openInChat={data.config.openInChat}\n feeds={data.feeds}\n discovery={data.config.discovery}\n siteUrl={data.config.site}\n pageType={frontmatter.type}\n published={frontmatter.date ?? frontmatter.changelog?.date ?? null}\n lastModified={lastModified}\n noindex={effectiveNoindex}\n structuredDataEnabled={data.config.structuredData}\n>\n <h1>{title}</h1>\n {frontmatter.description && <p class=\"text-lg text-muted-foreground\">{frontmatter.description}</p>}\n <LocaleLinks locale={locale}>\n <Content components={components} />\n </LocaleLinks>\n</LayoutComponent>\n`;\n};\n\n/**\n * Generate `.blume/src/pages/changelog.astro` — the changelog index. Collects\n * every `type: changelog` entry, sorts newest-first, and lists them grouped by\n * year: one row per release with its title (linked to the entry's own page),\n * its `category` tag, and its date. Bodies are deliberately not rendered — a\n * long-lived project's index otherwise grows past what an agent can read in\n * one context window (and what a reader will scroll), while every entry\n * already has a page of its own. Only written by\n * {@link generateAstroProject} when changelog entries exist.\n */\nexport const changelogIndexTemplate = (options: {\n exportEpub: boolean;\n exportPdf: boolean;\n /** Serialize the island-hooks snapshot; only needed when React is enabled. */\n needsReact: boolean;\n /** Whether a `staged` collection exists (non-filesystem changelog sources). */\n staged: boolean;\n}): string => {\n const clientData = options.needsReact\n ? '\\n clientData={{ config: data.config, navigation: data.navigation, page: { route: \"/changelog\", title: pageTitle } }}'\n : \"\";\n // Staged sources (e.g. GitHub Releases) render through a parallel collection,\n // so fold them in alongside filesystem entries when one exists.\n const stagedSpread = options.staged\n ? '\\n ...(await getCollection(\"staged\")),'\n : \"\";\n\n return `---\n// Generated by Blume. Do not edit.\nimport { getCollection } from \"astro:content\";\nimport RootLayout from \"blume/components/layout/RootLayout.astro\";\nimport { withBase } from \"blume/components/islands/base-path.ts\";\nimport { resolveSlot } from \"blume/components/layout/overrides.ts\";\nimport { resolveDateFormatOptions } from \"blume/core/date-format.ts\";\nimport { layoutOverrides } from \"../generated/components.ts\";\nimport data from \"blume:data\";\n\nexport const prerender = true;\n\nconst entryDate = (entry: {\n data: { date?: string | null; changelog?: { date?: string | null } | null };\n}) => entry.data.date ?? entry.data.changelog?.date ?? null;\n\nconst parseDate = (value: string | null | undefined) => {\n if (!value) {\n return null;\n }\n const date = new Date(value);\n return Number.isNaN(date.getTime()) ? null : date;\n};\n\n// The changelog is an unlocalized route, so its chrome renders in the default\n// locale's dictionary and direction (\\`data.ui\\` is the default locale's resolved\n// dictionary), mirroring the catch-all's locale wiring.\nconst i18n = data.config.i18n;\nconst localeMeta = i18n\n ? i18n.locales.find((l) => l.code === i18n.defaultLocale)\n : null;\nconst dir = localeMeta?.dir ?? \"ltr\";\nconst htmlLang = i18n ? i18n.defaultLocale : \"en\";\n\n// Formatted in the same locale as the chrome, and with the configured\n// \\`dateFormat\\` (UTC by default), to match the per-page \"last updated\" stamp.\n// The year heads each group, so a row shows the rest of the date: a preset\n// style keeps its month wording, a component format simply drops the year.\nconst dateFormatOptions = resolveDateFormatOptions(data.config.dateFormat);\nconst { dateStyle, year: _year, ...dateComponents } = dateFormatOptions;\nconst rowDateFormat: Intl.DateTimeFormatOptions = dateStyle\n ? {\n ...dateComponents,\n day: \"numeric\",\n month: dateStyle === \"medium\" ? \"short\" : \"long\",\n }\n : dateComponents;\nconst formatRowDate = (date: Date) =>\n new Intl.DateTimeFormat(htmlLang, rowDateFormat).format(date);\nconst formatYear = (date: Date) =>\n new Intl.DateTimeFormat(htmlLang, {\n timeZone: dateFormatOptions.timeZone,\n year: \"numeric\",\n }).format(date);\n\nconst slugify = (text: string) =>\n text.toLowerCase().replace(/[^a-z0-9]+/g, \"-\").replace(/^-+|-+$/g, \"\") ||\n \"update\";\n\n// Map each entry to its own generated page so the row can link to it. The\n// collection entry id matches the route manifest's \\`entryId\\` — and under\n// i18n every locale serves a page for it (its translations plus fallback\n// copies of an untranslated one), all sharing that id, so a map over every\n// route would keep whichever locale came last: a Portuguese permalink on an\n// unlocalized index. Only the default locale's routes are kept, and an entry\n// is listed only when it has one, so a translated changelog file (a distinct\n// entry that lives under its own locale) does not add a second row for the\n// same release.\nconst defaultLocale = i18n ? i18n.defaultLocale : null;\nconst routeByEntry = new Map<string, string>();\nfor (const route of data.routes) {\n if (defaultLocale === null || route.locale === defaultLocale) {\n routeByEntry.set(route.entryId, route.path);\n }\n}\n\nconst changelogEntries = [\n ...(await getCollection(\"docs\")),${stagedSpread}\n]\n .filter(\n (entry) =>\n entry.data.type === \"changelog\" &&\n !entry.data.draft &&\n !entry.data.sidebar?.hidden &&\n (defaultLocale === null || routeByEntry.has(entry.id))\n )\n .toSorted(\n (a, b) =>\n (parseDate(entryDate(b))?.getTime() ?? 0) -\n (parseDate(entryDate(a))?.getTime() ?? 0)\n );\n\nconst items = changelogEntries.map((entry) => {\n const label =\n entry.data.title ??\n (entry.data.changelog?.version\n ? \"v\" + entry.data.changelog.version\n : \"Update\");\n const date = parseDate(entryDate(entry));\n const route = routeByEntry.get(entry.id);\n return {\n date: date ? formatRowDate(date) : null,\n dateTime: date ? date.toISOString().slice(0, 10) : null,\n href: route ? withBase(route) : null,\n id: slugify(label),\n label,\n tag: entry.data.changelog?.category ?? null,\n year: date ? formatYear(date) : null,\n };\n});\n\n// Repeated labels slug to the same id (e.g. two entries with neither a title\n// nor a version both falling back to \"update\"); suffix the later ones -2, -3,\n// ... so every row keeps its own hash anchor. The first keeps the plain slug.\nconst seenIds = new Set();\nfor (const item of items) {\n let uniqueId = item.id;\n for (let n = 2; seenIds.has(uniqueId); n += 1) {\n uniqueId = item.id + \"-\" + n;\n }\n seenIds.add(uniqueId);\n item.id = uniqueId;\n}\n\n// Consecutive releases from the same year share a group, so the list reads as\n// a year rail beside the rows; undated entries (sorted last) form a final\n// group with no year label.\nconst groups: { items: typeof items; year: string | null }[] = [];\nfor (const item of items) {\n const last = groups[groups.length - 1];\n if (last && last.year === item.year) {\n last.items.push(item);\n } else {\n groups.push({ items: [item], year: item.year });\n }\n}\n\nconst base = data.config.site ? data.config.site.replace(/\\\\/$/, \"\") : null;\n// The canonical URL carries the deployment base (the page is served under it),\n// matching how the catch-all canonicalizes via \\`withBase(route)\\`.\nconst basedRoute = withBase(\"/changelog\");\nconst canonical = base ? base + basedRoute : null;\n\n// The generated OG card for this route (the /og endpoint emits it alongside\n// the content-route cards), absolutized like the catch-all's so crawlers get\n// a full URL when the site is known.\nconst ogPath = data.config.og.enabled ? withBase(\"/og/changelog.png\") : null;\nconst ogImage = ogPath && base ? base + ogPath : ogPath;\n\n// The page chrome (h1, title, description) comes from the translatable\n// \\`changelog\\` group; optional chaining tolerates a not-yet-regenerated data\n// snapshot from before these keys existed.\nconst changelogTitle = data.ui.changelog?.title ?? \"Changelog\";\nconst changelogDescription =\n data.ui.changelog?.description ??\n \"Product updates, new features, and fixes from every release.\";\n// The layout suffixes \"- {site title}\" itself, so the page title is just the\n// changelog's own name — prefixing the site title too would double it\n// (\"Acme Changelog - Acme\").\nconst pageTitle = changelogTitle;\n\nconst LayoutComponent = resolveSlot(layoutOverrides.Layout, RootLayout);\n---\n\n<LayoutComponent\n site={{ title: data.config.title, description: data.config.description }}\n layout={layoutOverrides}${clientData}\n logo={data.config.logo}\n mcp={data.config.mcp}\n favicon={data.config.favicon}\n appleIcon={data.config.appleIcon}\n banner={data.config.banner}\n analytics={data.config.analytics}\n imageZoom={data.config.imageZoom}\n codeWrap={data.config.codeWrap}\n navigation={data.navigation}\n locale={htmlLang}\n dir={dir}\n ui={data.ui}\n page={{\n title: pageTitle,\n description: changelogDescription,\n route: \"/changelog\",\n }}\n headings={[]}\n toc={data.config.toc}\n contentLayout=\"bare\"\n themeMode={data.config.theme.mode}\n fontCssVars={data.fontCssVars}\n searchEnabled={data.config.search.enabled}\n indexable={true}\n ogImage={ogImage}\n ogGenerated={Boolean(ogImage)}\n x={data.config.x}\n canonical={canonical}\n exportPdf={${options.exportPdf}}\n exportEpub={${options.exportEpub}}\n openInChat={data.config.openInChat}\n feeds={data.feeds}\n discovery={data.config.discovery}\n siteUrl={data.config.site}\n noindex={false}\n structuredDataEnabled={data.config.structuredData}\n>\n <h1>{changelogTitle}</h1>\n <p class=\"text-lg text-muted-foreground\">{changelogDescription}</p>\n {\n items.length === 0 ? (\n <p>No changelog entries yet.</p>\n ) : (\n <div class=\"not-prose mt-10 divide-y divide-border border-border border-y\">\n {groups.map((group) => (\n <section\n aria-label={group.year ?? undefined}\n class=\"grid md:grid-cols-[6rem_minmax(0,1fr)] md:gap-x-8\"\n >\n <h2 class=\"mt-0! pt-3! font-medium! text-muted-foreground text-sm! leading-5! tabular-nums max-md:pb-1\">\n {group.year}\n </h2>\n <ul class=\"m-0! list-none divide-y divide-border p-0!\">\n {group.items.map((item) => (\n <li class=\"m-0! p-0!\" id={item.id}>\n <a\n class=\"group/release flex items-baseline gap-4 py-3 no-underline! hover:no-underline!\"\n href={item.href ?? \"#\" + item.id}\n >\n <span class=\"min-w-0 flex-1 font-medium text-foreground text-sm transition-colors group-hover/release:text-accent\">\n {item.label}\n </span>\n {item.tag && (\n <span class=\"shrink-0 rounded-full bg-muted px-2 py-0.5 font-medium text-[0.65rem] text-muted-foreground\">\n {item.tag}\n </span>\n )}\n {item.date && (\n <time\n class=\"shrink-0 font-mono text-muted-foreground text-xs tabular-nums\"\n datetime={item.dateTime}\n >\n {item.date}\n </time>\n )}\n </a>\n </li>\n ))}\n </ul>\n </section>\n ))}\n </div>\n )\n }\n</LayoutComponent>\n`;\n};\n\n/**\n * Generate `.blume/src/pages/404.astro`: the default not-found page. Rendered\n * through `PageLayout` (header + search, no sidebar) so it stays consistent with\n * the rest of the site, with copy pulled from the translatable `notFound` UI\n * strings. Written at Astro's reserved `src/pages/404.astro` path so static\n * builds emit `dist/404.html` and the dev server serves it for unmatched routes.\n * Skipped by the generator when a user `pages/404.astro` already occupies the\n * `/404` route, so projects can fully override it.\n */\nexport const notFoundPageTemplate = (): string => `---\n// Generated by Blume. Do not edit. Override by adding \\`pages/404.astro\\`.\nimport Icon from \"blume/components/Icon.astro\";\nimport PageLayout from \"blume/components/layout/PageLayout.astro\";\nimport { withBase } from \"blume/components/islands/base-path.ts\";\nimport data from \"blume:data\";\n\nexport const prerender = true;\n\nconst nf = data.ui.notFound;\n\n// The 404 page is an unlocalized route, so its chrome renders in the default\n// locale's dictionary and direction (\\`data.ui\\` is the default locale's resolved\n// dictionary), mirroring the catch-all's locale wiring.\nconst i18n = data.config.i18n;\nconst localeMeta = i18n\n ? i18n.locales.find((l) => l.code === i18n.defaultLocale)\n : null;\nconst dir = localeMeta?.dir ?? \"ltr\";\nconst htmlLang = i18n ? i18n.defaultLocale : \"en\";\n\n// Recovery links, so a reader — or an agent that followed a stale URL — can\n// get back on track without guessing: every top-level section, then the\n// machine-readable indexes the build emits (the sitemap only exists with a\n// \\`deployment.site\\`; llms.txt only when \\`agents.llmsTxt\\` is on). Tabs link to\n// their resolved target when the section has no index page of its own.\nconst suggestions = [\n ...data.navigation.tabs.map((tab) => ({\n href: withBase(tab.href ?? tab.path),\n label: tab.label,\n })),\n ...(data.config.discovery.sitemap\n ? [{ href: withBase(\"/sitemap.xml\"), label: nf.sitemap }]\n : []),\n ...(data.config.discovery.llmsTxt\n ? [{ href: withBase(\"/llms.txt\"), label: nf.llms }]\n : []),\n];\n\n// Every host serves this one page for any missing URL, so a reader who\n// mistyped \\`/ar/…\\` would get the default locale's message. The other locales'\n// copy rides along, and the script below swaps it in when the URL's first\n// segment names one of them — the message, its language and direction, the\n// tab title, and the home link (to that locale's root).\nconst localized = Object.fromEntries(\n (i18n?.locales ?? [])\n .filter((l) => l.code !== i18n?.defaultLocale && data.uiByLocale[l.code])\n .map((l) => {\n const strings = data.uiByLocale[l.code].notFound;\n return [\n l.code,\n {\n description: strings.description,\n dir: l.dir ?? \"ltr\",\n home: strings.home,\n homeHref: withBase(\"/\" + l.code),\n suggestions: strings.suggestions,\n title: strings.title,\n },\n ];\n })\n);\nconst localizedJson = JSON.stringify(localized).replaceAll(\"<\", \"\\\\\\\\u003c\");\n---\n\n<PageLayout\n site={{ title: data.config.title, description: data.config.description }}\n logo={data.config.logo}\n favicon={data.config.favicon}\n appleIcon={data.config.appleIcon}\n banner={data.config.banner}\n analytics={data.config.analytics}\n navigation={data.navigation}\n page={{ title: nf.title, route: \"/404\" }}\n themeMode={data.config.theme.mode}\n fontCssVars={data.fontCssVars}\n searchEnabled={data.config.search.enabled}\n locale={htmlLang}\n dir={dir}\n ui={data.ui}\n noindex={true}\n>\n <div\n class=\"mx-auto grid w-full max-w-5xl gap-12 px-6 py-20 sm:py-28 md:grid-cols-[3fr_2fr] md:gap-16 lg:gap-24 lg:py-36\"\n data-base={withBase(\"/\")}\n data-blume-not-found\n >\n <div class=\"flex flex-col items-start\">\n <p\n class=\"font-mono text-xs font-medium tracking-widest text-muted-foreground\"\n >\n 404\n </p>\n <h1\n class=\"mt-4 text-balance text-4xl font-semibold tracking-tight text-foreground sm:text-5xl\"\n data-nf=\"title\"\n >\n {nf.title}\n </h1>\n <p class=\"mt-4 max-w-md text-pretty text-lg text-muted-foreground\" data-nf=\"description\">\n {nf.description}\n </p>\n <a\n class=\"mt-8 inline-flex items-center gap-1.5 rounded-full bg-accent py-2 pe-4 ps-3.5 text-sm font-medium text-accent-foreground transition-opacity hover:opacity-90\"\n data-nf-home\n href={withBase(\"/\")}\n >\n <Icon class=\"rtl:-scale-x-100\" name=\"arrow-left\" size={14} />\n <span data-nf=\"home\">{nf.home}</span>\n </a>\n </div>\n {\n suggestions.length > 0 && (\n <nav aria-label={nf.suggestions} class=\"md:border-s md:border-border md:ps-12 lg:ps-16\" data-nf-suggestions>\n <h2 class=\"text-xs font-medium uppercase tracking-widest text-muted-foreground\" data-nf=\"suggestions\">\n {nf.suggestions}\n </h2>\n <ul class=\"mt-4 divide-y divide-border border-y border-border\">\n {suggestions.map((link) => (\n <li>\n <a\n class=\"group flex items-center justify-between gap-4 py-3 text-sm font-medium text-foreground transition-colors hover:text-accent\"\n href={link.href}\n >\n <span>{link.label}</span>\n <Icon\n class=\"shrink-0 text-muted-foreground transition-transform group-hover:translate-x-0.5 rtl:-scale-x-100 rtl:group-hover:-translate-x-0.5\"\n name=\"arrow-right\"\n size={14}\n />\n </a>\n </li>\n ))}\n </ul>\n </nav>\n )\n }\n </div>\n <script id=\"blume-not-found-locales\" is:inline type=\"application/json\" set:html={localizedJson} />\n <script is:inline>\n (() => {\n const apply = () => {\n const root = document.querySelector(\"[data-blume-not-found]\");\n const source = document.getElementById(\"blume-not-found-locales\");\n if (!root || !source) {\n return;\n }\n const base = root.getAttribute(\"data-base\") || \"/\";\n const path = location.pathname.startsWith(base)\n ? location.pathname.slice(base.length)\n : location.pathname.replace(/^\\\\/+/, \"\");\n const code = path.split(\"/\")[0];\n const entry = JSON.parse(source.textContent || \"{}\")[code];\n if (!entry) {\n return;\n }\n root.setAttribute(\"lang\", code);\n root.setAttribute(\"dir\", entry.dir);\n for (const el of root.querySelectorAll(\"[data-nf]\")) {\n el.textContent = entry[el.getAttribute(\"data-nf\")];\n }\n root.querySelector(\"[data-nf-home]\")?.setAttribute(\"href\", entry.homeHref);\n root.querySelector(\"[data-nf-suggestions]\")?.setAttribute(\"aria-label\", entry.suggestions);\n document.title = entry.title;\n };\n apply();\n document.addEventListener(\"astro:page-load\", apply);\n })();\n </script>\n</PageLayout>\n`;\n\n/**\n * Generate `.blume/src/pages/404.md.ts`: the Markdown twin of the default 404\n * page, prerendered to `dist/404.md`. An agent that asked for a missing page\n * with `Accept: text/markdown` — or fetched a `.md` URL no page backs — gets\n * this body with the 404 status instead of the HTML shell; Vercel server\n * builds wire that into the routing config (`deploy/vercel-negotiation.ts`)\n * and Cloudflare server builds into the wrapper Worker\n * (`deploy/cloudflare-negotiation.ts`).\n * Same recovery links as the HTML page, absolute when the site URL is known:\n * the body is read out of context, so a relative link would leave the reader\n * guessing the host. Written alongside `404.astro` and skipped under the same\n * rule, so a project that owns `/404` owns both variants.\n */\nexport const notFoundMarkdownTemplate =\n (): string => `// Generated by Blume. Do not edit. Override by adding \\`pages/404.astro\\`.\nimport { withBase } from \"blume/components/islands/base-path.ts\";\nimport { absoluteUrl } from \"blume/core/site-url.ts\";\nimport data from \"blume:data\";\n\nexport const prerender = true;\n\nconst nf = data.ui.notFound;\n\n// Absolute for internal routes when the site is known; an external tab href\n// passes through untouched.\nconst href = (path: string): string => {\n const based = withBase(path);\n return data.config.site && based.startsWith(\"/\") && !based.startsWith(\"//\")\n ? absoluteUrl(data.config.site, based)\n : based;\n};\n\n// The recovery set of 404.astro: home, every top-level section (a tab links to\n// its resolved target), then the machine-readable indexes that exist.\nconst links = [\n { href: href(\"/\"), label: nf.home },\n ...data.navigation.tabs.map((tab) => ({\n href: href(tab.href ?? tab.path),\n label: tab.label,\n })),\n ...(data.config.discovery.sitemap\n ? [{ href: href(\"/sitemap.xml\"), label: nf.sitemap }]\n : []),\n ...(data.config.discovery.llmsTxt\n ? [{ href: href(\"/llms.txt\"), label: nf.llms }]\n : []),\n ...(data.config.discovery.api\n ? [{ href: href(\"/openapi.json\"), label: nf.api }]\n : []),\n];\n\nconst body = [\n \"# \" + nf.title,\n \"\",\n nf.description,\n \"\",\n \"## \" + nf.suggestions,\n \"\",\n ...links.map((link) => \"- [\" + link.label + \"](\" + link.href + \")\"),\n \"\",\n].join(\"\\\\n\");\n\nexport function GET() {\n return new Response(body, {\n headers: {\n \"Content-Type\": \"text/markdown; charset=utf-8\",\n // ~4 characters per token; keep in sync with markdownTokenCount.\n \"x-markdown-tokens\": String(Math.ceil(body.length / 4)),\n },\n });\n}\n`;\n\n/**\n * Generate `.blume/src/pages/404.json.ts`: the JSON twin of the default 404\n * page, prerendered to `dist/404.json` as RFC 9457 problem details. An agent\n * that asked for a missing page with `Accept: application/json` gets this body\n * with the 404 status instead of the HTML shell (Vercel and Cloudflare server\n * builds wire that into the deploy, like the Markdown twin). Same recovery links\n * as the other variants, carried as `links` and spelled out in `resolution`.\n * Written alongside `404.astro` and skipped under the same rule.\n */\nexport const notFoundJsonTemplate =\n (): string => `// Generated by Blume. Do not edit. Override by adding \\`pages/404.astro\\`.\nimport { problem } from \"blume/ai/api/problem.ts\";\nimport { withBase } from \"blume/components/islands/base-path.ts\";\nimport { absoluteUrl } from \"blume/core/site-url.ts\";\nimport data from \"blume:data\";\n\nexport const prerender = true;\n\nconst nf = data.ui.notFound;\n\n// Absolute for internal routes when the site is known; an external tab href\n// passes through untouched.\nconst href = (path: string): string => {\n const based = withBase(path);\n return data.config.site && based.startsWith(\"/\") && !based.startsWith(\"//\")\n ? absoluteUrl(data.config.site, based)\n : based;\n};\n\n// The recovery set of 404.astro: home, every top-level section (a tab links to\n// its resolved target), then the machine-readable indexes that exist.\nconst links = [\n { href: href(\"/\"), label: nf.home },\n ...data.navigation.tabs.map((tab) => ({\n href: href(tab.href ?? tab.path),\n label: tab.label,\n })),\n ...(data.config.discovery.sitemap\n ? [{ href: href(\"/sitemap.xml\"), label: nf.sitemap }]\n : []),\n ...(data.config.discovery.llmsTxt\n ? [{ href: href(\"/llms.txt\"), label: nf.llms }]\n : []),\n ...(data.config.discovery.api\n ? [{ href: href(\"/openapi.json\"), label: nf.api }]\n : []),\n];\n\nconst body = problem({\n code: \"PAGE_NOT_FOUND\",\n detail: nf.description,\n links,\n resolution: nf.suggestions + \": \" + links.map((link) => link.href).join(\", \"),\n status: 404,\n title: nf.title,\n});\n\nexport function GET() {\n return new Response(JSON.stringify(body, null, 2) + \"\\\\n\", {\n headers: { \"Content-Type\": \"application/problem+json; charset=utf-8\" },\n });\n}\n`;\n\n/**\n * Generate the prerendered JSON docs API endpoints under\n * `.blume/src/pages/api/docs/`: the page index (`pages.json`), one JSON\n * document per page (`pages/[...route].json`), and the navigation tree\n * (`navigation.json`). Each is a thin wrapper over `blume/ai/api/handlers.ts`\n * reading the same snapshot the MCP server serves (`blume:mcp-data`), so the\n * REST and MCP answers can never diverge.\n */\nexport const apiPagesIndexTemplate = (): string =>\n `// Generated by Blume. Do not edit.\nimport { pagesIndexResponse } from \"blume/ai/api/handlers.ts\";\nimport data from \"blume:mcp-data\";\n\nexport const prerender = true;\n\nexport function GET() {\n return pagesIndexResponse(data);\n}\n`;\n\nexport const apiPageTemplate = (): string =>\n `// Generated by Blume. Do not edit.\nimport { pageParams, pageResponse } from \"blume/ai/api/handlers.ts\";\nimport data from \"blume:mcp-data\";\n\nexport const prerender = true;\n\nexport function getStaticPaths() {\n return pageParams(data);\n}\n\nexport function GET({ props }: { props: { route: string } }) {\n return pageResponse(data, props.route);\n}\n`;\n\nexport const apiNavigationTemplate = (): string =>\n `// Generated by Blume. Do not edit.\nimport { navigationResponse } from \"blume/ai/api/handlers.ts\";\nimport data from \"blume:mcp-data\";\n\nexport const prerender = true;\n\nexport function GET() {\n return navigationResponse(data);\n}\n`;\n\n/**\n * Generate the live search endpoint (`.blume/src/pages/api/docs/search.ts`),\n * server output only: the REST twin of the MCP `search_docs` tool, over the\n * same snapshot and index.\n */\nexport const apiSearchTemplate = (): string =>\n `// Generated by Blume. Do not edit.\nimport type { APIRoute } from \"astro\";\nimport { createSearchHandler } from \"blume/ai/api/handlers.ts\";\nimport data from \"blume:mcp-data\";\n\nexport const prerender = false;\n\nconst handler = createSearchHandler(data);\n\nexport const GET: APIRoute = ({ request }) => handler(request);\n`;\n\n/**\n * Generate the API namespace's catch-all (`.blume/src/pages/api/[...path].ts`),\n * server output only: any `/api/…` request no endpoint answers gets an RFC\n * 9457 problem document with the 404 status instead of the HTML not-found\n * page. Static segments always beat the rest parameter, so `/api/ask`, the\n * search proxy, and every prerendered docs endpoint keep winning. The site\n * context is baked in so the resolution links are absolute when the site is\n * known.\n */\nexport const apiNotFoundTemplate = (context: {\n base: string;\n site: string | null;\n}): string =>\n `// Generated by Blume. Do not edit.\nimport type { APIRoute } from \"astro\";\nimport { apiNotFoundResponse } from \"blume/ai/api/handlers.ts\";\n\nexport const prerender = false;\n\nconst context = ${JSON.stringify(context)};\n\nexport const ALL: APIRoute = ({ request }) => apiNotFoundResponse(request, context);\n`;\n\n/** The literal Astro hydration directive for an example's framework/client. */\nconst exampleDirective = (spec: ExampleSpec): string => {\n if (spec.framework === \"astro\" || !spec.client) {\n return \"\";\n }\n return spec.client === \"only\"\n ? `client:only=\"${spec.framework}\" `\n : `client:${spec.client} `;\n};\n\n/** Filesystem-safe slug for an example's generated wrapper file. */\n/**\n * A filesystem-safe, injective token for an example path. Distinct paths must\n * never share a wrapper file (`button.demo` vs `button-demo` used to collide),\n * so every non-alphanumeric character is hex-escaped rather than collapsed.\n */\nexport const exampleSlug = (path: string): string =>\n path.replaceAll(\n /[^a-zA-Z0-9]/gu,\n (char) => `_${(char.codePointAt(0) ?? 0).toString(16)}_`\n );\n\n/**\n * Generate `.blume/src/generated/examples/<slug>.astro` — a wrapper that renders\n * one example live, with its hydration directive applied (none for `.astro`).\n * Mirrors the component-slot wrappers (`component-slots.ts`); `<Component>`\n * resolves these by path.\n */\nexport const exampleWrapperTemplate = (\n spec: ExampleSpec,\n /** The wrapper's directory, when its import must be relative (eject). */\n fromDir?: string\n): string =>\n `---\n// Generated by Blume. Do not edit.\nimport Example from ${JSON.stringify(importSpecifier(spec.file, fromDir))};\n${wrapperPropsType(\"Example\")}\n---\n<Example ${exampleDirective(spec)}{...Astro.props}><slot /></Example>\n`;\n\n/**\n * The route prefix `<Component />` preview frames are served under:\n * `{basePath}/blume-examples/<example path>`. `deployment.base` is layered on\n * top by Astro (components apply it with `withBase`).\n */\nexport const examplesRouteBase = (basePath: string): string =>\n `${basePath}/blume-examples`;\n\n/**\n * Generate `.blume/src/generated/examples.ts` — a map of example path to its live\n * wrapper component plus raw source and language for the code tab, and the route\n * base preview iframes point at. Reached by the shipped `Component.astro` and the\n * generated preview page via the `blume:examples` alias. Always written (an\n * empty object when there are no examples) so the alias resolves.\n */\nexport const exampleMapTemplate = (\n specs: ExampleSpec[],\n basePath: string\n): string => {\n const base = `export const examplesBase = ${JSON.stringify(\n examplesRouteBase(basePath)\n )};`;\n if (specs.length === 0) {\n return `// Generated by Blume. Do not edit.\n${base}\nexport const examples = {};\n`;\n }\n const imports = specs\n .map(\n (spec, index) =>\n `import E${index} from \"./examples/${exampleSlug(spec.path)}.astro\";`\n )\n .join(\"\\n\");\n const entries = specs\n .map(\n (spec, index) =>\n ` ${JSON.stringify(spec.path)}: { Component: E${index}, code: ${JSON.stringify(\n spec.source\n )}, lang: ${JSON.stringify(spec.lang)} },`\n )\n .join(\"\\n\");\n return `// Generated by Blume. Do not edit.\n${imports}\n${base}\nexport const examples = {\n${entries}\n};\n`;\n};\n\n/**\n * Generate the `<Component />` preview page — one prerendered route per\n * example under `{basePath}/blume-examples/`, rendered as a bare document\n * (no layout) that an iframe in the docs page embeds. The iframe boundary is\n * what isolates examples from the docs CSS: the only stylesheet here is the\n * example entry (`blume:examples-theme` — Tailwind, the Blume tokens, and the\n * user's configured examples css), so users can preview components styled by\n * their own design system (e.g. shadcn) with no prose styles bleeding in.\n *\n * The inline script mirrors the docs theme before first paint — same-document\n * reads of the parent's `data-theme` (same origin) with a MutationObserver for\n * live toggles — and sets both `data-theme` and a `dark` class so either\n * dark-mode convention works in user CSS. When the page is opened directly\n * (no parent), it falls back to the stored preference, then the OS setting.\n *\n * A second script reports the example's rendered height to the parent\n * (`blume:example-height` via postMessage) so the docs page can size the\n * preview pane to the content instead of guessing from the source line count.\n * A ResizeObserver keeps the report live, so examples that grow or shrink\n * after load (chat threads, accordions) stay in sync, and the frame re-reports\n * on request (`blume:example-height-request`) so a report posted before the\n * docs page's listener registered isn't lost.\n */\nexport const examplesPageTemplate = (): string =>\n `---\n// Generated by Blume. Do not edit.\nimport { examples } from \"blume:examples\";\nimport \"blume:examples-theme\";\n\n// Prerendered even in server output, like docs content.\nexport const prerender = true;\n\nexport const getStaticPaths = () =>\n Object.keys(examples).map((path) => ({ params: { path } }));\n\nconst { path } = Astro.params;\nconst entry = path ? examples[path] : undefined;\nif (!entry) {\n return new Response(null, { status: 404 });\n}\nconst Example = entry.Component;\n---\n\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <meta name=\"robots\" content=\"noindex\" />\n <title>{path}</title>\n <script is:inline>\n (() => {\n const root = document.documentElement;\n const apply = (theme) => {\n root.dataset.theme = theme;\n root.classList.toggle(\"dark\", theme === \"dark\");\n };\n const stored = () =>\n localStorage.getItem(\"blume-theme\") ??\n (matchMedia(\"(prefers-color-scheme: dark)\").matches\n ? \"dark\"\n : \"light\");\n try {\n const host = window.parent.document.documentElement;\n apply(host.dataset.theme ?? stored());\n new MutationObserver(() => {\n apply(host.dataset.theme ?? stored());\n }).observe(host, { attributeFilter: [\"data-theme\"] });\n } catch {\n apply(stored());\n }\n })();\n </script>\n </head>\n <!-- Flex + margin:auto centers the example and, unlike place-items, keeps\n the top edge reachable when the example outgrows the frame. -->\n <body style=\"display:flex;min-height:100svh;padding:1.5rem\">\n <div data-blume-example style=\"margin:auto\"><Example /></div>\n <script is:inline>\n (() => {\n // Report the example's rendered height so the embedding docs page can\n // size the preview pane to the content. The wrapper is observed rather\n // than the body: the body stretches to the frame's own height, so it\n // would only echo the pane back. Direct opens have no distinct parent\n // and skip out; the frame is same-origin with the docs page (see the\n // theme sync above), so the origin is pinned on both ends.\n if (window.parent === window) {\n return;\n }\n const wrapper = document.querySelector(\"[data-blume-example]\");\n if (!wrapper) {\n return;\n }\n // The body's padding frames the example; fold it into the report so\n // the parent can apply the number as-is. Read from the live value —\n // the user's examples.css is injected after Blume's defaults precisely\n // so their tokens win, so a root font-size override must be honored\n // rather than assuming 1.5rem is 48px.\n const bodyStyle = getComputedStyle(document.body);\n const paddingPx =\n parseFloat(bodyStyle.paddingTop) + parseFloat(bodyStyle.paddingBottom);\n const report = () => {\n window.parent.postMessage(\n {\n height:\n Math.ceil(wrapper.getBoundingClientRect().height) + paddingPx,\n type: \"blume:example-height\",\n },\n window.location.origin\n );\n };\n new ResizeObserver(report).observe(wrapper);\n // The parent asks for a fresh report when its listener comes up, in\n // case the first one above was posted before anyone was listening.\n window.addEventListener(\"message\", (event) => {\n if (\n event.source === window.parent &&\n event.origin === window.location.origin &&\n event.data?.type === \"blume:example-height-request\"\n ) {\n report();\n }\n });\n })();\n </script>\n </body>\n</html>\n`;\n\n/** Generate `.blume/package.json`. */\nexport const runtimePackageTemplate = (dependencies: string[] = []): string =>\n `${JSON.stringify(\n {\n dependencies: Object.fromEntries(\n [...dependencies].toSorted().map((name) => [name, \"*\"])\n ),\n name: \"blume-runtime\",\n private: true,\n type: \"module\",\n version: \"0.0.0\",\n },\n null,\n 2\n )}\\n`;\n\n/** Generate `.blume/tsconfig.json`. */\nexport const runtimeTsconfigTemplate = (): string =>\n `${JSON.stringify(\n {\n exclude: [\"dist\"],\n extends: \"astro/tsconfigs/strict\",\n include: [\".astro/types.d.ts\", \"**/*\"],\n },\n null,\n 2\n )}\\n`;\n"
8
- ],
9
- "mappings": "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AA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAyL3C,IAAM,2BACX,MAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAoET,IAAM,uBACX,MAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA8DT,IAAM,wBAAwB,MACnC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAWK,IAAM,kBAAkB,MAC7B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAeK,IAAM,wBAAwB,MACnC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgBK,IAAM,oBAAoB,MAC/B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAqBK,IAAM,sBAAsB,CAAC,YAIlC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,kBAMgB,KAAK,UAAU,OAAO;AAAA;AAAA;AAAA;AAMxC,IAAM,mBAAmB,CAAC,SAA8B;AAAA,EACtD,IAAI,KAAK,cAAc,WAAW,CAAC,KAAK,QAAQ;AAAA,IAC9C,OAAO;AAAA,EACT;AAAA,EACA,OAAO,KAAK,WAAW,SACnB,gBAAgB,KAAK,gBACrB,UAAU,KAAK;AAAA;AASd,IAAM,cAAc,CAAC,SAC1B,KAAK,WACH,kBACA,CAAC,SAAS,KAAK,KAAK,YAAY,CAAC,KAAK,GAAG,SAAS,EAAE,IACtD;AAQK,IAAM,yBAAyB,CACpC,MAEA,YAEA;AAAA;AAAA,sBAEoB,KAAK,UAAU,gBAAgB,KAAK,MAAM,OAAO,CAAC;AAAA,EACtE,iBAAiB,SAAS;AAAA;AAAA,WAEjB,iBAAiB,IAAI;AAAA;AAQzB,IAAM,oBAAoB,CAAC,aAChC,GAAG;AASE,IAAM,qBAAqB,CAChC,OACA,aACW;AAAA,EACX,MAAM,OAAO,+BAA+B,KAAK,UAC/C,kBAAkB,QAAQ,CAC5B;AAAA,EACA,IAAI,MAAM,WAAW,GAAG;AAAA,IACtB,OAAO;AAAA,EACT;AAAA;AAAA;AAAA,EAGA;AAAA,EACA,MAAM,UAAU,MACb,IACC,CAAC,MAAM,UACL,WAAW,0BAA0B,YAAY,KAAK,IAAI,WAC9D,EACC,KAAK;AAAA,CAAI;AAAA,EACZ,MAAM,UAAU,MACb,IACC,CAAC,MAAM,UACL,KAAK,KAAK,UAAU,KAAK,IAAI,oBAAoB,gBAAgB,KAAK,UACpE,KAAK,MACP,YAAY,KAAK,UAAU,KAAK,IAAI,MACxC,EACC,KAAK;AAAA,CAAI;AAAA,EACZ,OAAO;AAAA,EACP;AAAA,EACA;AAAA;AAAA,EAEA;AAAA;AAAA;AAAA;AA4BK,IAAM,uBAAuB,MAClC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAyGK,IAAM,yBAAyB,CAAC,eAAyB,CAAC,MAC/D,GAAG,KAAK,UACN;AAAA,EACE,cAAc,OAAO,YACnB,CAAC,GAAG,YAAY,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CACxD;AAAA,EACA,MAAM;AAAA,EACN,SAAS;AAAA,EACT,MAAM;AAAA,EACN,SAAS;AACX,GACA,MACA,CACF;AAAA;AAGK,IAAM,0BAA0B,MACrC,GAAG,KAAK,UACN;AAAA,EACE,SAAS,CAAC,MAAM;AAAA,EAChB,SAAS;AAAA,EACT,SAAS,CAAC,qBAAqB,MAAM;AACvC,GACA,MACA,CACF;AAAA;",
10
- "debugId": "9E2BF1A2CC0BBFF264756E2164756E21",
11
- "names": []
12
- }
@@ -1,10 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/registry/eject.ts"],
4
- "sourcesContent": [
5
- "import { existsSync } from \"node:fs\";\nimport { cp, mkdir, readFile, rm, writeFile } from \"node:fs/promises\";\n\nimport { join, relative } from \"pathe\";\n\nimport { buildAskData } from \"../ai/ask-data.ts\";\nimport { resolveAskBackend } from \"../ai/ask.ts\";\nimport { buildRawMarkdown } from \"../ai/markdown.ts\";\nimport { buildMcpData } from \"../ai/mcp/data.ts\";\nimport { buildMcpDiscovery, buildMcpServerCard } from \"../ai/mcp/discovery.ts\";\nimport { planComponentSlots } from \"../astro/component-slots.ts\";\nimport {\n EXAMPLE_SCAN_GLOB,\n discoverExamples,\n exampleMarkdownLookup,\n exampleScanRoots,\n} from \"../astro/examples.ts\";\nimport {\n analyzeComponentsFile,\n buildRuntimeData,\n clientFeaturesFor,\n collectStaged,\n detectNeedsReact,\n detectUsesMath,\n languageIconCssFor,\n} from \"../astro/generate.ts\";\nimport { discoverIslands } from \"../astro/islands.ts\";\nimport { customOgRoutes, discoverPages, routeIsTaken } from \"../astro/pages.ts\";\nimport {\n askComponentTemplate,\n askEndpointTemplate,\n astroConfigTemplate,\n catchAllPageTemplate,\n changelogIndexTemplate,\n contentConfigTemplate,\n exampleMapTemplate,\n exampleSlug,\n examplesPageTemplate,\n exampleWrapperTemplate,\n featuresTemplate,\n mcpEndpointTemplate,\n mcpPageFile,\n mixedbreadSearchEndpointTemplate,\n notFoundPageTemplate,\n ogEndpointTemplate,\n rawMarkdownEndpointTemplate,\n rssEndpointTemplate,\n runtimeDependencies,\n runtimeTsconfigTemplate,\n searchClientTemplate,\n searchEndpointTemplate,\n staticJsonEndpointTemplate,\n} from \"../astro/templates.ts\";\nimport { buildIncludeGraph } from \"../core/includes.ts\";\nimport { packageRoot } from \"../core/package-root.ts\";\nimport { scanProject } from \"../core/project-graph.ts\";\nimport type { BlumeProject } from \"../core/project-graph.ts\";\nimport {\n resolveDocsCollection,\n sourcesOfKind,\n} from \"../core/sources/collection.ts\";\nimport type { ProjectContext } from \"../core/types.ts\";\nimport { buildRssFeeds, renderRssFeed } from \"../deploy/rss.ts\";\nimport type { OpenApiData } from \"../openapi/model.ts\";\nimport { hasScalarReferences } from \"../openapi/references.ts\";\nimport { buildReferenceFiles } from \"../openapi/scalar.ts\";\nimport { isOpenApiSource } from \"../openapi/source.ts\";\nimport { buildSearchDocuments } from \"../search/documents.ts\";\nimport {\n examplesEntryTemplate,\n tailwindEntryTemplate,\n} from \"../theme/entry.ts\";\nimport { buildThemeCss } from \"../theme/palette.ts\";\nimport { rebaseSourceDirectives } from \"../theme/sources.ts\";\nimport { twoslashCss } from \"../theme/twoslash.ts\";\n\nconst toPosix = (path: string): string => path.split(\"\\\\\").join(\"/\");\n\n/** The portable `@source` guess: blume in the project's own node_modules. */\nconst LOCAL_BLUME_SOURCE = \"../../node_modules/blume/src/**/*.{astro,ts,tsx}\";\n\n/**\n * The `@source` glob pointing Tailwind at Blume's own source, relative to the\n * ejected `src/generated/app.css`. The project-local `node_modules/blume` is\n * preferred (portable, and under pnpm the symlink survives version bumps), but\n * hoisted installs (npm/yarn workspaces lift blume into the workspace root's\n * node_modules) would make that guess match nothing and silently drop utility\n * classes — so fall back to the package's real installed location, and when\n * even that fails, warn instead of failing silently.\n *\n * Exported for testing.\n */\nexport const blumeSourceGlob = (\n root: string,\n genDir: string,\n resolveBlumeRoot: () => string = packageRoot\n): string => {\n if (existsSync(join(root, \"node_modules\", \"blume\"))) {\n return LOCAL_BLUME_SOURCE;\n }\n try {\n const src = join(resolveBlumeRoot(), \"src\");\n return `${toPosix(relative(genDir, src))}/**/*.{astro,ts,tsx}`;\n } catch {\n console.warn(\n 'blume: could not locate the installed blume package; src/generated/app.css keeps its default `@source \"../../node_modules/blume/...\"` glob. If blume is hoisted elsewhere, point that glob at its install location or Blume\\'s utility classes will be missing.'\n );\n return LOCAL_BLUME_SOURCE;\n }\n};\n\n/** The `blume:openapi` payload for the ejected app (`{}` when none). */\nconst ejectOpenApiData = (project: BlumeProject): OpenApiData => {\n const source = project.sources.find(isOpenApiSource);\n return source ? source.openApiData() : {};\n};\n\n/**\n * The Ask AI endpoint plus, unless the backend runs its own retrieval (Inkeep),\n * its grounding snapshot. Empty when Ask AI is disabled.\n */\nconst askFiles = async (\n project: BlumeProject,\n srcDir: string,\n genDir: string\n): Promise<{ content: string; path: string }[]> => {\n const { ask } = project.config.ai;\n if (!(ask?.enabled && !ask.endpoint)) {\n const endpointPath = join(srcDir, \"pages\", \"api\", \"ask.ts\");\n if (existsSync(endpointPath)) {\n const content = await readFile(endpointPath, \"utf-8\");\n if (content.startsWith(\"// Generated by Blume. Do not edit.\")) {\n await rm(endpointPath, { force: true });\n }\n }\n await rm(join(genDir, \"ask-data.json\"), { force: true });\n return [];\n }\n const backend = resolveAskBackend(ask.provider);\n const files = [\n {\n content: askEndpointTemplate(backend, {\n cors: ask.cors,\n instructions: ask.instructions,\n retrieval: ask.retrieval,\n }),\n path: join(srcDir, \"pages\", \"api\", \"ask.ts\"),\n },\n ];\n if (backend.grounded) {\n files.push({\n content: `${JSON.stringify(await buildAskData(project))}\\n`,\n path: join(genDir, \"ask-data.json\"),\n });\n }\n return files;\n};\n\n/** Whether the ejected app hosts the MCP server (enabled and route free). */\nconst hostsMcp = (\n project: BlumeProject,\n userPages: { pattern: string }[]\n): boolean =>\n project.config.agents.mcp.enabled &&\n !routeIsTaken(\n userPages,\n project.graph.pages,\n project.config.agents.mcp.route\n );\n\n/**\n * The `.well-known` MCP discovery routes, injected as prerendered pages\n * alongside the user's own so the ejected Astro config wires them in. Empty\n * when the server is disabled or its route is already owned by a page.\n */\nconst mcpDiscoveryPages = (\n project: BlumeProject,\n userPages: { pattern: string }[]\n): { entrypoint: string; pattern: string }[] =>\n hostsMcp(project, userPages)\n ? [\n {\n entrypoint: \"src/blume-mcp/discovery.ts\",\n pattern: \"/.well-known/mcp.json\",\n },\n {\n entrypoint: \"src/blume-mcp/server-card.ts\",\n pattern: \"/.well-known/mcp/server-card.json\",\n },\n ]\n : [];\n\n/**\n * The MCP data snapshot, server endpoint, and `.well-known` discovery\n * documents, mirroring `writeMcpFiles` in generate.ts. Empty when the server\n * is disabled or its route is already owned by a page.\n */\nconst mcpFiles = async (\n project: BlumeProject,\n userPages: { pattern: string }[],\n srcDir: string,\n genDir: string\n): Promise<{ content: string; path: string }[]> => {\n if (!hostsMcp(project, userPages)) {\n return [];\n }\n const { route } = project.config.agents.mcp;\n const data = await buildMcpData(project);\n const discoveryInput = {\n base: data.base,\n name: data.name,\n route,\n site: data.site,\n version: data.version,\n };\n return [\n {\n content: `${JSON.stringify(data)}\\n`,\n path: join(genDir, \"mcp-data.json\"),\n },\n {\n content: mcpEndpointTemplate(),\n path: join(srcDir, \"pages\", mcpPageFile(route)),\n },\n {\n content: staticJsonEndpointTemplate(buildMcpDiscovery(discoveryInput)),\n path: join(srcDir, \"blume-mcp\", \"discovery.ts\"),\n },\n {\n content: staticJsonEndpointTemplate(buildMcpServerCard(discoveryInput)),\n path: join(srcDir, \"blume-mcp\", \"server-card.ts\"),\n },\n ];\n};\n\n/**\n * The `/changelog` index page, mirroring `shouldGenerateChangelog` in\n * generate.ts: emitted when `type: changelog` entries or a release-backed\n * changelog source exist, unless a user page already owns the route.\n */\nconst changelogFiles = (\n project: BlumeProject,\n userPages: { pattern: string }[],\n srcDir: string,\n options: Parameters<typeof changelogIndexTemplate>[0]\n): { content: string; path: string }[] => {\n const hasChangelog = project.graph.pages.some(\n (page) =>\n page.contentType === \"changelog\" &&\n !(page.meta.draft || page.meta.sidebar.hidden)\n );\n const hasChangelogSource =\n sourcesOfKind(project.config, \"github-releases\").length > 0;\n if (\n !(hasChangelog || hasChangelogSource) ||\n routeIsTaken(userPages, project.graph.pages, \"/changelog\")\n ) {\n return [];\n }\n return [\n {\n content: changelogIndexTemplate(options),\n path: join(srcDir, \"pages\", \"changelog.astro\"),\n },\n ];\n};\n\n/** Contents of the configured `examples.css`, or `\"\"` when unset/absent. */\n/**\n * Read a user stylesheet that eject inlines into a generated entry under\n * `genDir`, re-rooting its relative `@source` paths from the user's file.\n * Resolves to an empty string when the file is unset or absent.\n */\nconst readUserCss = async (\n file: string | null,\n genDir: string\n): Promise<string> => {\n if (!(file && existsSync(file))) {\n return \"\";\n }\n const css = await readFile(file, \"utf-8\");\n return rebaseSourceDirectives(css, { from: file, to: genDir });\n};\n\n/**\n * The per-example preview route `<Component />` iframes embed, nested under\n * `basePath` so it stays reachable behind a proxy that only forwards the\n * base. Empty when the project has no examples.\n */\nconst examplesPreviewFiles = (\n srcDir: string,\n basePath: string,\n hasExamples: boolean\n): { content: string; path: string }[] =>\n hasExamples\n ? [\n {\n content: examplesPageTemplate(),\n path: join(\n srcDir,\n \"pages\",\n ...basePath.split(\"/\").filter(Boolean),\n \"blume-examples\",\n \"[...path].astro\"\n ),\n },\n ]\n : [];\n\n/**\n * The packages the ejected app imports by bare name: Astro, Tailwind's Vite\n * plugin, `blume` itself, the integrations the config wires in, whatever the\n * configured adapters declare, and React when islands, examples, or Ask AI\n * render with it (Blume ships React, so projects rarely list it). They have\n * to be the project's own dependencies after eject — under a strict linker\n * such as pnpm nothing else makes them resolvable, and `astro build` fails.\n * The hidden runtime reaches the same packages through its `node_modules`\n * junction into Blume's own, so only eject declares the ones its generated\n * files import directly: the AI SDK the Ask AI route streams through (unless\n * `ai.ask.endpoint` points elsewhere, when no route is written) and the EPUB\n * generator's browser bundle `features.ts` loads.\n */\nconst ejectDependencies = (\n options: Parameters<typeof runtimeDependencies>[0]\n): string[] => {\n const { ask } = options.config.ai;\n return [\n ...new Set([\n \"astro\",\n \"@tailwindcss/vite\",\n \"blume\",\n ...runtimeDependencies(options),\n ...(options.needsReact ? [\"react\", \"react-dom\"] : []),\n ...(ask?.enabled && !ask.endpoint ? [\"ai\"] : []),\n ...(options.config.export.epub ? [\"epub-gen-memory\"] : []),\n ]),\n ];\n};\n\nconst ejectIntegrationBridge = (\n config: BlumeProject[\"config\"],\n root: string,\n configFile: string | null\n): Parameters<typeof astroConfigTemplate>[0][\"integrationBridge\"] =>\n config.integrations.length > 0 && configFile\n ? { configFile: toPosix(relative(root, configFile)) }\n : undefined;\n\n/**\n * Promote the generated runtime into the project as an owned Astro app. After\n * eject the project has a normal `astro.config.mjs` and `src/`, the `blume` CLI\n * is no longer required, and the `blume` package remains importable.\n *\n * Returns the written files plus non-fatal warnings, mirroring the generated\n * runtime (e.g. a Scalar reference spec that wasn't found, or a reference\n * route colliding with a content page), and the packages the ejected app\n * imports by bare name, for the caller to add to package.json.\n */\nexport const eject = async (\n root: string\n): Promise<{ dependencies: string[]; files: string[]; warnings: string[] }> => {\n const project = await scanProject(root, { mode: \"build\" });\n const { context, config } = project;\n\n const srcDir = join(root, \"src\");\n const genDir = join(srcDir, \"generated\");\n const askEnabled = config.ai.ask?.enabled ?? false;\n const exportPdf = config.export.pdf;\n const exportEpub = config.export.epub;\n\n // Discover examples first and expose them on the project, so the agent-facing\n // Markdown built below (raw `.md`, MCP) downlevels `<Component>` to its source.\n const examples = await discoverExamples(root, config.examples.source);\n project.examples = exampleMarkdownLookup(examples.examples);\n\n const [\n pages,\n needsReactRaw,\n usesMath,\n userTheme,\n userExamplesCss,\n rawMarkdown,\n islands,\n overrideAnalysis,\n ] = await Promise.all([\n context.pagesRoot ? discoverPages(context.pagesRoot) : Promise.resolve([]),\n detectNeedsReact(root),\n detectUsesMath(root),\n readUserCss(context.themeFile, genDir),\n readUserCss(\n config.examples.css ? join(root, config.examples.css) : null,\n genDir\n ),\n buildRawMarkdown(project),\n discoverIslands(root),\n analyzeComponentsFile(context.componentsFile),\n ]);\n // The `islands/` convention and `components.ts` share the same static plan\n // the CLI uses, with every import written relative to its generated file so\n // the ejected app builds from any checkout (like the example wrappers below).\n const slotPlan = planComponentSlots(islands.islands, overrideAnalysis, {\n relativeTo: genDir,\n });\n // Island/example/override frameworks drive which Astro renderers the ejected\n // config wires in; React also switches on for project `.tsx`/`.jsx` and Ask AI.\n const frameworks = new Set<string>([\n ...islands.islands.map((island) => island.framework),\n ...examples.examples.map((example) => example.framework),\n ...slotPlan.frameworks,\n ]);\n const needsReact = needsReactRaw || askEnabled || frameworks.has(\"react\");\n const needsVue = frameworks.has(\"vue\");\n const needsSvelte = frameworks.has(\"svelte\");\n\n // A project-relative context so generated files use portable paths.\n const relContext: ProjectContext = {\n ...context,\n contentRoot: toPosix(relative(root, context.contentRoot)),\n distDir: \"./dist\",\n outDir: \".\",\n pagesRoot: context.pagesRoot\n ? `./${toPosix(relative(root, context.pagesRoot))}`\n : null,\n root: \".\",\n };\n // The `docs` collection resolves its base against the real project root and\n // is then relativized like everything else, so the ejected content config\n // never bakes in the absolute path `eject` happened to run from.\n const docsCollection = resolveDocsCollection(config, root);\n const relCollection = {\n ...docsCollection,\n base: toPosix(relative(root, docsCollection.base)),\n };\n\n const relPages = [\n ...pages.map((page) => ({\n entrypoint: toPosix(relative(root, page.entrypoint)),\n pattern: page.pattern,\n })),\n ...mcpDiscoveryPages(project, pages),\n ];\n\n // Non-filesystem sources eject their materialized MDX into `<root>/blume-staged`\n // (a dedicated dir so it never clashes with a content root literally named\n // `content`; the relative `staged` collection points there).\n const staged = collectStaged(project);\n const hasStaged = staged.size > 0;\n const stagedDir = \"blume-staged\";\n const features = await clientFeaturesFor(project);\n const languageIcons = await languageIconCssFor(project);\n\n const files: {\n path: string;\n content: string;\n /** Don't overwrite a file the user already owns (e.g. a tuned tsconfig). */\n skipIfExists?: boolean;\n }[] = [\n {\n content: astroConfigTemplate({\n askPath: \"./src/generated/Ask.astro\",\n config,\n contentRoot: relContext.contentRoot,\n contentRoutes: project.manifest.routes.map((route) => route.path),\n context: relContext,\n examplesPath: \"./src/generated/examples.ts\",\n examplesThemePath: \"./src/generated/examples.css\",\n features,\n featuresPath: \"./src/generated/features.ts\",\n // No CLI publishes the runtime data modules in memory after eject, so\n // the config aliases each to the JSON snapshot written below.\n generatedModulesDir: \"./src/generated\",\n integrationBridge: ejectIntegrationBridge(\n config,\n root,\n context.configFile\n ),\n needsReact,\n needsSvelte,\n needsVue,\n pages: relPages,\n searchClientPath: \"./src/generated/search-client.ts\",\n themePath: \"./src/generated/app.css\",\n }),\n path: join(root, \"astro.config.mjs\"),\n },\n {\n content: runtimeTsconfigTemplate(),\n path: join(root, \"tsconfig.json\"),\n // Never clobber a hand-tuned tsconfig; only write ours if none exists.\n skipIfExists: true,\n },\n {\n content: contentConfigTemplate({\n collection: relCollection,\n config,\n context: relContext,\n staged: hasStaged,\n stagedBase: stagedDir,\n }),\n path: join(srcDir, \"content.config.ts\"),\n },\n {\n content: catchAllPageTemplate({\n exportEpub,\n exportPdf,\n mathEnabled: usesMath,\n needsReact,\n }),\n path: join(srcDir, \"pages\", \"[...slug].astro\"),\n },\n {\n content: slotPlan.module,\n path: join(genDir, \"components.ts\"),\n },\n // The example map the catch-all imports; written even when empty so the\n // `blume:examples` alias always resolves.\n {\n content: exampleMapTemplate(examples.examples, config.basePath),\n path: join(genDir, \"examples.ts\"),\n },\n {\n // The isolated Tailwind entry for `<Component />` preview frames.\n // Relative sources keep the ejected app portable.\n content: examplesEntryTemplate({\n configTokens: buildThemeCss(config.theme),\n sources: exampleScanRoots(root, examples.dir).map(\n (dir) => `${relative(genDir, dir)}/${EXAMPLE_SCAN_GLOB}`\n ),\n userCss: userExamplesCss,\n }),\n path: join(genDir, \"examples.css\"),\n },\n {\n content: tailwindEntryTemplate({\n configTokens: buildThemeCss(config.theme),\n languageIcons,\n // Relative paths from src/generated/app.css keep the ejected app\n // portable; the blume glob resolves the real install location when\n // the package is hoisted out of the project's own node_modules.\n sources: [\n blumeSourceGlob(root, genDir),\n \"../../**/*.{astro,mdx,ts,tsx}\",\n ],\n twoslashCss: twoslashCss(),\n userTheme,\n }),\n path: join(genDir, \"app.css\"),\n },\n { content: buildRuntimeData(project), path: join(genDir, \"data.json\") },\n // The header's Ask trigger behind the `blume:ask` alias. Always written — it\n // renders nothing when Ask is off — so the alias always resolves.\n {\n content: askComponentTemplate(askEnabled),\n path: join(genDir, \"Ask.astro\"),\n },\n {\n content: `${JSON.stringify(ejectOpenApiData(project))}\\n`,\n path: join(genDir, \"openapi.json\"),\n },\n {\n content: `${JSON.stringify(rawMarkdown)}\\n`,\n path: join(genDir, \"raw-markdown.json\"),\n },\n {\n // The partial → including-pages map behind `includeHmrPlugin`, which the\n // ejected astro.config wires at this exact path — without the file every\n // hot update's read would silently no-op and partial edits would serve\n // stale pages. A snapshot like the rest of `src/generated`: the ejected\n // app owns (and may regenerate or prune) it.\n content: `${JSON.stringify(buildIncludeGraph(project.graph.pages))}\\n`,\n path: join(genDir, \"includes.json\"),\n },\n {\n content: rawMarkdownEndpointTemplate(\"md\"),\n path: join(srcDir, \"pages\", \"[...slug].md.ts\"),\n },\n {\n content: rawMarkdownEndpointTemplate(\"mdx\"),\n path: join(srcDir, \"pages\", \"[...slug].mdx.ts\"),\n },\n ];\n\n if (askEnabled) {\n files.push(...(await askFiles(project, srcDir, genDir)));\n }\n\n if (config.seo.og.enabled) {\n files.push({\n content: ogEndpointTemplate(\n customOgRoutes(pages, config.title, config.seo.og.titles),\n { pageDescriptions: config.seo.og.description !== false }\n ),\n path: join(srcDir, \"pages\", \"og\", \"[...slug].png.ts\"),\n });\n }\n\n // The hosted MCP server and the `/changelog` index, mirrored from the\n // generated runtime (each helper returns `[]` when its feature is off).\n files.push(\n ...(await mcpFiles(project, pages, srcDir, genDir)),\n ...changelogFiles(project, pages, srcDir, {\n exportEpub,\n exportPdf,\n needsReact,\n staged: hasStaged,\n })\n );\n\n // Default 404 page, unless the project already owns `/404` (a custom\n // `pages/404.astro` or a `404.md` content page). The ejected project owns the\n // file afterwards and can edit or remove it.\n if (!routeIsTaken(pages, project.graph.pages, \"/404\")) {\n files.push({\n content: notFoundPageTemplate(),\n path: join(srcDir, \"pages\", \"404.astro\"),\n });\n }\n\n // The client-feature loaders behind the `blume:features` alias, and the\n // provider-specific client loader behind `blume:search-client`.\n files.push(\n {\n content: featuresTemplate(features),\n path: join(genDir, \"features.ts\"),\n },\n {\n content: searchClientTemplate(config),\n path: join(genDir, \"search-client.ts\"),\n }\n );\n\n const searchAdapter = config.search.provider;\n if (searchAdapter.mode === \"static\") {\n const documents = await buildSearchDocuments(project);\n files.push(\n {\n content: `${JSON.stringify(documents)}\\n`,\n path: join(genDir, \"search.json\"),\n },\n {\n content: searchEndpointTemplate(),\n path: join(srcDir, \"pages\", \"blume-search.json.ts\"),\n }\n );\n }\n\n if (searchAdapter.kind === \"mixedbread\") {\n files.push({\n content: mixedbreadSearchEndpointTemplate(searchAdapter.options),\n path: join(srcDir, \"pages\", \"api\", \"search.ts\"),\n });\n }\n\n const feeds = buildRssFeeds(project);\n if (feeds.length > 0) {\n const feedXml = Object.fromEntries(\n feeds.map((feed) => [feed.type, renderRssFeed(feed)])\n );\n files.push(\n {\n content: `${JSON.stringify(feedXml)}\\n`,\n path: join(genDir, \"rss.json\"),\n },\n {\n content: rssEndpointTemplate(),\n path: join(srcDir, \"pages\", \"[section]\", \"rss.xml.ts\"),\n }\n );\n }\n\n // Scalar API/AsyncAPI reference pages, mirrored from the generated runtime so\n // the ejected app keeps its reference routes — including the warnings (a\n // missing spec file, a route collision), which the caller surfaces exactly\n // like the generated-runtime path does.\n const warnings: string[] = [];\n if (hasScalarReferences(config)) {\n const references = await buildReferenceFiles({\n config,\n contentRoutes: new Set(project.graph.pages.map((page) => page.route)),\n root,\n });\n warnings.push(...references.warnings);\n for (const file of references.files) {\n files.push({\n content: file.content,\n path: join(srcDir, \"pages\", file.pagePath),\n });\n }\n }\n\n // Per-override and per-example live wrappers referenced by the maps above.\n files.push(\n ...slotPlan.wrappers.map((wrapper) => ({\n content: wrapper.content,\n path: join(genDir, \"component-slots\", `${wrapper.name}.astro`),\n })),\n ...examples.examples.map((example) => ({\n content: exampleWrapperTemplate(example, join(genDir, \"examples\")),\n path: join(genDir, \"examples\", `${exampleSlug(example.path)}.astro`),\n })),\n ...examplesPreviewFiles(\n srcDir,\n config.basePath,\n examples.examples.length > 0\n )\n );\n\n // Materialize staged source bodies under `<root>/blume-staged/<source>/<ref>`,\n // matching the relative `staged` collection base in the ejected config.\n for (const [entryId, content] of staged) {\n files.push({ content, path: join(root, stagedDir, entryId) });\n }\n\n const written = files.filter(\n (file) => !(file.skipIfExists && existsSync(file.path))\n );\n await Promise.all(\n written.map(async (file) => {\n await mkdir(join(file.path, \"..\"), { recursive: true });\n await writeFile(file.path, file.content, \"utf-8\");\n })\n );\n\n // Materialized source assets (e.g. downloaded Notion images) live under the\n // hidden runtime's public dir; copy them into the owned project's `public/`\n // so the staged content's `/blume-assets/…` references still resolve.\n const assetsSrc = join(context.outDir, \"public\", \"blume-assets\");\n if (existsSync(assetsSrc)) {\n await cp(assetsSrc, join(root, \"public\", \"blume-assets\"), {\n recursive: true,\n });\n }\n\n // The hidden runtime is no longer the source of truth.\n await rm(context.outDir, { force: true, recursive: true });\n\n return {\n dependencies: ejectDependencies({\n config,\n needsReact,\n needsSvelte,\n needsVue,\n }),\n files: written.map((file) => file.path),\n warnings,\n };\n};\n"
6
- ],
7
- "mappings": "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",
8
- "debugId": "21FD19BF3F1015A764756E2164756E21",
9
- "names": []
10
- }