blume 1.6.5 → 1.7.0

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 (179) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/bin/blume.mjs +3 -2
  3. package/dist/cli/chunk-0qhq7b8q.js +111 -0
  4. package/dist/cli/chunk-0qhq7b8q.js.map +11 -0
  5. package/dist/cli/chunk-18tjv4f7.js +96 -0
  6. package/dist/cli/chunk-18tjv4f7.js.map +10 -0
  7. package/dist/cli/chunk-27gtm2ym.js +69 -0
  8. package/dist/cli/chunk-27gtm2ym.js.map +11 -0
  9. package/dist/cli/chunk-2aj8ddew.js +72 -0
  10. package/dist/cli/chunk-2aj8ddew.js.map +10 -0
  11. package/dist/cli/chunk-3r94j3tc.js +221 -0
  12. package/dist/cli/chunk-3r94j3tc.js.map +10 -0
  13. package/dist/cli/chunk-4trphnvy.js +102 -0
  14. package/dist/cli/chunk-4trphnvy.js.map +11 -0
  15. package/dist/cli/chunk-4xyggvgf.js +21 -0
  16. package/dist/cli/chunk-4xyggvgf.js.map +10 -0
  17. package/dist/cli/chunk-5d4q7121.js +4064 -0
  18. package/dist/cli/chunk-5d4q7121.js.map +40 -0
  19. package/dist/cli/chunk-5hs6gb7n.js +32 -0
  20. package/dist/cli/chunk-5hs6gb7n.js.map +10 -0
  21. package/dist/cli/chunk-6kzzpsx8.js +26 -0
  22. package/dist/cli/chunk-6kzzpsx8.js.map +10 -0
  23. package/dist/cli/chunk-8gnpdsn1.js +952 -0
  24. package/dist/cli/chunk-8gnpdsn1.js.map +12 -0
  25. package/dist/cli/chunk-9qs6acpw.js +176 -0
  26. package/dist/cli/chunk-9qs6acpw.js.map +10 -0
  27. package/dist/cli/chunk-agy5rzxy.js +2453 -0
  28. package/dist/cli/chunk-agy5rzxy.js.map +15 -0
  29. package/dist/cli/chunk-bcy492zc.js +16 -0
  30. package/dist/cli/chunk-bcy492zc.js.map +10 -0
  31. package/dist/cli/chunk-btfr9yvw.js +41 -0
  32. package/dist/cli/chunk-btfr9yvw.js.map +10 -0
  33. package/dist/cli/chunk-cbjnx4s8.js +73 -0
  34. package/dist/cli/chunk-cbjnx4s8.js.map +10 -0
  35. package/dist/cli/chunk-cfw6x4rm.js +1967 -0
  36. package/dist/cli/chunk-cfw6x4rm.js.map +34 -0
  37. package/dist/cli/chunk-ckh3a410.js +277 -0
  38. package/dist/cli/chunk-ckh3a410.js.map +11 -0
  39. package/dist/cli/chunk-drke6t0h.js +259 -0
  40. package/dist/cli/chunk-drke6t0h.js.map +11 -0
  41. package/dist/cli/chunk-ev67ycx0.js +15 -0
  42. package/dist/cli/chunk-ev67ycx0.js.map +10 -0
  43. package/dist/cli/chunk-ey89bjj1.js +209 -0
  44. package/dist/cli/chunk-ey89bjj1.js.map +11 -0
  45. package/dist/cli/chunk-j6pxe0dt.js +69 -0
  46. package/dist/cli/chunk-j6pxe0dt.js.map +11 -0
  47. package/dist/cli/chunk-jk1zwka1.js +387 -0
  48. package/dist/cli/chunk-jk1zwka1.js.map +12 -0
  49. package/dist/cli/chunk-jtb45atp.js +467 -0
  50. package/dist/cli/chunk-jtb45atp.js.map +14 -0
  51. package/dist/cli/chunk-jxkxjsc1.js +76 -0
  52. package/dist/cli/chunk-jxkxjsc1.js.map +10 -0
  53. package/dist/cli/chunk-kwx90v78.js +81 -0
  54. package/dist/cli/chunk-kwx90v78.js.map +10 -0
  55. package/dist/cli/chunk-n0nyat6g.js +30 -0
  56. package/dist/cli/chunk-n0nyat6g.js.map +10 -0
  57. package/dist/cli/chunk-pxj10x8y.js +35 -0
  58. package/dist/cli/chunk-pxj10x8y.js.map +10 -0
  59. package/dist/cli/chunk-qq9nm3qd.js +1141 -0
  60. package/dist/cli/chunk-qq9nm3qd.js.map +19 -0
  61. package/dist/cli/chunk-s102bysw.js +5170 -0
  62. package/dist/cli/chunk-s102bysw.js.map +47 -0
  63. package/dist/cli/chunk-s5dsk8bj.js +769 -0
  64. package/dist/cli/chunk-s5dsk8bj.js.map +13 -0
  65. package/dist/cli/chunk-s5e5jt53.js +227 -0
  66. package/dist/cli/chunk-s5e5jt53.js.map +11 -0
  67. package/dist/cli/chunk-sbdqrjbb.js +81 -0
  68. package/dist/cli/chunk-sbdqrjbb.js.map +10 -0
  69. package/dist/cli/chunk-tnskyrej.js +117 -0
  70. package/dist/cli/chunk-tnskyrej.js.map +10 -0
  71. package/dist/cli/chunk-v2ymm99c.js +1016 -0
  72. package/dist/cli/chunk-v2ymm99c.js.map +13 -0
  73. package/dist/cli/chunk-v5mm027v.js +185 -0
  74. package/dist/cli/chunk-v5mm027v.js.map +11 -0
  75. package/dist/cli/chunk-vt8fgygt.js +23 -0
  76. package/dist/cli/chunk-vt8fgygt.js.map +10 -0
  77. package/dist/cli/chunk-vxv4x1n8.js +17 -0
  78. package/dist/cli/chunk-vxv4x1n8.js.map +10 -0
  79. package/dist/cli/chunk-wd27zjcz.js +60 -0
  80. package/dist/cli/chunk-wd27zjcz.js.map +10 -0
  81. package/dist/cli/chunk-x66c5yjn.js +23 -0
  82. package/dist/cli/chunk-x66c5yjn.js.map +10 -0
  83. package/dist/cli/chunk-xv91q4nm.js +5314 -0
  84. package/dist/cli/chunk-xv91q4nm.js.map +58 -0
  85. package/dist/cli/chunk-y3g15rvv.js +679 -0
  86. package/dist/cli/chunk-y3g15rvv.js.map +15 -0
  87. package/dist/cli/chunk-ye9zdkgv.js +136 -0
  88. package/dist/cli/chunk-ye9zdkgv.js.map +10 -0
  89. package/dist/cli/chunk-ynacq3ev.js +1062 -0
  90. package/dist/cli/chunk-ynacq3ev.js.map +25 -0
  91. package/dist/cli/chunk-zr3ygrq3.js +54 -0
  92. package/dist/cli/chunk-zr3ygrq3.js.map +10 -0
  93. package/dist/cli/index.js +55 -27597
  94. package/dist/cli/index.js.map +5 -243
  95. package/dist/types/ai/ask-context.d.ts +26 -0
  96. package/dist/types/components/layout/nav-utils.d.ts +33 -1
  97. package/dist/types/core/code-fences.d.ts +11 -0
  98. package/dist/types/core/package-root.d.ts +1 -1
  99. package/dist/types/core/schema.d.ts +70 -0
  100. package/dist/types/theme/fonts.d.ts +22 -22
  101. package/docs/02-deployment.mdx +22 -1
  102. package/docs/configuration/ask-ai.mdx +1 -1
  103. package/docs/configuration/customization.mdx +2 -9
  104. package/docs/content/navigation.mdx +2 -0
  105. package/docs/content/syntax.mdx +1 -1
  106. package/docs/discoverability/open-graph.mdx +4 -0
  107. package/docs/reference/cli.mdx +1 -1
  108. package/package.json +4 -2
  109. package/src/ai/api/handlers.ts +4 -7
  110. package/src/ai/api/paths.ts +8 -0
  111. package/src/ai/api/spec.ts +2 -1
  112. package/src/ai/ask-context.ts +378 -22
  113. package/src/astro/generate.ts +161 -28
  114. package/src/astro/include-hmr.ts +10 -13
  115. package/src/astro/include-refresh.ts +0 -0
  116. package/src/astro/index.ts +6 -1
  117. package/src/astro/integration.ts +280 -53
  118. package/src/astro/module-types.ts +83 -0
  119. package/src/astro/templates.ts +256 -108
  120. package/src/audit/image-size.ts +10 -8
  121. package/src/cli/command-meta.ts +77 -0
  122. package/src/cli/commands/add.ts +2 -4
  123. package/src/cli/commands/audit.ts +2 -4
  124. package/src/cli/commands/build.ts +70 -346
  125. package/src/cli/commands/check.ts +2 -4
  126. package/src/cli/commands/dev.ts +31 -42
  127. package/src/cli/commands/doctor.ts +2 -4
  128. package/src/cli/commands/eject.ts +3 -41
  129. package/src/cli/commands/eval.ts +2 -5
  130. package/src/cli/commands/init.ts +2 -4
  131. package/src/cli/commands/mcp-stdio.ts +2 -5
  132. package/src/cli/commands/preview.ts +3 -5
  133. package/src/cli/commands/sync.ts +2 -4
  134. package/src/cli/commands/translate.ts +2 -5
  135. package/src/cli/commands/validate.ts +2 -4
  136. package/src/cli/commands/version.ts +2 -4
  137. package/src/cli/eject-scripts.ts +0 -45
  138. package/src/cli/host-args.ts +16 -0
  139. package/src/cli/index.ts +84 -35
  140. package/src/cli/lazy-command.ts +47 -0
  141. package/src/components/Icon.astro +24 -0
  142. package/src/components/content/GithubInfo.astro +4 -1
  143. package/src/components/icon-sprite-middleware.ts +41 -0
  144. package/src/components/icon-sprite.ts +93 -0
  145. package/src/components/layout/IconSprite.astro +11 -0
  146. package/src/components/layout/NavTree.astro +156 -188
  147. package/src/components/layout/NavTreeCache.astro +45 -0
  148. package/src/components/layout/NavTreeScript.astro +256 -0
  149. package/src/components/layout/PageActions.astro +11 -5
  150. package/src/components/layout/PageLayout.astro +21 -3
  151. package/src/components/layout/ReferenceLayout.astro +21 -4
  152. package/src/components/layout/RootLayout.astro +44 -6
  153. package/src/components/layout/nav-cache.ts +49 -0
  154. package/src/components/layout/nav-utils.ts +69 -1
  155. package/src/components/layout/page-locale.ts +29 -0
  156. package/src/core/api-name.ts +18 -0
  157. package/src/core/code-fences.ts +48 -0
  158. package/src/core/content-assets.ts +3 -7
  159. package/src/core/includes.ts +3 -7
  160. package/src/core/package-root.ts +1 -1
  161. package/src/core/schema.ts +19 -0
  162. package/src/core/sources/normalize.ts +2 -37
  163. package/src/core/sources/obsidian.ts +3 -2
  164. package/src/core/svg-dimensions.ts +97 -0
  165. package/src/core/version-cut.ts +2 -2
  166. package/src/deploy/artifacts.ts +370 -0
  167. package/src/deploy/cloudflare-negotiation.ts +97 -32
  168. package/src/deploy/function-bundle.ts +66 -20
  169. package/src/deploy/sitemap.ts +6 -0
  170. package/src/deploy/vercel-negotiation.ts +8 -30
  171. package/src/markdown/language-icon.ts +64 -20
  172. package/src/markdown/mermaid.ts +11 -0
  173. package/src/og/cache.ts +236 -0
  174. package/src/og/card.ts +18 -16
  175. package/src/og/index.ts +8 -1
  176. package/src/openapi/render-mdx.ts +9 -5
  177. package/src/registry/eject.ts +23 -10
  178. package/src/theme/entry.ts +41 -7
  179. package/src/theme/fonts.ts +30 -23
package/CHANGELOG.md CHANGED
@@ -1,5 +1,44 @@
1
1
  # blume
2
2
 
3
+ ## 1.7.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 78aadc8: Bundle Mermaid and the EPUB generator only for sites that use them. Both were dynamic imports, so they never loaded on a page that didn't need them, but they were still built on every `blume build` — over 3 MB of client chunks (ELK, Cytoscape, KaTeX, every diagram type) that most sites never serve, and most of the client build's memory. The generated runtime now decides at generation time whether any page has a mermaid fence and whether `export.epub` is on, and leaves the libraries out of the module graph otherwise; the dev server also stops pre-bundling them for sites that don't need them.
8
+ - 51eb2ac: Leave collapsed sidebar sections out of the page. In `group` and `page` display modes, a section that isn't open on the current page no longer ships its rows in that page's HTML; the empty disclosure or panel fetches a prerendered fragment on first open (prefetched on hover or focus, and kept for the visit), so a large site's pages carry only the open section instead of every section on every page. Group ids are now stable across tab-scoped views of the sidebar.
9
+
10
+ ### Patch Changes
11
+
12
+ - ede3c94: Cache rendered OG cards between builds. Each card is stored under `node_modules/.cache/blume/og`, keyed by everything that decides its pixels (title, description, branding, palette, fonts, and the Blume version), so a rebuild renders only the cards whose inputs changed and reads the rest back from disk. The build log reports how many cards were reused, and cards no page asks for any more are pruned after each build.
13
+ - a43c1d9: Paint code-block language icons from the theme instead of inlining an SVG in every block. A block now carries `data-icon="<slug>"` and the theme masks the brand path onto it, with one rule per language the site's Markdown uses, so a page with twenty TypeScript blocks no longer repeats the same 1 kB logo twenty times. Set icons rendered by the Icon component are deduplicated the same way, through a per-page sprite.
14
+ - 7b7d585: Prerender pages concurrently. The build now renders up to eight pages at once (one per available CPU below that), so the main thread renders the next page while a page's OG card renders on a native thread and its HTML is written to disk, instead of idling behind each page's off-thread work. A 1,400-page site's route generation phase runs in roughly half the time.
15
+ - 903eec0: Render the sidebar's group rows (collapsible summaries, flat headings, drill-in buttons, and the links inside them) through theme utility classes, as page rows already are, so a sidebar with many groups adds a few dozen bytes per group to every page instead of several hundred.
16
+ - 7b7d585: Shrink the sidebar's per-page cost. Every page's HTML carries the whole sidebar, so on a large site the sidebar was most of every page: each row now renders as a single theme utility class (`blume-nav-link`) instead of a dozen Tailwind classes, a bare row drops its inner spans, and groups that don't contain the current page are rendered once per build and reused across pages. A 1,400-page site's pages go from about 570 kB to under 190 kB each, and the build's HTML output shrinks and renders accordingly.
17
+ - 4005c27: Load the curated Google fonts that ship as variable fonts (every preset except IBM Plex Mono, IBM Plex Serif, and Space Mono) as one weight range, so each page declares one `@font-face` per style instead of one per weight — the same font files, a quarter of the inline font CSS. A configured `"100..900"` range now reaches Astro in the form it understands; it previously loaded nothing for that weight.
18
+ - 071e35d: Fix `blume dev` and `blume build` failing on Windows with `Cannot find module '@astrojs/mdx'` under Bun's isolated linker. The hidden runtime's `node_modules` link is now a directory symlink (falling back to a junction where symlinks need elevated privileges), because Windows can't follow the relative symlinks Bun writes into Blume's dependency directory when they're reached through a junction.
19
+
20
+ ## 1.6.6
21
+
22
+ ### Patch Changes
23
+
24
+ - 2c373ef: Stop repeating `API` in generated descriptions when the name already says it. API reference overview and operation meta descriptions no longer turn a spec titled `Example API` (or `Payments APIs`, `Example API v2`) into `Example API API`, and the generated `/openapi.json` title no longer doubles the word for a site titled the same way.
25
+ - 520a660: Write the deploy artifacts from the integration's `astro:build:done` hook instead of after `astro build` returns: the search index and hosted-provider sync, `llms.txt`, `sitemap.xml`, `robots.txt`, `agent-readability.json`, the `.well-known` discovery files, Agent Skills, and the platform `_redirects`/`_headers` files now land in the directory Astro reports as its client output during the build. `blume build` output is unchanged. An ejected app keeps producing all of them from plain `astro build` — its config tells the hook to scan the project — so `blume eject` no longer warns that they are lost.
26
+ - a63c10e: Serve the prerendered per-page JSON documents on Cloudflare server builds whose requests run through the Worker: the wrapper answers them from the asset binding (conditional revalidations included) instead of letting Astro hand them to the `/api/` catch-all, and `.json` files stay on the static path under a deployment base or the coarse worker-first fallback.
27
+ - 1197e8b: Opt Cloudflare server builds out of Astro sessions with `session: false` instead of configuring an in-memory session driver. Blume never reads `Astro.session`; the driver only existed to keep `@astrojs/cloudflare` from declaring a `SESSION` KV binding, which Astro's opt-out now does directly.
28
+ - 1328384: Declare Blume's page front-matter schema on the generated content collections, so `entry.data` in the runtime is typed and normalized the same way the scan's page metadata is. Custom front-matter keys pass through, and a page the scan already dropped as invalid resolves to defaults instead of failing Astro's content sync.
29
+ - 4f5660b: Honor `Accept: text/markdown` and send the homepage agent-discovery `Link` header in `blume dev` when `deployment.base` is set. Astro's dev server rewrites the base off the URL before Blume's handler runs, so stripping it a second time left every request unmatched.
30
+ - bbdaa55: Keep the dev server up across page adds, removes, and folder renames. `blume dev` used to stop and recreate the Astro server on every route-set change; it now asks Astro's content layer to re-sync (`refreshContent`) and republishes the Markdown-negotiation routes in memory, so the generated config no longer changes with content and Astro never restarts in place for it. A moved page answers under its new route within a second, and editing an `<include>` partial re-syncs the store the same way instead of touching page mtimes.
31
+ - 6b9f367: Drop the `image-size` dependency. Its ICNS, HEIF/AVIF, and JXL parsers can be driven into an infinite loop by a crafted file with a zero-sized box or entry, which would hang `blume audit` or a build, and no patched release exists. The audit's Open Graph image checks now read dimensions through sharp, the image optimizer the build already ships, which covers the same formats and rejects malformed input instead of looping on it. SVG logos for the header and the OG card are measured by a small root-tag parser with the same rules as before: explicit `width`/`height`, a `viewBox` fallback, and CSS units at 96dpi.
32
+ - 7ee1100: Show Blume's config and content errors in the browser error overlay during `blume dev` on published installs. The dev-server handle was a module-level variable, and the CLI bundle and the copy of the integration Vite loads are separate module instances, so the overlay only ever worked inside the Blume repository itself. The handle now lives on `globalThis`, like the runtime data registry.
33
+ - 6b26322: Improve Ask AI grounding for conversational follow-ups and long structured pages. A bare follow-up ("Why?", "And then?") retrieves from the earlier user turn, a short follow-up keeps that turn's best match in view behind its own, and a question that names its subject always leads. Excerpts now keep whole Markdown sections — heading, list, and code fence intact — chosen by how well they cover the question rather than by length, and pages are parsed once per endpoint instead of per request. A closing code fence must now be bare, as CommonMark requires, so a same-length fence line with an info string inside an open block no longer ends it.
34
+ - 8d32eb3: Keep `<include>`-bearing Markdown pages fresh through Astro's documented loader context instead of patching the content store's writes. The collection loader now records a digest of each including page's partials in the collection's meta store and evicts the page's entry when that digest changes, so Astro's glob loader re-renders it on the next sync exactly as it would a new file. Same behavior on a partial edit in dev and on a warm-cache production build; no more replaying the store's private import bookkeeping.
35
+ - e9cd88f: Declare the `blume:*` virtual modules through Astro's `injectTypes` hook instead of a generated `src/env.d.ts`. The declarations now live under `.astro/integrations/blume/` and are referenced from Astro's own `types.d.ts`, so every project that mounts the integration — the hidden runtime and an ejected app alike — gets them without a hand-written file. A project `tsconfig.json` no longer needs `.blume/src/env.d.ts` in its `include`.
36
+ - 3e58b91: Load only the selected CLI command, reducing startup time for every command.
37
+ - f04afce: Read the Vercel function bundle audit's imports with `es-module-lexer` instead of a text scan, so code samples inside the MCP data chunk (a page quoting `import { config } from 'dotenv'` or `await import('bcryptjs')`) no longer fail `blume build` as missing packages.
38
+ - 3422d29: Read provider secrets through Astro's `getSecret()` from `astro:env/server` instead of `process.env`: the Ask AI endpoint's provider key (and the AI Gateway key, now passed to the gateway provider explicitly), the Mixedbread search endpoint's key, and `<GithubInfo>`'s `GITHUB_TOKEN`. Each adapter supplies secrets its own way, so a Cloudflare deployment's Worker bindings now reach these endpoints the same as environment variables do on Node and Vercel.
39
+ - 9dde9cb: Default `<html lang>` and `dir` in every document shell to the locale Astro resolves from the URL (`Astro.currentLocale`), then the site's default locale. A custom `pages/` page or a Scalar reference page under `/fr/` rendered as `lang="en"` unless it passed `locale` itself; the content catch-all still passes the manifest's locale, which also covers fallback pages.
40
+ - 8bbf9a1: Set Astro's `trailingSlash: "never"` in the generated config instead of hand-splicing a trailing-slash redirect into the Vercel routing config. Blume already treats the slashless URL as the one address for a page (canonicals, sitemap, hreflang), so Astro now enforces it too: `blume dev` answers `/guide/` with a 404 that names the setting, an on-demand server route redirects to `/guide`, and the Vercel adapter emits the same 308 route from the config that Blume used to splice by hand. Prerendered pages on static hosts are served exactly as before.
41
+
3
42
  ## 1.6.5
4
43
 
5
44
  ### Patch Changes
package/bin/blume.mjs CHANGED
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  // Blume CLI launcher.
3
3
  //
4
- // The published package ships a Node-compatible bundle at dist/cli/index.js
5
- // (built by scripts/build.ts) and the launcher prefers it. When it is absent —
4
+ // The published package ships a Node-compatible bundle in dist/cli/ — the entry
5
+ // index.js plus the code-split command chunks it imports, built by
6
+ // scripts/build.ts — and the launcher prefers it. When it is absent —
6
7
  // e.g. running from a source checkout — it falls back to the TypeScript source,
7
8
  // which requires a TS-aware runtime such as Bun (`bun bin/blume.mjs <cmd>`).
8
9
  import { existsSync } from "node:fs";
@@ -0,0 +1,111 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ serverFeatures
4
+ } from "./chunk-n0nyat6g.js";
5
+ import {
6
+ reportInternalError
7
+ } from "./chunk-btfr9yvw.js";
8
+ import {
9
+ generateRuntime
10
+ } from "./chunk-5d4q7121.js";
11
+ import {
12
+ loadEnvFiles
13
+ } from "./chunk-vt8fgygt.js";
14
+ import {
15
+ resolveAskBackend
16
+ } from "./chunk-agy5rzxy.js";
17
+ import {
18
+ scanProject
19
+ } from "./chunk-s102bysw.js";
20
+ import {
21
+ BlumeError,
22
+ hasErrors,
23
+ logger,
24
+ reportDiagnostics
25
+ } from "./chunk-ey89bjj1.js";
26
+
27
+ // src/cli/required-secrets.ts
28
+ var checkRequiredSecrets = (config) => {
29
+ const diagnostics = [];
30
+ const requireSecret = (feature, env, note) => {
31
+ if (process.env[env]) {
32
+ return;
33
+ }
34
+ const noteSuffix = note ? ` (${note})` : "";
35
+ diagnostics.push({
36
+ code: "BLUME_MISSING_SECRET",
37
+ message: `${feature} is enabled but ${env} is not set${noteSuffix}.`,
38
+ severity: "warning",
39
+ suggestion: `Set ${env} in .env.local for local dev, or in your host's environment for production.`
40
+ });
41
+ };
42
+ if (config.ai.ask?.enabled && !config.ai.ask.endpoint) {
43
+ const backend = resolveAskBackend(config.ai.ask);
44
+ if (backend.kind === "gateway") {
45
+ requireSecret("Ask AI (AI Gateway)", "AI_GATEWAY_API_KEY", "on Vercel the gateway can also authenticate via OIDC");
46
+ } else {
47
+ requireSecret("Ask AI", backend.apiKeyEnv);
48
+ }
49
+ }
50
+ if (config.search.provider === "mixedbread") {
51
+ requireSecret("Mixedbread search", "MIXEDBREAD_API_KEY");
52
+ }
53
+ return diagnostics;
54
+ };
55
+
56
+ // src/cli/prepare.ts
57
+ var prepareProject = async (options) => {
58
+ loadEnvFiles(options.root);
59
+ let project;
60
+ try {
61
+ project = await scanProject(options.root, {
62
+ devServerUrl: options.devServerUrl,
63
+ mode: options.mode,
64
+ overrides: options.overrides,
65
+ preview: options.preview,
66
+ refresh: options.refresh,
67
+ runtimeDir: options.runtimeDir
68
+ });
69
+ } catch (error) {
70
+ if (error instanceof BlumeError) {
71
+ reportDiagnostics([error.diagnostic], options.root);
72
+ } else {
73
+ reportInternalError(error);
74
+ }
75
+ process.exit(1);
76
+ }
77
+ if (options.mode === "build" && project.config.deployment.output === "static") {
78
+ const features = serverFeatures(project.config);
79
+ if (features.length > 0) {
80
+ reportDiagnostics([
81
+ {
82
+ code: "BLUME_SERVER_FEATURE_REQUIRED",
83
+ message: `${features.join(", ")} require server output, but deployment.output is "static".`,
84
+ severity: "error",
85
+ suggestion: 'Set deployment: { output: "server", adapter: "vercel" } in blume.config.ts.'
86
+ }
87
+ ], options.root);
88
+ process.exit(1);
89
+ }
90
+ }
91
+ const hadErrors = reportDiagnostics(project.diagnostics, options.root);
92
+ const dropped = project.droppedPages > 0 ? `${project.droppedPages} page(s) failed frontmatter validation and were dropped from the site. ` : "";
93
+ if (hadErrors && options.strict) {
94
+ logger.error(`Aborting due to errors. ${dropped}Fix the diagnostics above, or pass --no-strict to continue despite them.`);
95
+ process.exit(1);
96
+ }
97
+ if (hasErrors(project.diagnostics) && !options.strict) {
98
+ logger.warn(`Continuing despite errors. ${dropped}Use --strict to fail instead.`);
99
+ }
100
+ const { warnings } = await generateRuntime(project);
101
+ for (const warning of warnings) {
102
+ logger.warn(warning);
103
+ }
104
+ reportDiagnostics(checkRequiredSecrets(project.config), options.root);
105
+ return project;
106
+ };
107
+
108
+ export { prepareProject };
109
+
110
+ //# debugId=134333D0D8A00B5564756E2164756E21
111
+ //# sourceMappingURL=chunk-0qhq7b8q.js.map
@@ -0,0 +1,11 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/cli/required-secrets.ts", "../src/cli/prepare.ts"],
4
+ "sourcesContent": [
5
+ "import { resolveAskBackend } from \"../ai/ask.ts\";\nimport type { ResolvedConfig } from \"../core/schema.ts\";\nimport type { Diagnostic } from \"../core/types.ts\";\n\n/**\n * Warn early when an enabled feature needs a secret env var that isn't set, so\n * the failure surfaces at `blume dev`/`build` instead of at the first request in\n * production. These are runtime secrets (the endpoint reads them on the server),\n * so this warns rather than hard-fails — the value may live only in the deploy\n * environment. Build-time secrets (search-index sync) already warn during sync.\n */\nexport const checkRequiredSecrets = (config: ResolvedConfig): Diagnostic[] => {\n const diagnostics: Diagnostic[] = [];\n const requireSecret = (feature: string, env: string, note?: string): void => {\n if (process.env[env]) {\n return;\n }\n const noteSuffix = note ? ` (${note})` : \"\";\n diagnostics.push({\n code: \"BLUME_MISSING_SECRET\",\n message: `${feature} is enabled but ${env} is not set${noteSuffix}.`,\n severity: \"warning\",\n suggestion: `Set ${env} in .env.local for local dev, or in your host's environment for production.`,\n });\n };\n\n if (config.ai.ask?.enabled && !config.ai.ask.endpoint) {\n const backend = resolveAskBackend(config.ai.ask);\n if (backend.kind === \"gateway\") {\n requireSecret(\n \"Ask AI (AI Gateway)\",\n \"AI_GATEWAY_API_KEY\",\n \"on Vercel the gateway can also authenticate via OIDC\"\n );\n } else {\n requireSecret(\"Ask AI\", backend.apiKeyEnv);\n }\n }\n\n if (config.search.provider === \"mixedbread\") {\n requireSecret(\"Mixedbread search\", \"MIXEDBREAD_API_KEY\");\n }\n\n return diagnostics;\n};\n",
6
+ "import { generateRuntime } from \"../astro/generate.ts\";\nimport { BlumeError, hasErrors } from \"../core/diagnostics.ts\";\nimport { scanProject } from \"../core/project-graph.ts\";\nimport type {\n BlumeProject,\n BuildMode,\n ConfigOverrides,\n} from \"../core/project-graph.ts\";\nimport { serverFeatures } from \"../core/server-features.ts\";\nimport { loadEnvFiles } from \"./env.ts\";\nimport { reportInternalError } from \"./internal-error.ts\";\nimport { logger, reportDiagnostics } from \"./log.ts\";\nimport { checkRequiredSecrets } from \"./required-secrets.ts\";\n\nexport interface PrepareOptions {\n root: string;\n mode?: BuildMode;\n strict?: boolean;\n /** Local dev server URL, used as the `deployment.site` fallback (dev only). */\n devServerUrl?: string;\n /** Render drafts and fetch unpublished CMS content. */\n preview?: boolean;\n /** Force remote sources to re-fetch instead of serving the cached snapshot. */\n refresh?: boolean;\n /** CLI config overrides (e.g. `--output`, `--content-dir`). */\n overrides?: ConfigOverrides;\n /** Relocate the generated runtime (e.g. `.blume-verify` for `--isolated`). */\n runtimeDir?: string;\n}\n\n/**\n * Scan the project, surface diagnostics, and (re)generate the `.blume` runtime.\n * In strict mode, any error aborts. Returns the resolved project.\n */\nexport const prepareProject = async (\n options: PrepareOptions\n): Promise<BlumeProject> => {\n // Load `.env` files from the project root before the scan: remote sources\n // read their tokens from `process.env` during `scanProject`, and\n // `loadEnvFiles` never overrides variables that are already set.\n loadEnvFiles(options.root);\n\n let project: BlumeProject;\n try {\n project = await scanProject(options.root, {\n devServerUrl: options.devServerUrl,\n mode: options.mode,\n overrides: options.overrides,\n preview: options.preview,\n refresh: options.refresh,\n runtimeDir: options.runtimeDir,\n });\n } catch (error) {\n if (error instanceof BlumeError) {\n reportDiagnostics([error.diagnostic], options.root);\n } else {\n reportInternalError(error);\n }\n process.exit(1);\n }\n\n // Hard gate: server-only features cannot ship in a static build.\n if (\n options.mode === \"build\" &&\n project.config.deployment.output === \"static\"\n ) {\n const features = serverFeatures(project.config);\n if (features.length > 0) {\n reportDiagnostics(\n [\n {\n code: \"BLUME_SERVER_FEATURE_REQUIRED\",\n message: `${features.join(\", \")} require server output, but deployment.output is \"static\".`,\n severity: \"error\",\n suggestion:\n 'Set deployment: { output: \"server\", adapter: \"vercel\" } in blume.config.ts.',\n },\n ],\n options.root\n );\n process.exit(1);\n }\n }\n\n const hadErrors = reportDiagnostics(project.diagnostics, options.root);\n const dropped =\n project.droppedPages > 0\n ? `${project.droppedPages} page(s) failed frontmatter validation and were dropped from the site. `\n : \"\";\n if (hadErrors && options.strict) {\n logger.error(\n `Aborting due to errors. ${dropped}Fix the diagnostics above, or pass --no-strict to continue despite them.`\n );\n process.exit(1);\n }\n if (hasErrors(project.diagnostics) && !options.strict) {\n logger.warn(\n `Continuing despite errors. ${dropped}Use --strict to fail instead.`\n );\n }\n\n const { warnings } = await generateRuntime(project);\n for (const warning of warnings) {\n logger.warn(warning);\n }\n\n // Fail-fast on missing runtime secrets: warn now, not at the first request.\n reportDiagnostics(checkRequiredSecrets(project.config), options.root);\n\n return project;\n};\n"
7
+ ],
8
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;AAWO,IAAM,uBAAuB,CAAC,WAAyC;AAAA,EAC5E,MAAM,cAA4B,CAAC;AAAA,EACnC,MAAM,gBAAgB,CAAC,SAAiB,KAAa,SAAwB;AAAA,IAC3E,IAAI,QAAQ,IAAI,MAAM;AAAA,MACpB;AAAA,IACF;AAAA,IACA,MAAM,aAAa,OAAO,KAAK,UAAU;AAAA,IACzC,YAAY,KAAK;AAAA,MACf,MAAM;AAAA,MACN,SAAS,GAAG,0BAA0B,iBAAiB;AAAA,MACvD,UAAU;AAAA,MACV,YAAY,OAAO;AAAA,IACrB,CAAC;AAAA;AAAA,EAGH,IAAI,OAAO,GAAG,KAAK,WAAW,CAAC,OAAO,GAAG,IAAI,UAAU;AAAA,IACrD,MAAM,UAAU,kBAAkB,OAAO,GAAG,GAAG;AAAA,IAC/C,IAAI,QAAQ,SAAS,WAAW;AAAA,MAC9B,cACE,uBACA,sBACA,sDACF;AAAA,IACF,EAAO;AAAA,MACL,cAAc,UAAU,QAAQ,SAAS;AAAA;AAAA,EAE7C;AAAA,EAEA,IAAI,OAAO,OAAO,aAAa,cAAc;AAAA,IAC3C,cAAc,qBAAqB,oBAAoB;AAAA,EACzD;AAAA,EAEA,OAAO;AAAA;;;ACTF,IAAM,iBAAiB,OAC5B,YAC0B;AAAA,EAI1B,aAAa,QAAQ,IAAI;AAAA,EAEzB,IAAI;AAAA,EACJ,IAAI;AAAA,IACF,UAAU,MAAM,YAAY,QAAQ,MAAM;AAAA,MACxC,cAAc,QAAQ;AAAA,MACtB,MAAM,QAAQ;AAAA,MACd,WAAW,QAAQ;AAAA,MACnB,SAAS,QAAQ;AAAA,MACjB,SAAS,QAAQ;AAAA,MACjB,YAAY,QAAQ;AAAA,IACtB,CAAC;AAAA,IACD,OAAO,OAAO;AAAA,IACd,IAAI,iBAAiB,YAAY;AAAA,MAC/B,kBAAkB,CAAC,MAAM,UAAU,GAAG,QAAQ,IAAI;AAAA,IACpD,EAAO;AAAA,MACL,oBAAoB,KAAK;AAAA;AAAA,IAE3B,QAAQ,KAAK,CAAC;AAAA;AAAA,EAIhB,IACE,QAAQ,SAAS,WACjB,QAAQ,OAAO,WAAW,WAAW,UACrC;AAAA,IACA,MAAM,WAAW,eAAe,QAAQ,MAAM;AAAA,IAC9C,IAAI,SAAS,SAAS,GAAG;AAAA,MACvB,kBACE;AAAA,QACE;AAAA,UACE,MAAM;AAAA,UACN,SAAS,GAAG,SAAS,KAAK,IAAI;AAAA,UAC9B,UAAU;AAAA,UACV,YACE;AAAA,QACJ;AAAA,MACF,GACA,QAAQ,IACV;AAAA,MACA,QAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,EACF;AAAA,EAEA,MAAM,YAAY,kBAAkB,QAAQ,aAAa,QAAQ,IAAI;AAAA,EACrE,MAAM,UACJ,QAAQ,eAAe,IACnB,GAAG,QAAQ,wFACX;AAAA,EACN,IAAI,aAAa,QAAQ,QAAQ;AAAA,IAC/B,OAAO,MACL,2BAA2B,iFAC7B;AAAA,IACA,QAAQ,KAAK,CAAC;AAAA,EAChB;AAAA,EACA,IAAI,UAAU,QAAQ,WAAW,KAAK,CAAC,QAAQ,QAAQ;AAAA,IACrD,OAAO,KACL,8BAA8B,sCAChC;AAAA,EACF;AAAA,EAEA,QAAQ,aAAa,MAAM,gBAAgB,OAAO;AAAA,EAClD,WAAW,WAAW,UAAU;AAAA,IAC9B,OAAO,KAAK,OAAO;AAAA,EACrB;AAAA,EAGA,kBAAkB,qBAAqB,QAAQ,MAAM,GAAG,QAAQ,IAAI;AAAA,EAEpE,OAAO;AAAA;",
9
+ "debugId": "134333D0D8A00B5564756E2164756E21",
10
+ "names": []
11
+ }
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ ensureGitignore
4
+ } from "./chunk-5hs6gb7n.js";
5
+ import {
6
+ prepareProject
7
+ } from "./chunk-0qhq7b8q.js";
8
+ import"./chunk-n0nyat6g.js";
9
+ import"./chunk-btfr9yvw.js";
10
+ import {
11
+ refuseIfDevRunning
12
+ } from "./chunk-ye9zdkgv.js";
13
+ import"./chunk-5d4q7121.js";
14
+ import"./chunk-ynacq3ev.js";
15
+ import"./chunk-pxj10x8y.js";
16
+ import"./chunk-sbdqrjbb.js";
17
+ import"./chunk-v5mm027v.js";
18
+ import"./chunk-ev67ycx0.js";
19
+ import"./chunk-vt8fgygt.js";
20
+ import"./chunk-3r94j3tc.js";
21
+ import"./chunk-agy5rzxy.js";
22
+ import"./chunk-j6pxe0dt.js";
23
+ import"./chunk-vxv4x1n8.js";
24
+ import"./chunk-wd27zjcz.js";
25
+ import"./chunk-s102bysw.js";
26
+ import"./chunk-xv91q4nm.js";
27
+ import"./chunk-27gtm2ym.js";
28
+ import"./chunk-4xyggvgf.js";
29
+ import"./chunk-6kzzpsx8.js";
30
+ import {
31
+ logger
32
+ } from "./chunk-ey89bjj1.js";
33
+ import {
34
+ commandMeta
35
+ } from "./chunk-2aj8ddew.js";
36
+
37
+ // src/cli/commands/check.ts
38
+ import { existsSync } from "node:fs";
39
+ import { check } from "@astrojs/check";
40
+ import { sync } from "astro";
41
+ import { defineCommand } from "citty";
42
+ import { join } from "pathe";
43
+ var checkCommand = defineCommand({
44
+ args: {
45
+ isolated: {
46
+ description: "Type-check in an isolated .blume-verify runtime so a running dev server is untouched. For verifying changes while `blume dev` runs.",
47
+ type: "boolean"
48
+ },
49
+ preview: {
50
+ description: "Include drafts and unpublished CMS content.",
51
+ type: "boolean"
52
+ },
53
+ strict: {
54
+ description: "Fail on content diagnostics as well as type errors.",
55
+ type: "boolean"
56
+ }
57
+ },
58
+ meta: commandMeta.check,
59
+ async run({ args }) {
60
+ const root = process.cwd();
61
+ const runtimeDir = args.isolated ? ".blume-verify" : process.env.BLUME_RUNTIME_DIR;
62
+ refuseIfDevRunning(root, "checking", { isolatedHint: true, runtimeDir });
63
+ if (args.isolated) {
64
+ await ensureGitignore(root, [".blume-verify/"]);
65
+ }
66
+ const project = await prepareProject({
67
+ mode: "build",
68
+ preview: args.preview,
69
+ root,
70
+ runtimeDir,
71
+ strict: args.strict
72
+ });
73
+ const { outDir } = project.context;
74
+ await sync({ logLevel: "warn", root: outDir });
75
+ const tsconfig = join(root, "tsconfig.json");
76
+ logger.start(`Type-checking ${project.graph.pages.length} page(s)`);
77
+ const failed = await check({
78
+ minimumFailingSeverity: "error",
79
+ minimumSeverity: "hint",
80
+ root: outDir,
81
+ tsconfig: existsSync(tsconfig) ? tsconfig : undefined,
82
+ watch: false
83
+ });
84
+ if (failed) {
85
+ logger.error("Type check failed.");
86
+ process.exit(1);
87
+ }
88
+ logger.success("No type errors.");
89
+ }
90
+ });
91
+ export {
92
+ checkCommand
93
+ };
94
+
95
+ //# debugId=3E921ABECB33B7AC64756E2164756E21
96
+ //# sourceMappingURL=chunk-18tjv4f7.js.map
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/cli/commands/check.ts"],
4
+ "sourcesContent": [
5
+ "import { existsSync } from \"node:fs\";\n\nimport { check } from \"@astrojs/check\";\nimport { sync } from \"astro\";\nimport { defineCommand } from \"citty\";\nimport { join } from \"pathe\";\n\nimport { ensureGitignore } from \"../../core/gitignore.ts\";\nimport { commandMeta } from \"../command-meta.ts\";\nimport { refuseIfDevRunning } from \"../dev-lock.ts\";\nimport { logger } from \"../log.ts\";\nimport { prepareProject } from \"../prepare.ts\";\n\nexport const checkCommand = defineCommand({\n args: {\n isolated: {\n description:\n \"Type-check in an isolated .blume-verify runtime so a running dev server is untouched. For verifying changes while `blume dev` runs.\",\n type: \"boolean\",\n },\n preview: {\n description: \"Include drafts and unpublished CMS content.\",\n type: \"boolean\",\n },\n strict: {\n description: \"Fail on content diagnostics as well as type errors.\",\n type: \"boolean\",\n },\n },\n meta: commandMeta.check,\n async run({ args }) {\n const root = process.cwd();\n\n // `blume check` regenerates `.blume` just like `build`, so it must refuse a\n // live dev server unless isolated. `--isolated` (or BLUME_RUNTIME_DIR)\n // relocates the runtime to `.blume-verify`, which dev never locks.\n const runtimeDir = args.isolated\n ? \".blume-verify\"\n : process.env.BLUME_RUNTIME_DIR;\n refuseIfDevRunning(root, \"checking\", { isolatedHint: true, runtimeDir });\n if (args.isolated) {\n await ensureGitignore(root, [\".blume-verify/\"]);\n }\n\n const project = await prepareProject({\n mode: \"build\",\n preview: args.preview,\n root,\n runtimeDir,\n strict: args.strict,\n });\n\n const { outDir } = project.context;\n\n // Generate Astro's content/collection and font types into `.blume/.astro`\n // so `astro:*` virtual modules resolve during the check.\n await sync({ logLevel: \"warn\", root: outDir });\n\n // The project-root tsconfig is what covers the authored `pages/` and config;\n // without it astro check only sees the generated `.blume` project. Falls back\n // to the generated project's own tsconfig when the project has none.\n const tsconfig = join(root, \"tsconfig.json\");\n\n logger.start(`Type-checking ${project.graph.pages.length} page(s)`);\n const failed = await check({\n minimumFailingSeverity: \"error\",\n minimumSeverity: \"hint\",\n root: outDir,\n tsconfig: existsSync(tsconfig) ? tsconfig : undefined,\n watch: false,\n });\n\n if (failed) {\n logger.error(\"Type check failed.\");\n process.exit(1);\n }\n\n logger.success(\"No type errors.\");\n },\n});\n"
6
+ ],
7
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAEA;AACA;AACA;AACA;AAQO,IAAM,eAAe,cAAc;AAAA,EACxC,MAAM;AAAA,IACJ,UAAU;AAAA,MACR,aACE;AAAA,MACF,MAAM;AAAA,IACR;AAAA,IACA,SAAS;AAAA,MACP,aAAa;AAAA,MACb,MAAM;AAAA,IACR;AAAA,IACA,QAAQ;AAAA,MACN,aAAa;AAAA,MACb,MAAM;AAAA,IACR;AAAA,EACF;AAAA,EACA,MAAM,YAAY;AAAA,OACZ,IAAG,GAAG,QAAQ;AAAA,IAClB,MAAM,OAAO,QAAQ,IAAI;AAAA,IAKzB,MAAM,aAAa,KAAK,WACpB,kBACA,QAAQ,IAAI;AAAA,IAChB,mBAAmB,MAAM,YAAY,EAAE,cAAc,MAAM,WAAW,CAAC;AAAA,IACvE,IAAI,KAAK,UAAU;AAAA,MACjB,MAAM,gBAAgB,MAAM,CAAC,gBAAgB,CAAC;AAAA,IAChD;AAAA,IAEA,MAAM,UAAU,MAAM,eAAe;AAAA,MACnC,MAAM;AAAA,MACN,SAAS,KAAK;AAAA,MACd;AAAA,MACA;AAAA,MACA,QAAQ,KAAK;AAAA,IACf,CAAC;AAAA,IAED,QAAQ,WAAW,QAAQ;AAAA,IAI3B,MAAM,KAAK,EAAE,UAAU,QAAQ,MAAM,OAAO,CAAC;AAAA,IAK7C,MAAM,WAAW,KAAK,MAAM,eAAe;AAAA,IAE3C,OAAO,MAAM,iBAAiB,QAAQ,MAAM,MAAM,gBAAgB;AAAA,IAClE,MAAM,SAAS,MAAM,MAAM;AAAA,MACzB,wBAAwB;AAAA,MACxB,iBAAiB;AAAA,MACjB,MAAM;AAAA,MACN,UAAU,WAAW,QAAQ,IAAI,WAAW;AAAA,MAC5C,OAAO;AAAA,IACT,CAAC;AAAA,IAED,IAAI,QAAQ;AAAA,MACV,OAAO,MAAM,oBAAoB;AAAA,MACjC,QAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,IAEA,OAAO,QAAQ,iBAAiB;AAAA;AAEpC,CAAC;",
8
+ "debugId": "3E921ABECB33B7AC64756E2164756E21",
9
+ "names": []
10
+ }
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/core/trim.ts
4
+ var trimStart = (text, char) => {
5
+ let start = 0;
6
+ while (start < text.length && text[start] === char) {
7
+ start += 1;
8
+ }
9
+ return text.slice(start);
10
+ };
11
+ var trimEnd = (text, char) => {
12
+ let end = text.length;
13
+ while (end > 0 && text[end - 1] === char) {
14
+ end -= 1;
15
+ }
16
+ return text.slice(0, end);
17
+ };
18
+ var trimChar = (text, char) => trimEnd(trimStart(text, char), char);
19
+
20
+ // src/core/base-path.ts
21
+ var normalizeBasePath = (input) => {
22
+ if (!input) {
23
+ return "";
24
+ }
25
+ const trimmed = input.trim().split("/").filter(Boolean).join("/");
26
+ return trimmed === "" ? "" : `/${trimmed}`;
27
+ };
28
+ var normalizePath = (path) => {
29
+ const trimmed = trimEnd(path, "/");
30
+ return trimmed === "" ? "/" : trimmed;
31
+ };
32
+ var normalizeRoute = (input) => {
33
+ const trimmed = trimEnd(input.trim(), "/");
34
+ if (trimmed === "") {
35
+ return "/";
36
+ }
37
+ return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
38
+ };
39
+ var isInternalPath = (target) => target.startsWith("/") && !target.startsWith("//");
40
+ var withBasePath = (basePath, route) => {
41
+ if (!basePath || !isInternalPath(route)) {
42
+ return route;
43
+ }
44
+ if (route === basePath || route.startsWith(`${basePath}/`)) {
45
+ return route;
46
+ }
47
+ return route === "/" ? basePath : `${basePath}${route}`;
48
+ };
49
+ var withComposedBasePath = (deployBase, basePath, route) => {
50
+ const composed = `${deployBase}${basePath}`;
51
+ if (composed && isInternalPath(route) && (route === composed || route.startsWith(`${composed}/`))) {
52
+ return route;
53
+ }
54
+ return withBasePath(deployBase, withBasePath(basePath, route));
55
+ };
56
+ var stripBasePath = (basePath, route) => {
57
+ if (!basePath) {
58
+ return route;
59
+ }
60
+ if (route === basePath) {
61
+ return "/";
62
+ }
63
+ return route.startsWith(`${basePath}/`) ? route.slice(basePath.length) : route;
64
+ };
65
+
66
+ export { trimEnd, trimChar, normalizeBasePath, normalizePath, normalizeRoute, isInternalPath, withBasePath, withComposedBasePath, stripBasePath };
67
+
68
+ //# debugId=208B4F87BEF51A7364756E2164756E21
69
+ //# sourceMappingURL=chunk-27gtm2ym.js.map
@@ -0,0 +1,11 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/core/trim.ts", "../src/core/base-path.ts"],
4
+ "sourcesContent": [
5
+ "/**\n * Single-character trimming, done without regular expressions.\n *\n * The obvious spellings — `/^\\/+/`, `/\\/+$/`, `/^\\/+|\\/+$/g` — take quadratic\n * time on a run of the trimmed character, because a failed match at one start\n * position tells the engine nothing about the next one. Every caller here trims\n * a value that comes from outside Blume (a configured route, a spec URL, a site\n * origin), so the slow path is reachable from user input rather than only from\n * our own literals. These loops are linear and allocation-free.\n */\n\n/** Drop every leading `char` (`\"///a\"` -> `\"a\"` for `\"/\"`). */\nexport const trimStart = (text: string, char: string): string => {\n let start = 0;\n while (start < text.length && text[start] === char) {\n start += 1;\n }\n return text.slice(start);\n};\n\n/** Drop every trailing `char` (`\"a///\"` -> `\"a\"` for `\"/\"`). */\nexport const trimEnd = (text: string, char: string): string => {\n let end = text.length;\n while (end > 0 && text[end - 1] === char) {\n end -= 1;\n }\n return text.slice(0, end);\n};\n\n/** Drop every leading *and* trailing `char` (`\"///a///\"` -> `\"a\"`). */\nexport const trimChar = (text: string, char: string): string =>\n trimEnd(trimStart(text, char), char);\n",
6
+ "/**\n * The site-wide `basePath` — a mount point that prepends a segment to every\n * generated route (`/docs/getting-started`) while staying invisible to the\n * navigation tree (see `core/navigation.ts`, which builds groups from a page's\n * base-less `navPath` and takes URLs from its based `route`). This is distinct\n * from a per-source `prefix` (a namespace that *does* create a sidebar group)\n * and from `deployment.base` (Astro's host-subdirectory base); the two compose,\n * stacking as `{deployment.base}/{basePath}/page`.\n *\n * These helpers run server-side (config, route construction, the link checker,\n * redirects, the markdown link plugin). The client-side counterpart lives in\n * `components/islands/base-path.ts` and serves `deployment.base` via `BASE_URL`.\n */\n\nimport { trimEnd } from \"./trim.ts\";\n\n/**\n * Canonicalize a configured base path to either `\"\"` (none) or `/seg[/seg…]`\n * (leading slash, no trailing slash, collapsed inner slashes). A blank value or\n * bare `/` normalizes to `\"\"`, so an unset/`\"/\"` base is a clean no-op.\n */\nexport const normalizeBasePath = (input?: string): string => {\n if (!input) {\n return \"\";\n }\n // Splitting on \"/\" and dropping the empty parts trims the edges and collapses\n // inner runs in one linear pass; the regex spellings of both are quadratic on\n // a long run of slashes (see `core/trim.ts`).\n const trimmed = input.trim().split(\"/\").filter(Boolean).join(\"/\");\n return trimmed === \"\" ? \"\" : `/${trimmed}`;\n};\n\n/**\n * Normalize a served path for comparison: drop the trailing slash (Astro serves\n * `/docs` and `/docs/` as the same page) and collapse an empty path to `/`.\n */\nexport const normalizePath = (path: string): string => {\n const trimmed = trimEnd(path, \"/\");\n return trimmed === \"\" ? \"/\" : trimmed;\n};\n\n/**\n * Canonicalize a route-ish string (a configured route, a page path, an agent-\n * supplied route) to `/` or `/seg[/seg…]`: trimmed, exactly one leading slash,\n * no trailing slash. The shared spelling of what openapi/references,\n * ai/ask-context, and ai/mcp/server each hand-rolled with slightly different\n * regexes.\n */\nexport const normalizeRoute = (input: string): string => {\n const trimmed = trimEnd(input.trim(), \"/\");\n if (trimmed === \"\") {\n return \"/\";\n }\n return trimmed.startsWith(\"/\") ? trimmed : `/${trimmed}`;\n};\n\n/**\n * Whether a link target is a root-relative internal path (`/x`) — the only\n * shape a base path applies to. Protocol-relative (`//host`), absolute URLs,\n * other schemes (`mailto:`), fragments (`#x`), and relative paths are excluded.\n */\nexport const isInternalPath = (target: string): boolean =>\n target.startsWith(\"/\") && !target.startsWith(\"//\");\n\n/**\n * Whether a rendered link should open in a new tab: an absolute http(s) URL or\n * a protocol-relative one (`//host/path`). Other schemes (`mailto:`, `tel:`)\n * also leave the site but hand off to another application, where a `_blank`\n * target only opens an empty tab beside it. The one predicate behind every\n * chrome link, so a header action and a sidebar featured link treat the same\n * href alike.\n */\nexport const isExternalUrl = (target: string): boolean =>\n /^https?:\\/\\//iu.test(target) || target.startsWith(\"//\");\n\n/**\n * Idempotently prepend `basePath` to a root-relative route. A route already\n * equal to or nested under the base is returned unchanged, so authors who write\n * the base by hand (`/docs/x`) aren't double-prefixed to `/docs/docs/x`.\n */\nexport const withBasePath = (basePath: string, route: string): string => {\n if (!basePath || !isInternalPath(route)) {\n return route;\n }\n if (route === basePath || route.startsWith(`${basePath}/`)) {\n return route;\n }\n return route === \"/\" ? basePath : `${basePath}${route}`;\n};\n\n/**\n * {@link withBasePath} for the composed `deployment.base` + `basePath` stack\n * (`/base` + `/docs` serves pages at `/base/docs/x`). The hand-written-base\n * promise applies per layer: authors write `basePath` by hand (see\n * `markdown/base-links.ts`), so a `/docs/x` link gains only the deployment base\n * (`/base/docs/x`) rather than being double-prefixed to `/base/docs/docs/x`,\n * and a route already under the full composite is returned unchanged.\n */\nexport const withComposedBasePath = (\n deployBase: string,\n basePath: string,\n route: string\n): string => {\n const composed = `${deployBase}${basePath}`;\n if (\n composed &&\n isInternalPath(route) &&\n (route === composed || route.startsWith(`${composed}/`))\n ) {\n return route;\n }\n return withBasePath(deployBase, withBasePath(basePath, route));\n};\n\n/**\n * Remove `basePath` from the front of a route (`/docs/guide` -> `/guide`,\n * `/docs` -> `/`). A route not under the base is returned unchanged. Inverse of\n * {@link withBasePath}; used to resolve public assets, which live at the site\n * root regardless of the base.\n */\nexport const stripBasePath = (basePath: string, route: string): string => {\n if (!basePath) {\n return route;\n }\n if (route === basePath) {\n return \"/\";\n }\n return route.startsWith(`${basePath}/`)\n ? route.slice(basePath.length)\n : route;\n};\n"
7
+ ],
8
+ "mappings": ";;;AAYO,IAAM,YAAY,CAAC,MAAc,SAAyB;AAAA,EAC/D,IAAI,QAAQ;AAAA,EACZ,OAAO,QAAQ,KAAK,UAAU,KAAK,WAAW,MAAM;AAAA,IAClD,SAAS;AAAA,EACX;AAAA,EACA,OAAO,KAAK,MAAM,KAAK;AAAA;AAIlB,IAAM,UAAU,CAAC,MAAc,SAAyB;AAAA,EAC7D,IAAI,MAAM,KAAK;AAAA,EACf,OAAO,MAAM,KAAK,KAAK,MAAM,OAAO,MAAM;AAAA,IACxC,OAAO;AAAA,EACT;AAAA,EACA,OAAO,KAAK,MAAM,GAAG,GAAG;AAAA;AAInB,IAAM,WAAW,CAAC,MAAc,SACrC,QAAQ,UAAU,MAAM,IAAI,GAAG,IAAI;;;ACV9B,IAAM,oBAAoB,CAAC,UAA2B;AAAA,EAC3D,IAAI,CAAC,OAAO;AAAA,IACV,OAAO;AAAA,EACT;AAAA,EAIA,MAAM,UAAU,MAAM,KAAK,EAAE,MAAM,GAAG,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;AAAA,EAChE,OAAO,YAAY,KAAK,KAAK,IAAI;AAAA;AAO5B,IAAM,gBAAgB,CAAC,SAAyB;AAAA,EACrD,MAAM,UAAU,QAAQ,MAAM,GAAG;AAAA,EACjC,OAAO,YAAY,KAAK,MAAM;AAAA;AAUzB,IAAM,iBAAiB,CAAC,UAA0B;AAAA,EACvD,MAAM,UAAU,QAAQ,MAAM,KAAK,GAAG,GAAG;AAAA,EACzC,IAAI,YAAY,IAAI;AAAA,IAClB,OAAO;AAAA,EACT;AAAA,EACA,OAAO,QAAQ,WAAW,GAAG,IAAI,UAAU,IAAI;AAAA;AAQ1C,IAAM,iBAAiB,CAAC,WAC7B,OAAO,WAAW,GAAG,KAAK,CAAC,OAAO,WAAW,IAAI;AAkB5C,IAAM,eAAe,CAAC,UAAkB,UAA0B;AAAA,EACvE,IAAI,CAAC,YAAY,CAAC,eAAe,KAAK,GAAG;AAAA,IACvC,OAAO;AAAA,EACT;AAAA,EACA,IAAI,UAAU,YAAY,MAAM,WAAW,GAAG,WAAW,GAAG;AAAA,IAC1D,OAAO;AAAA,EACT;AAAA,EACA,OAAO,UAAU,MAAM,WAAW,GAAG,WAAW;AAAA;AAW3C,IAAM,uBAAuB,CAClC,YACA,UACA,UACW;AAAA,EACX,MAAM,WAAW,GAAG,aAAa;AAAA,EACjC,IACE,YACA,eAAe,KAAK,MACnB,UAAU,YAAY,MAAM,WAAW,GAAG,WAAW,IACtD;AAAA,IACA,OAAO;AAAA,EACT;AAAA,EACA,OAAO,aAAa,YAAY,aAAa,UAAU,KAAK,CAAC;AAAA;AASxD,IAAM,gBAAgB,CAAC,UAAkB,UAA0B;AAAA,EACxE,IAAI,CAAC,UAAU;AAAA,IACb,OAAO;AAAA,EACT;AAAA,EACA,IAAI,UAAU,UAAU;AAAA,IACtB,OAAO;AAAA,EACT;AAAA,EACA,OAAO,MAAM,WAAW,GAAG,WAAW,IAClC,MAAM,MAAM,SAAS,MAAM,IAC3B;AAAA;",
9
+ "debugId": "208B4F87BEF51A7364756E2164756E21",
10
+ "names": []
11
+ }
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from "node:module";
3
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
4
+
5
+ // src/cli/command-meta.ts
6
+ var commandMeta = {
7
+ add: {
8
+ description: "Install a source component or template from the registry.",
9
+ name: "add"
10
+ },
11
+ audit: {
12
+ description: "Audit the built site for SEO and site-health issues.",
13
+ name: "audit"
14
+ },
15
+ build: {
16
+ description: "Build the docs site for production.",
17
+ name: "build"
18
+ },
19
+ check: {
20
+ description: "Type-check the docs site with astro check.",
21
+ name: "check"
22
+ },
23
+ dev: {
24
+ description: "Start the Blume development server.",
25
+ name: "dev"
26
+ },
27
+ doctor: {
28
+ description: "Diagnose common configuration and content problems.",
29
+ name: "doctor"
30
+ },
31
+ eject: {
32
+ description: "Promote the generated runtime into an owned Astro project.",
33
+ name: "eject"
34
+ },
35
+ eval: {
36
+ description: "Test the docs: an agent answers your questions using only the documentation.",
37
+ name: "eval"
38
+ },
39
+ init: {
40
+ description: "Scaffold a minimal Blume project.",
41
+ name: "init"
42
+ },
43
+ "mcp-stdio": {
44
+ description: "Serve an MCP data snapshot over stdio (internal, used by `blume eval`).",
45
+ name: "mcp-stdio"
46
+ },
47
+ preview: {
48
+ description: "Preview the last production build.",
49
+ name: "preview"
50
+ },
51
+ sync: {
52
+ description: "Re-fetch remote content sources and regenerate the runtime.",
53
+ name: "sync"
54
+ },
55
+ translate: {
56
+ description: "Translate docs into the configured locales with a local agent CLI.",
57
+ name: "translate"
58
+ },
59
+ validate: {
60
+ description: "Validate internal, anchor, asset, and external links.",
61
+ name: "validate"
62
+ },
63
+ version: {
64
+ description: "Freeze the current docs as an archived version.",
65
+ name: "version"
66
+ }
67
+ };
68
+
69
+ export { __require, commandMeta };
70
+
71
+ //# debugId=3DF60EB79641093164756E2164756E21
72
+ //# sourceMappingURL=chunk-2aj8ddew.js.map
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/cli/command-meta.ts"],
4
+ "sourcesContent": [
5
+ "import type { CommandMeta } from \"citty\";\n\n/**\n * Every command's `meta`, held apart from the command modules themselves.\n *\n * The CLI entry loads each command lazily (see `lazy-command.ts`), but citty\n * still reads every subcommand's `meta` to render `blume --help` and to match\n * an unknown name against aliases. Keeping that table here lets those paths\n * run without importing a single command module — `dev` alone drags in Astro,\n * `mcp-stdio` the MCP SDK. The command modules read their `meta` from this\n * table too, so the entry and the command can't drift.\n */\nexport const commandMeta = {\n add: {\n description: \"Install a source component or template from the registry.\",\n name: \"add\",\n },\n audit: {\n description: \"Audit the built site for SEO and site-health issues.\",\n name: \"audit\",\n },\n build: {\n description: \"Build the docs site for production.\",\n name: \"build\",\n },\n check: {\n description: \"Type-check the docs site with astro check.\",\n name: \"check\",\n },\n dev: {\n description: \"Start the Blume development server.\",\n name: \"dev\",\n },\n doctor: {\n description: \"Diagnose common configuration and content problems.\",\n name: \"doctor\",\n },\n eject: {\n description: \"Promote the generated runtime into an owned Astro project.\",\n name: \"eject\",\n },\n eval: {\n description:\n \"Test the docs: an agent answers your questions using only the documentation.\",\n name: \"eval\",\n },\n init: {\n description: \"Scaffold a minimal Blume project.\",\n name: \"init\",\n },\n \"mcp-stdio\": {\n description:\n \"Serve an MCP data snapshot over stdio (internal, used by `blume eval`).\",\n name: \"mcp-stdio\",\n },\n preview: {\n description: \"Preview the last production build.\",\n name: \"preview\",\n },\n sync: {\n description: \"Re-fetch remote content sources and regenerate the runtime.\",\n name: \"sync\",\n },\n translate: {\n description:\n \"Translate docs into the configured locales with a local agent CLI.\",\n name: \"translate\",\n },\n validate: {\n description: \"Validate internal, anchor, asset, and external links.\",\n name: \"validate\",\n },\n version: {\n description: \"Freeze the current docs as an archived version.\",\n name: \"version\",\n },\n} satisfies Record<string, CommandMeta>;\n"
6
+ ],
7
+ "mappings": ";;;;;AAYO,IAAM,cAAc;AAAA,EACzB,KAAK;AAAA,IACH,aAAa;AAAA,IACb,MAAM;AAAA,EACR;AAAA,EACA,OAAO;AAAA,IACL,aAAa;AAAA,IACb,MAAM;AAAA,EACR;AAAA,EACA,OAAO;AAAA,IACL,aAAa;AAAA,IACb,MAAM;AAAA,EACR;AAAA,EACA,OAAO;AAAA,IACL,aAAa;AAAA,IACb,MAAM;AAAA,EACR;AAAA,EACA,KAAK;AAAA,IACH,aAAa;AAAA,IACb,MAAM;AAAA,EACR;AAAA,EACA,QAAQ;AAAA,IACN,aAAa;AAAA,IACb,MAAM;AAAA,EACR;AAAA,EACA,OAAO;AAAA,IACL,aAAa;AAAA,IACb,MAAM;AAAA,EACR;AAAA,EACA,MAAM;AAAA,IACJ,aACE;AAAA,IACF,MAAM;AAAA,EACR;AAAA,EACA,MAAM;AAAA,IACJ,aAAa;AAAA,IACb,MAAM;AAAA,EACR;AAAA,EACA,aAAa;AAAA,IACX,aACE;AAAA,IACF,MAAM;AAAA,EACR;AAAA,EACA,SAAS;AAAA,IACP,aAAa;AAAA,IACb,MAAM;AAAA,EACR;AAAA,EACA,MAAM;AAAA,IACJ,aAAa;AAAA,IACb,MAAM;AAAA,EACR;AAAA,EACA,WAAW;AAAA,IACT,aACE;AAAA,IACF,MAAM;AAAA,EACR;AAAA,EACA,UAAU;AAAA,IACR,aAAa;AAAA,IACb,MAAM;AAAA,EACR;AAAA,EACA,SAAS;AAAA,IACP,aAAa;AAAA,IACb,MAAM;AAAA,EACR;AACF;",
8
+ "debugId": "3DF60EB79641093164756E2164756E21",
9
+ "names": []
10
+ }