blume 1.6.4 → 1.6.6

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 (178) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/bin/blume.mjs +3 -2
  3. package/dist/cli/chunk-0ewz4trd.js +679 -0
  4. package/dist/cli/chunk-0ewz4trd.js.map +15 -0
  5. package/dist/cli/chunk-27gtm2ym.js +69 -0
  6. package/dist/cli/chunk-27gtm2ym.js.map +11 -0
  7. package/dist/cli/chunk-2aj8ddew.js +72 -0
  8. package/dist/cli/chunk-2aj8ddew.js.map +10 -0
  9. package/dist/cli/chunk-3k0kzs6d.js +69 -0
  10. package/dist/cli/chunk-3k0kzs6d.js.map +11 -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-5hs6gb7n.js +32 -0
  18. package/dist/cli/chunk-5hs6gb7n.js.map +10 -0
  19. package/dist/cli/chunk-5yvt556e.js +185 -0
  20. package/dist/cli/chunk-5yvt556e.js.map +11 -0
  21. package/dist/cli/chunk-62qsssnh.js +3808 -0
  22. package/dist/cli/chunk-62qsssnh.js.map +36 -0
  23. package/dist/cli/chunk-6kzzpsx8.js +26 -0
  24. package/dist/cli/chunk-6kzzpsx8.js.map +10 -0
  25. package/dist/cli/chunk-8gnpdsn1.js +952 -0
  26. package/dist/cli/chunk-8gnpdsn1.js.map +12 -0
  27. package/dist/cli/chunk-9sh49q0h.js +30 -0
  28. package/dist/cli/chunk-9sh49q0h.js.map +10 -0
  29. package/dist/cli/chunk-aerwpe14.js +2370 -0
  30. package/dist/cli/chunk-aerwpe14.js.map +15 -0
  31. package/dist/cli/chunk-ag1zyr5x.js +176 -0
  32. package/dist/cli/chunk-ag1zyr5x.js.map +10 -0
  33. package/dist/cli/chunk-bawgnt8x.js +277 -0
  34. package/dist/cli/chunk-bawgnt8x.js.map +11 -0
  35. package/dist/cli/chunk-bcy492zc.js +16 -0
  36. package/dist/cli/chunk-bcy492zc.js.map +10 -0
  37. package/dist/cli/chunk-btfr9yvw.js +41 -0
  38. package/dist/cli/chunk-btfr9yvw.js.map +10 -0
  39. package/dist/cli/chunk-cbjnx4s8.js +73 -0
  40. package/dist/cli/chunk-cbjnx4s8.js.map +10 -0
  41. package/dist/cli/chunk-cnvm6k3e.js +96 -0
  42. package/dist/cli/chunk-cnvm6k3e.js.map +10 -0
  43. package/dist/cli/chunk-etsqspj6.js +5170 -0
  44. package/dist/cli/chunk-etsqspj6.js.map +47 -0
  45. package/dist/cli/chunk-ev67ycx0.js +15 -0
  46. package/dist/cli/chunk-ev67ycx0.js.map +10 -0
  47. package/dist/cli/chunk-ey89bjj1.js +209 -0
  48. package/dist/cli/chunk-ey89bjj1.js.map +11 -0
  49. package/dist/cli/chunk-f75cqye8.js +76 -0
  50. package/dist/cli/chunk-f75cqye8.js.map +10 -0
  51. package/dist/cli/chunk-j00ezcg5.js +259 -0
  52. package/dist/cli/chunk-j00ezcg5.js.map +11 -0
  53. package/dist/cli/chunk-jtb45atp.js +467 -0
  54. package/dist/cli/chunk-jtb45atp.js.map +14 -0
  55. package/dist/cli/chunk-m3p3wahd.js +117 -0
  56. package/dist/cli/chunk-m3p3wahd.js.map +10 -0
  57. package/dist/cli/chunk-n0y172hf.js +387 -0
  58. package/dist/cli/chunk-n0y172hf.js.map +12 -0
  59. package/dist/cli/chunk-n4qjabmt.js +1062 -0
  60. package/dist/cli/chunk-n4qjabmt.js.map +25 -0
  61. package/dist/cli/chunk-nyqzjdhj.js +111 -0
  62. package/dist/cli/chunk-nyqzjdhj.js.map +11 -0
  63. package/dist/cli/chunk-pxj10x8y.js +35 -0
  64. package/dist/cli/chunk-pxj10x8y.js.map +10 -0
  65. package/dist/cli/chunk-s4jn7f1q.js +54 -0
  66. package/dist/cli/chunk-s4jn7f1q.js.map +10 -0
  67. package/dist/cli/chunk-s4k1pnvf.js +81 -0
  68. package/dist/cli/chunk-s4k1pnvf.js.map +10 -0
  69. package/dist/cli/chunk-s5e5jt53.js +227 -0
  70. package/dist/cli/chunk-s5e5jt53.js.map +11 -0
  71. package/dist/cli/chunk-sbdqrjbb.js +81 -0
  72. package/dist/cli/chunk-sbdqrjbb.js.map +10 -0
  73. package/dist/cli/chunk-tc89yh2r.js +136 -0
  74. package/dist/cli/chunk-tc89yh2r.js.map +10 -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-vv237fp3.js +1002 -0
  78. package/dist/cli/chunk-vv237fp3.js.map +13 -0
  79. package/dist/cli/chunk-vv3f8mb6.js +5314 -0
  80. package/dist/cli/chunk-vv3f8mb6.js.map +58 -0
  81. package/dist/cli/chunk-vxv4x1n8.js +17 -0
  82. package/dist/cli/chunk-vxv4x1n8.js.map +10 -0
  83. package/dist/cli/chunk-wb067mv3.js +758 -0
  84. package/dist/cli/chunk-wb067mv3.js.map +13 -0
  85. package/dist/cli/chunk-wd27zjcz.js +60 -0
  86. package/dist/cli/chunk-wd27zjcz.js.map +10 -0
  87. package/dist/cli/chunk-wkq5tbtq.js +1141 -0
  88. package/dist/cli/chunk-wkq5tbtq.js.map +19 -0
  89. package/dist/cli/chunk-x1vrdjyk.js +1967 -0
  90. package/dist/cli/chunk-x1vrdjyk.js.map +34 -0
  91. package/dist/cli/chunk-x66c5yjn.js +23 -0
  92. package/dist/cli/chunk-x66c5yjn.js.map +10 -0
  93. package/dist/cli/index.js +55 -27587
  94. package/dist/cli/index.js.map +5 -243
  95. package/dist/types/ai/ask-context.d.ts +26 -0
  96. package/dist/types/core/code-fences.d.ts +11 -0
  97. package/dist/types/core/config-input.d.ts +10 -0
  98. package/dist/types/core/package-root.d.ts +1 -1
  99. package/dist/types/core/schema.d.ts +74 -1
  100. package/docs/02-deployment.mdx +1 -1
  101. package/docs/configuration/analytics.mdx +21 -2
  102. package/docs/configuration/ask-ai.mdx +1 -1
  103. package/docs/configuration/customization.mdx +2 -9
  104. package/docs/content/syntax.mdx +1 -1
  105. package/docs/reference/cli.mdx +1 -1
  106. package/package.json +16 -14
  107. package/src/ai/api/handlers.ts +4 -7
  108. package/src/ai/api/paths.ts +8 -0
  109. package/src/ai/api/spec.ts +2 -1
  110. package/src/ai/ask-context.ts +378 -22
  111. package/src/astro/generate.ts +25 -29
  112. package/src/astro/include-hmr.ts +10 -13
  113. package/src/astro/include-refresh.ts +0 -0
  114. package/src/astro/index.ts +6 -1
  115. package/src/astro/integration.ts +269 -53
  116. package/src/astro/module-types.ts +74 -0
  117. package/src/astro/templates.ts +85 -97
  118. package/src/audit/image-size.ts +10 -8
  119. package/src/cli/command-meta.ts +77 -0
  120. package/src/cli/commands/add.ts +2 -4
  121. package/src/cli/commands/audit.ts +2 -4
  122. package/src/cli/commands/build.ts +42 -346
  123. package/src/cli/commands/check.ts +2 -4
  124. package/src/cli/commands/dev.ts +31 -42
  125. package/src/cli/commands/doctor.ts +2 -4
  126. package/src/cli/commands/eject.ts +3 -41
  127. package/src/cli/commands/eval.ts +2 -5
  128. package/src/cli/commands/init.ts +2 -4
  129. package/src/cli/commands/mcp-stdio.ts +2 -5
  130. package/src/cli/commands/preview.ts +3 -5
  131. package/src/cli/commands/sync.ts +2 -4
  132. package/src/cli/commands/translate.ts +2 -5
  133. package/src/cli/commands/validate.ts +2 -4
  134. package/src/cli/commands/version.ts +2 -4
  135. package/src/cli/eject-scripts.ts +0 -45
  136. package/src/cli/host-args.ts +16 -0
  137. package/src/cli/index.ts +84 -35
  138. package/src/cli/lazy-command.ts +47 -0
  139. package/src/components/content/GithubInfo.astro +4 -1
  140. package/src/components/content/mermaid-element.ts +8 -0
  141. package/src/components/layout/Analytics.astro +20 -1
  142. package/src/components/layout/PageLayout.astro +14 -3
  143. package/src/components/layout/ReferenceLayout.astro +15 -4
  144. package/src/components/layout/RootLayout.astro +15 -4
  145. package/src/components/layout/analytics-client.ts +2 -1
  146. package/src/components/layout/page-locale.ts +29 -0
  147. package/src/components/openapi/AsyncApiOperation.astro +5 -3
  148. package/src/components/openapi/Authorization.astro +4 -6
  149. package/src/components/openapi/Bindings.astro +2 -2
  150. package/src/components/openapi/Description.astro +109 -0
  151. package/src/components/openapi/GraphqlFieldsTable.astro +5 -7
  152. package/src/components/openapi/GraphqlOperation.astro +4 -3
  153. package/src/components/openapi/GraphqlType.astro +4 -6
  154. package/src/components/openapi/ParametersTable.astro +5 -7
  155. package/src/components/openapi/RequestBody.astro +2 -4
  156. package/src/components/openapi/Responses.astro +4 -3
  157. package/src/components/openapi/SchemaProperty.astro +11 -6
  158. package/src/components/openapi/description.ts +91 -0
  159. package/src/core/api-name.ts +18 -0
  160. package/src/core/code-fences.ts +48 -0
  161. package/src/core/config-input.ts +10 -0
  162. package/src/core/content-assets.ts +3 -7
  163. package/src/core/includes.ts +3 -7
  164. package/src/core/package-root.ts +1 -1
  165. package/src/core/schema.ts +27 -0
  166. package/src/core/sources/normalize.ts +2 -37
  167. package/src/core/sources/obsidian.ts +3 -2
  168. package/src/core/svg-dimensions.ts +97 -0
  169. package/src/core/version-cut.ts +2 -2
  170. package/src/deploy/artifacts.ts +370 -0
  171. package/src/deploy/cloudflare-negotiation.ts +97 -32
  172. package/src/deploy/function-bundle.ts +66 -20
  173. package/src/deploy/sitemap.ts +6 -0
  174. package/src/deploy/vercel-negotiation.ts +8 -30
  175. package/src/og/card.ts +6 -12
  176. package/src/openapi/render-mdx.ts +9 -5
  177. package/src/registry/eject.ts +0 -2
  178. package/src/theme/entry.ts +9 -2
@@ -1,63 +1,26 @@
1
1
  import { defineCommand } from "citty";
2
2
  import { relative } from "pathe";
3
3
 
4
- import { loadConfig } from "../../core/config.ts";
5
4
  import { eject } from "../../registry/eject.ts";
5
+ import { commandMeta } from "../command-meta.ts";
6
6
  import { refuseIfDevRunning } from "../dev-lock.ts";
7
- import {
8
- droppedArtifactNotices,
9
- updatePackageScripts,
10
- } from "../eject-scripts.ts";
7
+ import { updatePackageScripts } from "../eject-scripts.ts";
11
8
  import { commandsFor, detectProjectPackageManager } from "../init/scaffold.ts";
12
9
  import { logger } from "../log.ts";
13
10
 
14
- /**
15
- * Warn which `blume build` post-build artifacts the ejected app stops
16
- * producing. Printed both at the confirmation (so the decision is informed)
17
- * and after `--yes` (so a direct eject still sees it). No-op when the config
18
- * activates none of them.
19
- */
20
- const reportDroppedArtifacts = (notices: string[]): void => {
21
- if (notices.length === 0) {
22
- return;
23
- }
24
- logger.warn(
25
- [
26
- "The ejected build script runs plain `astro build`, which stops producing these `blume build` artifacts:",
27
- ...notices.map((notice) => ` - ${notice}`),
28
- ].join("\n")
29
- );
30
- };
31
-
32
11
  export const ejectCommand = defineCommand({
33
12
  args: {
34
13
  yes: { description: "Skip the confirmation prompt.", type: "boolean" },
35
14
  },
36
- meta: {
37
- description: "Promote the generated runtime into an owned Astro project.",
38
- name: "eject",
39
- },
15
+ meta: commandMeta.eject,
40
16
  async run({ args }) {
41
17
  const root = process.cwd();
42
18
  refuseIfDevRunning(root, "ejecting");
43
19
 
44
- // Config-aware drop list: only the artifacts this project actually
45
- // produces are mentioned (e.g. the Pagefind index only for
46
- // `search.provider: "pagefind"`).
47
- let notices: string[] = [];
48
- try {
49
- const { config } = await loadConfig(root);
50
- notices = droppedArtifactNotices(config);
51
- } catch {
52
- // A config that fails to load can't gate the notice; the eject itself
53
- // surfaces the load error.
54
- }
55
-
56
20
  if (!args.yes) {
57
21
  logger.warn(
58
22
  "Eject is one-way: it writes astro.config.mjs, src/, and (if absent) tsconfig.json, rewrites your package.json scripts, and removes .blume. An existing tsconfig.json is left untouched."
59
23
  );
60
- reportDroppedArtifacts(notices);
61
24
  logger.info("Re-run with --yes to proceed.");
62
25
  return;
63
26
  }
@@ -75,7 +38,6 @@ export const ejectCommand = defineCommand({
75
38
  for (const file of files) {
76
39
  process.stdout.write(` ${relative(root, file)}\n`);
77
40
  }
78
- reportDroppedArtifacts(notices);
79
41
  // Print run commands matching the project's package manager (lockfile
80
42
  // detection, since eject runs inside an existing project).
81
43
  const pm = await detectProjectPackageManager(root);
@@ -22,6 +22,7 @@ import {
22
22
  import { runEval } from "../../eval/run.ts";
23
23
  import type { EvalResult } from "../../eval/run.ts";
24
24
  import { EvalsFileError, loadEvalsFile } from "../../eval/schema.ts";
25
+ import { commandMeta } from "../command-meta.ts";
25
26
  import { reportInternalError } from "../internal-error.ts";
26
27
  import { flushStdout, logger } from "../log.ts";
27
28
 
@@ -183,11 +184,7 @@ export const evalCommand = defineCommand({
183
184
  type: "boolean",
184
185
  },
185
186
  },
186
- meta: {
187
- description:
188
- "Test the docs: an agent answers your questions using only the documentation.",
189
- name: "eval",
190
- },
187
+ meta: commandMeta.eval,
191
188
  async run({ args }) {
192
189
  const root = process.cwd();
193
190
  const { agent, threshold, timeoutS } = parseFlags(args);
@@ -4,6 +4,7 @@ import { resolve } from "pathe";
4
4
 
5
5
  import { ensureGitignore } from "../../core/gitignore.ts";
6
6
  import { eject } from "../../registry/eject.ts";
7
+ import { commandMeta } from "../command-meta.ts";
7
8
  import { updatePackageScripts } from "../eject-scripts.ts";
8
9
  import { collectAnswers } from "../init/questions.ts";
9
10
  import {
@@ -84,10 +85,7 @@ export const initCommand = defineCommand({
84
85
  type: "boolean",
85
86
  },
86
87
  },
87
- meta: {
88
- description: "Scaffold a minimal Blume project.",
89
- name: "init",
90
- },
88
+ meta: commandMeta.init,
91
89
  async run({ args }) {
92
90
  const cwd = process.cwd();
93
91
 
@@ -4,6 +4,7 @@ import { defineCommand } from "citty";
4
4
 
5
5
  import type { McpData } from "../../ai/mcp/data.ts";
6
6
  import { serveMcpStdio } from "../../ai/mcp/stdio.ts";
7
+ import { commandMeta } from "../command-meta.ts";
7
8
 
8
9
  export const mcpStdioCommand = defineCommand({
9
10
  args: {
@@ -13,11 +14,7 @@ export const mcpStdioCommand = defineCommand({
13
14
  type: "string",
14
15
  },
15
16
  },
16
- meta: {
17
- description:
18
- "Serve an MCP data snapshot over stdio (internal, used by `blume eval`).",
19
- name: "mcp-stdio",
20
- },
17
+ meta: commandMeta["mcp-stdio"],
21
18
  async run({ args }) {
22
19
  // stdout belongs to the JSON-RPC transport from here on; every diagnostic
23
20
  // must go to stderr or the MCP client chokes on the stray line.
@@ -7,18 +7,16 @@ import { join } from "pathe";
7
7
  import { loadConfig } from "../../core/config.ts";
8
8
  import { resolveProjectContext } from "../../core/project.ts";
9
9
  import { parsePort } from "../args.ts";
10
+ import { commandMeta } from "../command-meta.ts";
11
+ import { normalizeHost } from "../host-args.ts";
10
12
  import { logger } from "../log.ts";
11
- import { normalizeHost } from "./dev.ts";
12
13
 
13
14
  export const previewCommand = defineCommand({
14
15
  args: {
15
16
  host: { description: "Network host to bind.", type: "string" },
16
17
  port: { description: "Port to listen on.", type: "string" },
17
18
  },
18
- meta: {
19
- description: "Preview the last production build.",
20
- name: "preview",
21
- },
19
+ meta: commandMeta.preview,
22
20
  async run({ args }) {
23
21
  const root = process.cwd();
24
22
  const { config } = await loadConfig(root);
@@ -8,6 +8,7 @@ import {
8
8
  resolveProjectContext,
9
9
  resolveRuntimeDir,
10
10
  } from "../../core/project.ts";
11
+ import { commandMeta } from "../command-meta.ts";
11
12
  import { readDevLock } from "../dev-lock.ts";
12
13
  import { logger } from "../log.ts";
13
14
  import { prepareProject } from "../prepare.ts";
@@ -24,10 +25,7 @@ export const syncCommand = defineCommand({
24
25
  },
25
26
  strict: { description: "Fail on diagnostics.", type: "boolean" },
26
27
  },
27
- meta: {
28
- description: "Re-fetch remote content sources and regenerate the runtime.",
29
- name: "sync",
30
- },
28
+ meta: commandMeta.sync,
31
29
  async run({ args }) {
32
30
  const root = process.cwd();
33
31
 
@@ -26,6 +26,7 @@ import {
26
26
  } from "../../translate/report.ts";
27
27
  import { runTranslate } from "../../translate/run.ts";
28
28
  import { computeWorkList } from "../../translate/work-list.ts";
29
+ import { commandMeta } from "../command-meta.ts";
29
30
  import { reportInternalError } from "../internal-error.ts";
30
31
  import { flushStdout, logger } from "../log.ts";
31
32
 
@@ -181,11 +182,7 @@ export const translateCommand = defineCommand({
181
182
  type: "string",
182
183
  },
183
184
  },
184
- meta: {
185
- description:
186
- "Translate docs into the configured locales with a local agent CLI.",
187
- name: "translate",
188
- },
185
+ meta: commandMeta.translate,
189
186
  async run({ args }) {
190
187
  const root = process.cwd();
191
188
  const { agent, concurrency, timeoutS } = parseFlags(args);
@@ -13,6 +13,7 @@ import { validateLinks } from "../../core/links.ts";
13
13
  import { buildManifest } from "../../core/manifest.ts";
14
14
  import { scanProject } from "../../core/project-graph.ts";
15
15
  import type { Diagnostic } from "../../core/types.ts";
16
+ import { commandMeta } from "../command-meta.ts";
16
17
  import { reportInternalError } from "../internal-error.ts";
17
18
  import {
18
19
  flushStdout,
@@ -36,10 +37,7 @@ export const validateCommand = defineCommand({
36
37
  type: "boolean",
37
38
  },
38
39
  },
39
- meta: {
40
- description: "Validate internal, anchor, asset, and external links.",
41
- name: "validate",
42
- },
40
+ meta: commandMeta.validate,
43
41
  async run({ args }) {
44
42
  const root = process.cwd();
45
43
  const diagnostics: Diagnostic[] = [];
@@ -3,6 +3,7 @@ import { defineCommand } from "citty";
3
3
  import { loadConfig } from "../../core/config.ts";
4
4
  import { BlumeError } from "../../core/diagnostics.ts";
5
5
  import { CutError, cutVersion } from "../../core/version-cut.ts";
6
+ import { commandMeta } from "../command-meta.ts";
6
7
  import { reportInternalError } from "../internal-error.ts";
7
8
  import { logger } from "../log.ts";
8
9
 
@@ -18,10 +19,7 @@ export const versionCommand = defineCommand({
18
19
  type: "positional",
19
20
  },
20
21
  },
21
- meta: {
22
- description: "Freeze the current docs as an archived version.",
23
- name: "version",
24
- },
22
+ meta: commandMeta.version,
25
23
  async run({ args }) {
26
24
  const root = process.cwd();
27
25
 
@@ -2,9 +2,6 @@ import { readFile, writeFile } from "node:fs/promises";
2
2
 
3
3
  import { join } from "pathe";
4
4
 
5
- import type { ResolvedConfig } from "../core/schema.ts";
6
- import { searchProviderMeta } from "../search/providers.ts";
7
-
8
5
  /** A JSON value, as `JSON.parse` of a manifest can return. */
9
6
  type JsonValue =
10
7
  | string
@@ -20,48 +17,6 @@ interface PackageManifest {
20
17
  scripts?: Record<string, string>;
21
18
  }
22
19
 
23
- /**
24
- * The `blume build`-only artifacts this project's config actually produces, as
25
- * notice lines for the eject command. After an eject the build script runs
26
- * plain `astro build`, which emits none of them — printing the config-aware
27
- * list makes the loss explicit instead of silent (a Pagefind site would
28
- * otherwise eject into a build whose search fails at runtime). Each gate
29
- * mirrors the artifact's producer in `blume build` (cli/commands/build.ts).
30
- */
31
- export const droppedArtifactNotices = (config: ResolvedConfig): string[] => {
32
- const notices: string[] = [];
33
- if (config.search.provider === "pagefind") {
34
- notices.push(
35
- 'the Pagefind search index — the search UI loads it from the built site, so search will break in production. Add a post-build step: `"build": "astro build && pagefind --site dist"` (with `pagefind` installed as a devDependency).'
36
- );
37
- }
38
- if (searchProviderMeta(config.search.provider).syncs) {
39
- notices.push(
40
- `the hosted ${config.search.provider} index sync — new and updated pages stop being pushed; re-upload your search records after each build with the provider's API or CLI.`
41
- );
42
- }
43
- if (config.ai.llmsTxt.enabled) {
44
- notices.push("llms.txt and llms-full.txt");
45
- }
46
- if (config.deployment.site && config.seo.sitemap) {
47
- notices.push(
48
- "sitemap.xml — recreate it with the @astrojs/sitemap integration."
49
- );
50
- }
51
- if (config.seo.robots) {
52
- notices.push("robots.txt — recreate it as a public/robots.txt file.");
53
- }
54
- if (config.seo.agentReadability) {
55
- notices.push("agent-readability.json");
56
- }
57
- if (config.redirects.length > 0 && config.deployment.output === "static") {
58
- notices.push(
59
- "the platform redirect files (_redirects, vercel.json) — your redirects still work as Astro-generated meta-refresh pages."
60
- );
61
- }
62
- return notices;
63
- };
64
-
65
20
  /**
66
21
  * Rewrite the project's package.json scripts to run Astro directly. After an
67
22
  * eject the Blume CLI no longer manages the runtime, so scaffolded scripts like
@@ -16,3 +16,19 @@ export const normalizeHostArgs = (rawArgs: readonly string[]): string[] =>
16
16
  const next = rawArgs[index + 1];
17
17
  return next === undefined || next.startsWith("-") ? "--host=" : arg;
18
18
  });
19
+
20
+ /**
21
+ * Resolve a `--host` flag value into what Astro/Vite's `server.host` expects.
22
+ * citty has no mixed string/boolean arg type, so `host` is declared as a
23
+ * string and a bare `--host` parses as `""` (`normalizeHostArgs` rewrites it
24
+ * to `--host=` first) — Node would bind all interfaces for `""`, but Vite's
25
+ * `resolveHostname` treats it as a literal hostname and prints malformed URLs
26
+ * like `http://:4321/`. Match Astro's own `--host` semantics instead: bare
27
+ * flag → `true` (bind all interfaces), `--host 10.0.0.1` → that address,
28
+ * absent → `false` (localhost only).
29
+ *
30
+ * Lives beside `normalizeHostArgs` rather than in `commands/dev.ts` so
31
+ * `preview` can share it without importing the whole dev command graph.
32
+ */
33
+ export const normalizeHost = (host?: string): boolean | string =>
34
+ host === "" ? true : (host ?? false);
package/src/cli/index.ts CHANGED
@@ -1,24 +1,18 @@
1
1
  import { defineCommand, runMain } from "citty";
2
2
 
3
3
  import { getBlumeVersion } from "../core/version.ts";
4
- import { addCommand } from "./commands/add.ts";
5
- import { auditCommand } from "./commands/audit.ts";
6
- import { buildCommand } from "./commands/build.ts";
7
- import { checkCommand } from "./commands/check.ts";
8
- import { devCommand } from "./commands/dev.ts";
9
- import { doctorCommand } from "./commands/doctor.ts";
10
- import { ejectCommand } from "./commands/eject.ts";
11
- import { evalCommand } from "./commands/eval.ts";
12
- import { initCommand } from "./commands/init.ts";
13
- import { mcpStdioCommand } from "./commands/mcp-stdio.ts";
14
- import { previewCommand } from "./commands/preview.ts";
15
- import { syncCommand } from "./commands/sync.ts";
16
- import { translateCommand } from "./commands/translate.ts";
17
- import { validateCommand } from "./commands/validate.ts";
18
- import { versionCommand } from "./commands/version.ts";
4
+ import { commandMeta } from "./command-meta.ts";
19
5
  import { loadEnvFiles } from "./env.ts";
20
6
  import { normalizeHostArgs } from "./host-args.ts";
21
7
  import { reportInternalError } from "./internal-error.ts";
8
+ import { lazyCommand } from "./lazy-command.ts";
9
+ // consola reads CONSOLA_LEVEL / NODE_ENV / TEST / CI / DEBUG when its module
10
+ // evaluates. Import the logger here, ahead of `loadEnvFiles`, so those values
11
+ // come from the real environment — never from a project `.env`, which would
12
+ // otherwise silence or restyle every command's output. The commands are loaded
13
+ // lazily below, so without this line consola would first evaluate after `.env`
14
+ // had been applied.
15
+ import "./log.ts";
22
16
 
23
17
  const main = defineCommand({
24
18
  meta: {
@@ -27,31 +21,86 @@ const main = defineCommand({
27
21
  version: getBlumeVersion(),
28
22
  },
29
23
  subCommands: {
30
- add: addCommand,
31
- audit: auditCommand,
32
- build: buildCommand,
33
- check: checkCommand,
34
- dev: devCommand,
35
- doctor: doctorCommand,
36
- eject: ejectCommand,
37
- eval: evalCommand,
38
- init: initCommand,
39
- "mcp-stdio": mcpStdioCommand,
40
- preview: previewCommand,
41
- sync: syncCommand,
42
- translate: translateCommand,
43
- validate: validateCommand,
44
- version: versionCommand,
24
+ add: lazyCommand(
25
+ commandMeta.add,
26
+ () => import("./commands/add.ts"),
27
+ "addCommand"
28
+ ),
29
+ audit: lazyCommand(
30
+ commandMeta.audit,
31
+ () => import("./commands/audit.ts"),
32
+ "auditCommand"
33
+ ),
34
+ build: lazyCommand(
35
+ commandMeta.build,
36
+ () => import("./commands/build.ts"),
37
+ "buildCommand"
38
+ ),
39
+ check: lazyCommand(
40
+ commandMeta.check,
41
+ () => import("./commands/check.ts"),
42
+ "checkCommand"
43
+ ),
44
+ dev: lazyCommand(
45
+ commandMeta.dev,
46
+ () => import("./commands/dev.ts"),
47
+ "devCommand"
48
+ ),
49
+ doctor: lazyCommand(
50
+ commandMeta.doctor,
51
+ () => import("./commands/doctor.ts"),
52
+ "doctorCommand"
53
+ ),
54
+ eject: lazyCommand(
55
+ commandMeta.eject,
56
+ () => import("./commands/eject.ts"),
57
+ "ejectCommand"
58
+ ),
59
+ eval: lazyCommand(
60
+ commandMeta.eval,
61
+ () => import("./commands/eval.ts"),
62
+ "evalCommand"
63
+ ),
64
+ init: lazyCommand(
65
+ commandMeta.init,
66
+ () => import("./commands/init.ts"),
67
+ "initCommand"
68
+ ),
69
+ "mcp-stdio": lazyCommand(
70
+ commandMeta["mcp-stdio"],
71
+ () => import("./commands/mcp-stdio.ts"),
72
+ "mcpStdioCommand"
73
+ ),
74
+ preview: lazyCommand(
75
+ commandMeta.preview,
76
+ () => import("./commands/preview.ts"),
77
+ "previewCommand"
78
+ ),
79
+ sync: lazyCommand(
80
+ commandMeta.sync,
81
+ () => import("./commands/sync.ts"),
82
+ "syncCommand"
83
+ ),
84
+ translate: lazyCommand(
85
+ commandMeta.translate,
86
+ () => import("./commands/translate.ts"),
87
+ "translateCommand"
88
+ ),
89
+ validate: lazyCommand(
90
+ commandMeta.validate,
91
+ () => import("./commands/validate.ts"),
92
+ "validateCommand"
93
+ ),
94
+ version: lazyCommand(
95
+ commandMeta.version,
96
+ () => import("./commands/version.ts"),
97
+ "versionCommand"
98
+ ),
45
99
  },
46
100
  });
47
101
 
48
- // Load `.env`/`.env.local` before any command runs so remote content sources
49
- // can read their tokens (e.g. `GITHUB_TOKEN`) during the content scan.
50
102
  loadEnvFiles(process.cwd());
51
103
 
52
- // Backstop for unexpected async failures that escape a command's own handling
53
- // (e.g. a rejected timer/watcher in `blume dev`), so even those report through
54
- // the stable internal-error contract rather than a bare stack trace.
55
104
  process.on("uncaughtException", (error) => {
56
105
  reportInternalError(error);
57
106
  process.exit(1);
@@ -0,0 +1,47 @@
1
+ import type { ArgsDef, CommandDef, CommandMeta } from "citty";
2
+
3
+ /**
4
+ * Wrap a command so its module is imported only when citty needs its `args`
5
+ * or runs it. `meta` stays static (from `command-meta.ts`), so rendering root
6
+ * usage, matching an unknown name, and `blume <cmd> --help` for *another*
7
+ * command never touch this one's dependency graph.
8
+ *
9
+ * `load` imports the command's module and `key` names its export, so the call
10
+ * site stays a plain `() => import("./commands/x.ts")`.
11
+ *
12
+ * Only `args`, `setup`, `run`, and `cleanup` are forwarded: no Blume command
13
+ * declares `default`, `plugins`, or nested `subCommands`.
14
+ */
15
+ export const lazyCommand = <Args extends ArgsDef, Key extends string>(
16
+ meta: CommandMeta,
17
+ load: () => Promise<Record<Key, CommandDef<Args>>>,
18
+ key: Key
19
+ ): CommandDef<Args> => {
20
+ const command = async () => {
21
+ const module = await load();
22
+ return module[key];
23
+ };
24
+ return {
25
+ args: async () => {
26
+ const { args } = await command();
27
+ // SAFETY: every Blume command passes `args` to `defineCommand` as a plain
28
+ // object literal (the "command registry" test checks it); citty's
29
+ // `Resolvable` widening is the only reason the type also admits a thunk
30
+ // or a promise here.
31
+ return args as Args;
32
+ },
33
+ cleanup: async (context) => {
34
+ const loaded = await command();
35
+ await loaded.cleanup?.(context);
36
+ },
37
+ meta,
38
+ run: async (context) => {
39
+ const loaded = await command();
40
+ await loaded.run?.(context);
41
+ },
42
+ setup: async (context) => {
43
+ const loaded = await command();
44
+ await loaded.setup?.(context);
45
+ },
46
+ };
47
+ };
@@ -3,6 +3,7 @@
3
3
  // at build time (no client JS). Pass `owner`/`repo`, or omit them to use the
4
4
  // repo from blume.config. A `GITHUB_TOKEN` env var lifts the API rate limit.
5
5
  // If the API is unreachable the card still renders, just without counts.
6
+ import { getSecret } from "astro:env/server";
6
7
  import { apiUrl, PUBLIC_HOST_URL } from "../../core/github.ts";
7
8
  import { resolveIcon } from "../../theme/icons.ts";
8
9
  import data from "blume:data";
@@ -38,7 +39,9 @@ interface Props {
38
39
  const configured = data.config.github;
39
40
  const owner = Astro.props.owner ?? configured?.owner;
40
41
  const repo = Astro.props.repo ?? configured?.repo;
41
- const token = Astro.props.token ?? process.env.GITHUB_TOKEN;
42
+ // Read through Astro's `getSecret` so the adapter's secret store is honored
43
+ // (the card renders at build time, where that is the environment).
44
+ const token = Astro.props.token ?? getSecret("GITHUB_TOKEN");
42
45
  // A `host` prop is reduced to its origin, as the config field is, so a
43
46
  // trailing slash or a path never lands mid-link.
44
47
  const host = Astro.props.host
@@ -42,6 +42,14 @@ class BlumeMermaid extends HTMLElement {
42
42
  const token = this.#renderToken;
43
43
  const mermaid = await loadMermaid();
44
44
  mermaid.initialize({
45
+ // Mermaid 12 defaults to the bundled ELK layout and the "neo" look,
46
+ // which re-lays out and restyles every existing diagram and pulls a
47
+ // ~1.4 MB ELK chunk onto any page with a flowchart. Pin the previous
48
+ // defaults so diagrams keep rendering as authored; a diagram opts
49
+ // into ELK or neo through its own front matter (`config: { layout:
50
+ // elk }`), which outranks these initialize() values.
51
+ layout: "dagre",
52
+ look: "classic",
45
53
  securityLevel: "strict",
46
54
  startOnLoad: false,
47
55
  theme: prefersDark() ? "dark" : "default",
@@ -3,7 +3,7 @@ import VercelAnalytics from "@vercel/analytics/astro";
3
3
 
4
4
  // Analytics scripts injected into <head>. PostHog and custom providers use the
5
5
  // inline-snippet house style (banner/theme/JSON-LD); Vercel uses its official
6
- // Astro component. Loads ONLY in production builds so `blume dev` stays clean
6
+ // Astro component; Cloudflare is its beacon script tag. Loads ONLY in production builds so `blume dev` stays clean
7
7
  // and local traffic never reaches your analytics.
8
8
  interface AnalyticsScript {
9
9
  attributes?: Record<string, string>;
@@ -14,6 +14,7 @@ interface AnalyticsScript {
14
14
 
15
15
  interface Props {
16
16
  analytics?: {
17
+ cloudflare?: { token: string };
17
18
  posthog?: { host?: string; key: string };
18
19
  scripts?: AnalyticsScript[];
19
20
  vercel?: boolean;
@@ -45,6 +46,14 @@ const posthogSnippet = posthog
45
46
  ? `${POSTHOG_LOADER}posthog.init(${JSON.stringify(posthog.key)},{api_host:${JSON.stringify(posthog.host ?? "https://us.i.posthog.com")}});${POSTHOG_SPA_PAGEVIEWS}`
46
47
  : null;
47
48
 
49
+ // Cloudflare Web Analytics (manual setup): the beacon script keyed by the
50
+ // site token, exactly as the dashboard's snippet renders it. It tracks
51
+ // history changes itself, so client-router navigations need no extra hook.
52
+ const cloudflareBeacon =
53
+ enabled && analytics?.cloudflare
54
+ ? JSON.stringify({ token: analytics.cloudflare.token })
55
+ : null;
56
+
48
57
  // Custom scripts: any other provider. Each is either external (`src`) or inline
49
58
  // (`content`). Explicit fields win over spread `attributes`.
50
59
  const customScripts = enabled ? (analytics?.scripts ?? []) : [];
@@ -62,6 +71,16 @@ const inlineScripts = customScripts
62
71
  ---
63
72
 
64
73
  {vercelEnabled && <VercelAnalytics />}
74
+ {
75
+ cloudflareBeacon && (
76
+ <script
77
+ is:inline
78
+ defer
79
+ src="https://static.cloudflareinsights.com/beacon.min.js"
80
+ data-cf-beacon={cloudflareBeacon}
81
+ />
82
+ )
83
+ }
65
84
  {posthogSnippet && <script is:inline set:html={posthogSnippet} />}
66
85
  {externalScripts.map((attrs) => <script is:inline {...attrs} />)}
67
86
  {
@@ -35,6 +35,7 @@ import {
35
35
  import { buildStructuredData } from "../../seo/jsonld.ts";
36
36
  import { normalizeXHandle } from "../../seo/x-handle.ts";
37
37
  import { withBase } from "../islands/base-path.ts";
38
+ import { pageDirection, pageLocale } from "./page-locale.ts";
38
39
  import { searchLocaleFor } from "./search-locale.ts";
39
40
  import "blume:theme";
40
41
  import { ClientRouter } from "astro:transitions";
@@ -108,7 +109,11 @@ interface Props {
108
109
  */
109
110
  x?: { creator?: string; handle?: string };
110
111
  noindex?: boolean;
111
- /** Active locale + direction for `<html lang>`/`<html dir>`. */
112
+ /**
113
+ * Active locale + direction for `<html lang>`/`<html dir>`. Default to the
114
+ * locale Astro resolved from the URL (`Astro.currentLocale`), then the site
115
+ * default, and to that locale's configured direction.
116
+ */
112
117
  locale?: string;
113
118
  dir?: "ltr" | "rtl";
114
119
  /** Resolved UI dictionary; English baseline when omitted. */
@@ -145,13 +150,19 @@ const {
145
150
  structuredDataEnabled,
146
151
  x,
147
152
  noindex,
148
- locale = "en",
149
- dir = "ltr",
153
+ locale: localeProp,
154
+ dir: dirProp,
150
155
  ui,
151
156
  localeSwitch,
152
157
  clientData,
153
158
  } = Astro.props;
154
159
 
160
+ // The locale and direction: the page's own values when it passes them (the
161
+ // content catch-all always does), else what Astro's i18n routing resolved for
162
+ // this URL, else the site default (see `page-locale.ts`).
163
+ const locale = pageLocale(data.config.i18n, localeProp, Astro.currentLocale);
164
+ const dir = dirProp ?? pageDirection(data.config.i18n, locale);
165
+
155
166
  const clientDataJson = clientData
156
167
  ? JSON.stringify(clientData).replaceAll("<", "\\u003c")
157
168
  : null;