blume 1.4.3 → 1.5.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 (204) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +16 -12
  3. package/dist/cli/index.js +1784 -633
  4. package/dist/cli/index.js.map +111 -106
  5. package/dist/types/ai/component-markdown.d.ts +14 -4
  6. package/dist/types/core/config-input.d.ts +80 -28
  7. package/dist/types/core/config.d.ts +2 -1
  8. package/dist/types/core/data.d.ts +19 -3
  9. package/dist/types/core/diagnostics.d.ts +5 -1
  10. package/dist/types/core/i18n-ui.d.ts +12 -0
  11. package/dist/types/core/schema.d.ts +112 -15
  12. package/dist/types/core/sources/types.d.ts +3 -1
  13. package/dist/types/core/standard-schema.d.ts +7 -3
  14. package/dist/types/core/types.d.ts +43 -2
  15. package/dist/types/core/ui-packs/index.d.ts +9 -1
  16. package/dist/types/openapi/references.d.ts +6 -5
  17. package/dist/types/seo/x-handle.d.ts +3 -2
  18. package/dist/types/theme/fonts.d.ts +11 -2
  19. package/docs/advanced/api-reference.mdx +8 -6
  20. package/docs/advanced/custom-pages.mdx +5 -1
  21. package/docs/configuration/index.mdx +1 -1
  22. package/docs/configuration/search.mdx +2 -0
  23. package/docs/configuration/seo.mdx +1 -1
  24. package/docs/configuration/theming.mdx +4 -2
  25. package/docs/content/i18n.mdx +1 -1
  26. package/docs/content/meta.mdx +2 -1
  27. package/docs/content/meta.ts +1 -0
  28. package/docs/content/navigation.mdx +35 -1
  29. package/docs/content/versioning.mdx +106 -0
  30. package/docs/reference/cli.mdx +2 -1
  31. package/docs/reference/frontmatter.mdx +3 -0
  32. package/package.json +3 -1
  33. package/skills/blume-migrate/SKILL.md +2 -2
  34. package/skills/blume-migrate/references/docusaurus.md +1 -1
  35. package/skills/blume-migrate/references/fumadocs.md +1 -1
  36. package/skills/blume-migrate/references/mintlify.md +1 -1
  37. package/src/ai/agent-readability.ts +37 -10
  38. package/src/ai/ask-context.ts +5 -1
  39. package/src/ai/ask.ts +10 -1
  40. package/src/ai/component-markdown.ts +80 -43
  41. package/src/ai/llms.ts +40 -16
  42. package/src/ai/mcp/data.ts +48 -12
  43. package/src/ai/mcp/discovery.ts +28 -11
  44. package/src/ai/mcp/server.ts +183 -38
  45. package/src/ai/mcp/tools.ts +3 -3
  46. package/src/ai/skills.ts +32 -9
  47. package/src/ai/visibility.ts +2 -2
  48. package/src/astro/component-slots.ts +2 -0
  49. package/src/astro/examples.ts +6 -2
  50. package/src/astro/generate.ts +64 -34
  51. package/src/astro/integration.ts +13 -2
  52. package/src/astro/islands.ts +16 -9
  53. package/src/astro/templates.ts +181 -40
  54. package/src/audit/agent.ts +2 -2
  55. package/src/audit/checks/content.ts +26 -11
  56. package/src/audit/checks/dns-aid.ts +3 -0
  57. package/src/audit/checks/indexability.ts +24 -6
  58. package/src/audit/checks/llms.ts +9 -4
  59. package/src/audit/checks/network.ts +2 -0
  60. package/src/audit/checks/social.ts +18 -10
  61. package/src/audit/crawl.ts +37 -9
  62. package/src/audit/report.ts +20 -19
  63. package/src/audit/run.ts +5 -2
  64. package/src/audit/snapshot.ts +2 -4
  65. package/src/audit/types.ts +25 -3
  66. package/src/blume-modules.d.ts +5 -1
  67. package/src/cli/commands/audit.ts +9 -4
  68. package/src/cli/commands/build.ts +15 -9
  69. package/src/cli/commands/dev.ts +2 -0
  70. package/src/cli/commands/doctor.ts +2 -0
  71. package/src/cli/commands/eval.ts +7 -3
  72. package/src/cli/commands/init.ts +9 -9
  73. package/src/cli/commands/mcp-stdio.ts +3 -0
  74. package/src/cli/commands/translate.ts +14 -3
  75. package/src/cli/commands/version.ts +85 -0
  76. package/src/cli/dev-lock.ts +31 -10
  77. package/src/cli/eject-scripts.ts +17 -2
  78. package/src/cli/index.ts +2 -0
  79. package/src/cli/init/questions.ts +1 -1
  80. package/src/cli/init/scaffold.ts +22 -15
  81. package/src/cli/internal-error.ts +1 -0
  82. package/src/components/content/auto-type-table.ts +3 -0
  83. package/src/components/content/diff.ts +9 -5
  84. package/src/components/content/github-info.ts +2 -0
  85. package/src/components/islands/ask-ai.tsx +33 -25
  86. package/src/components/islands/hooks.ts +5 -1
  87. package/src/components/islands/webmcp.ts +49 -12
  88. package/src/components/layout/Fonts.astro +23 -3
  89. package/src/components/layout/Header.astro +25 -1
  90. package/src/components/layout/NavSelector.astro +11 -2
  91. package/src/components/layout/NavTree.astro +4 -2
  92. package/src/components/layout/PageLayout.astro +72 -3
  93. package/src/components/layout/ReferenceLayout.astro +2 -1
  94. package/src/components/layout/RootLayout.astro +20 -1
  95. package/src/components/layout/Search.astro +77 -13
  96. package/src/components/layout/VersionBanner.astro +39 -0
  97. package/src/components/layout/analytics-client.ts +8 -5
  98. package/src/components/layout/hydration-hint.ts +1 -1
  99. package/src/components/layout/nav-utils.ts +1 -4
  100. package/src/components/layout/overrides.ts +25 -12
  101. package/src/components/layout/search/algolia.ts +18 -5
  102. package/src/components/layout/search/endpoint.ts +3 -0
  103. package/src/components/layout/search/flexsearch.ts +23 -7
  104. package/src/components/layout/search/orama-cloud.ts +1 -1
  105. package/src/components/layout/search/orama.ts +4 -1
  106. package/src/components/layout/search/pagefind.ts +2 -0
  107. package/src/components/layout/search/types.ts +13 -1
  108. package/src/components/layout/search/typesense.ts +19 -3
  109. package/src/components/openapi/ApiOverview.astro +32 -6
  110. package/src/components/openapi/AsyncApiOperation.astro +237 -0
  111. package/src/components/openapi/Bindings.astro +89 -0
  112. package/src/components/openapi/MethodBadge.astro +3 -0
  113. package/src/components/openapi/Operation.astro +7 -2
  114. package/src/components/openapi/PanelTabs.astro +131 -0
  115. package/src/components/openapi/ParametersTable.astro +2 -0
  116. package/src/components/openapi/RequestPanel.astro +12 -119
  117. package/src/components/openapi/async-snippets.ts +174 -0
  118. package/src/components/openapi/async.ts +348 -0
  119. package/src/components/openapi/helpers.ts +52 -20
  120. package/src/components/openapi/security.ts +102 -29
  121. package/src/components/openapi/snippets.ts +11 -11
  122. package/src/core/component-overrides.ts +28 -23
  123. package/src/core/config-input.ts +89 -28
  124. package/src/core/config.ts +20 -7
  125. package/src/core/content.ts +3 -1
  126. package/src/core/data.ts +19 -3
  127. package/src/core/define-components.ts +5 -0
  128. package/src/core/diagnostics.ts +46 -38
  129. package/src/core/frontmatter.ts +33 -7
  130. package/src/core/graph.ts +137 -53
  131. package/src/core/i18n-ui.ts +15 -0
  132. package/src/core/i18n.ts +16 -8
  133. package/src/core/last-modified.ts +49 -0
  134. package/src/core/load-module.ts +1 -0
  135. package/src/core/manifest.ts +92 -3
  136. package/src/core/meta.ts +44 -14
  137. package/src/core/nav-diagnostics.ts +3 -3
  138. package/src/core/navigation.ts +247 -67
  139. package/src/core/project-graph.ts +26 -3
  140. package/src/core/schema.ts +214 -68
  141. package/src/core/sources/assets.ts +2 -0
  142. package/src/core/sources/cache.ts +6 -0
  143. package/src/core/sources/github-releases.ts +39 -31
  144. package/src/core/sources/mdx-remote.ts +4 -0
  145. package/src/core/sources/normalize.ts +67 -20
  146. package/src/core/sources/notion.ts +49 -17
  147. package/src/core/sources/portable-text.ts +32 -11
  148. package/src/core/sources/sanity.ts +68 -14
  149. package/src/core/sources/types.ts +4 -0
  150. package/src/core/sources/watch.ts +1 -1
  151. package/src/core/standard-schema.ts +9 -3
  152. package/src/core/text-width.ts +26 -0
  153. package/src/core/tsconfig-aliases.ts +9 -5
  154. package/src/core/types.ts +45 -2
  155. package/src/core/ui-packs/index.ts +9 -1
  156. package/src/core/version-cut.ts +301 -0
  157. package/src/core/version.ts +2 -0
  158. package/src/core/versions.ts +170 -0
  159. package/src/deploy/adapter-output.ts +5 -2
  160. package/src/deploy/cloudflare-negotiation.ts +25 -10
  161. package/src/deploy/sitemap.ts +33 -1
  162. package/src/deploy/vercel-negotiation.ts +45 -18
  163. package/src/eval/report.ts +4 -4
  164. package/src/eval/run.ts +2 -2
  165. package/src/eval/schema.ts +1 -1
  166. package/src/markdown/base-links.ts +6 -6
  167. package/src/markdown/directives.ts +7 -1
  168. package/src/markdown/heading-anchors.ts +17 -6
  169. package/src/markdown/index.ts +73 -24
  170. package/src/markdown/inline-code.ts +14 -2
  171. package/src/markdown/language-icon.ts +6 -2
  172. package/src/markdown/mdast.ts +18 -4
  173. package/src/markdown/package-commands.ts +6 -8
  174. package/src/markdown/table-wrap.ts +4 -1
  175. package/src/markdown/twoslash.ts +2 -0
  176. package/src/og/card.ts +33 -12
  177. package/src/og/derive.ts +43 -27
  178. package/src/openapi/asyncapi.ts +366 -0
  179. package/src/openapi/model.ts +126 -57
  180. package/src/openapi/parse.ts +97 -5
  181. package/src/openapi/references.ts +12 -10
  182. package/src/openapi/render-mdx.ts +73 -34
  183. package/src/openapi/scalar.ts +6 -8
  184. package/src/openapi/source.ts +98 -28
  185. package/src/registry/eject.ts +7 -2
  186. package/src/search/documents.ts +25 -5
  187. package/src/search/facets.ts +7 -5
  188. package/src/search/orama-index.ts +66 -20
  189. package/src/search/popular.ts +10 -5
  190. package/src/search/providers.ts +2 -2
  191. package/src/search/sync/index.ts +2 -0
  192. package/src/search/sync/typesense.ts +4 -2
  193. package/src/seo/jsonld.ts +24 -6
  194. package/src/seo/x-handle.ts +8 -3
  195. package/src/theme/chrome-icons.ts +7 -2
  196. package/src/theme/entry.ts +24 -2
  197. package/src/theme/fonts.ts +83 -7
  198. package/src/theme/icons.ts +4 -2
  199. package/src/theme/palette.ts +22 -14
  200. package/src/translate/meta.ts +15 -6
  201. package/src/translate/report.ts +9 -5
  202. package/src/translate/run.ts +10 -4
  203. package/src/translate/validate.ts +52 -17
  204. package/src/translate/work-list.ts +0 -0
@@ -8,9 +8,39 @@ import type {
8
8
  ProjectContext,
9
9
  RouteAlternate,
10
10
  RouteManifestEntry,
11
+ VersionAlternate,
11
12
  } from "./types.ts";
12
13
  import { getBlumeVersion } from "./version.ts";
13
14
 
15
+ /**
16
+ * Key for the same logical page across versions within one locale: the
17
+ * version- and locale-agnostic route plus the locale code. NUL never appears
18
+ * in either part, so the join is unambiguous.
19
+ */
20
+ const versionAlternateKey = (versionKey: string, locale: string): string =>
21
+ `${versionKey}\u0000${locale}`;
22
+
23
+ /**
24
+ * Get-or-create the shared alternate list for a (versionKey, locale) pair.
25
+ * Lists are attached to route entries by reference and mutated as fallback
26
+ * routes materialize, then sorted once at the end — every holder sees the
27
+ * final list.
28
+ */
29
+ const versionAlternatesFor = (
30
+ byKey: Map<string, VersionAlternate[]>,
31
+ versionKey: string,
32
+ locale: string
33
+ ): VersionAlternate[] => {
34
+ const key = versionAlternateKey(versionKey, locale);
35
+ const existing = byKey.get(key);
36
+ if (existing) {
37
+ return existing;
38
+ }
39
+ const list: VersionAlternate[] = [];
40
+ byKey.set(key, list);
41
+ return list;
42
+ };
43
+
14
44
  /** The current manifest schema version. */
15
45
  export const MANIFEST_VERSION = 1;
16
46
 
@@ -37,7 +67,8 @@ const buildFallbackRoutes = (
37
67
  graph: ContentGraph,
38
68
  i18n: NonNullable<ResolvedConfig["i18n"]>,
39
69
  alternatesByKey: Map<string, RouteAlternate[]>,
40
- basePath: string
70
+ basePath: string,
71
+ versionAlternatesByKey: Map<string, VersionAlternate[]> | undefined
41
72
  ): RouteManifestEntry[] => {
42
73
  const fallback = resolveFallbackLocale(i18n);
43
74
  if (!fallback) {
@@ -62,6 +93,17 @@ const buildFallbackRoutes = (
62
93
  if (present.has(key)) {
63
94
  continue;
64
95
  }
96
+ const path = withBasePath(basePath, localizeRoute(key, code, i18n));
97
+ // A fallback route is a real prerendered page, so it registers as a
98
+ // version alternate too — the switcher on a sibling version's page
99
+ // lands here instead of bouncing to the version root. Its own path is
100
+ // recorded (not the fallback source's), keeping the target in-locale.
101
+ const versionAlternates: VersionAlternate[] = versionAlternatesByKey
102
+ ? versionAlternatesFor(versionAlternatesByKey, source.versionKey, code)
103
+ : [];
104
+ if (versionAlternatesByKey) {
105
+ versionAlternates.push({ path, version: source.version });
106
+ }
65
107
  routes.push({
66
108
  alternates: alternatesByKey.get(key) ?? [],
67
109
  collection: source.collection ?? "docs",
@@ -75,10 +117,12 @@ const buildFallbackRoutes = (
75
117
  indexable: false,
76
118
  lastModified: source.lastModified,
77
119
  locale: code,
78
- path: withBasePath(basePath, localizeRoute(key, code, i18n)),
120
+ path,
79
121
  source: source.source,
80
122
  sourcePath: source.sourcePath,
81
123
  title: source.title,
124
+ version: source.version,
125
+ versionAlternates,
82
126
  });
83
127
  }
84
128
  }
@@ -106,6 +150,23 @@ export const buildManifest = (options: {
106
150
  }
107
151
  }
108
152
 
153
+ // The same logical page across versions, within each locale — for the
154
+ // version switcher and the canonical-to-latest lookup. Built only under
155
+ // versioning; lists are shared by reference and finalized (fallback routes
156
+ // appended, then sorted) before the manifest is returned.
157
+ const versionAlternatesByKey = config.versions
158
+ ? new Map<string, VersionAlternate[]>()
159
+ : undefined;
160
+ if (versionAlternatesByKey) {
161
+ for (const page of graph.pages) {
162
+ versionAlternatesFor(
163
+ versionAlternatesByKey,
164
+ page.versionKey,
165
+ page.locale
166
+ ).push({ path: page.route, version: page.version });
167
+ }
168
+ }
169
+
109
170
  const routes: RouteManifestEntry[] = graph.pages.map((page) => ({
110
171
  alternates: alternatesByKey.get(page.translationKey) ?? [],
111
172
  collection: page.collection ?? "docs",
@@ -122,14 +183,42 @@ export const buildManifest = (options: {
122
183
  source: page.source,
123
184
  sourcePath: page.sourcePath,
124
185
  title: page.title,
186
+ version: page.version,
187
+ versionAlternates: versionAlternatesByKey
188
+ ? versionAlternatesFor(
189
+ versionAlternatesByKey,
190
+ page.versionKey,
191
+ page.locale
192
+ )
193
+ : [],
125
194
  }));
126
195
 
127
196
  if (i18n) {
128
197
  routes.push(
129
- ...buildFallbackRoutes(graph, i18n, alternatesByKey, config.basePath)
198
+ ...buildFallbackRoutes(
199
+ graph,
200
+ i18n,
201
+ alternatesByKey,
202
+ config.basePath,
203
+ versionAlternatesByKey
204
+ )
130
205
  );
131
206
  }
132
207
 
208
+ // Alternate lists read current-first, then archived versions in configured
209
+ // (switcher) order. Sorted once here — every route holding a list by
210
+ // reference sees the final ordering.
211
+ if (versionAlternatesByKey && config.versions) {
212
+ const rank = new Map<string, number>(
213
+ config.versions.archived.map((version, index) => [version.id, index + 1])
214
+ );
215
+ for (const list of versionAlternatesByKey.values()) {
216
+ list.sort(
217
+ (a, b) => (rank.get(a.version) ?? 0) - (rank.get(b.version) ?? 0)
218
+ );
219
+ }
220
+ }
221
+
133
222
  routes.sort((a, b) => a.path.localeCompare(b.path));
134
223
 
135
224
  return {
package/src/core/meta.ts CHANGED
@@ -17,9 +17,23 @@ const META_FILES = [
17
17
  "**/meta.$.mjs",
18
18
  ];
19
19
 
20
+ /**
21
+ * A meta module's default export before validation: `folderMetaSchema` parses
22
+ * it only after any factory is resolved, so it carries the loader's raw type.
23
+ */
24
+ type MetaModuleExport = Awaited<
25
+ ReturnType<ReturnType<typeof createModuleLoader>>
26
+ >;
27
+
28
+ /** A factory-style meta module default-exports a function returning the meta. */
29
+ const isMetaFactory = (
30
+ mod: MetaModuleExport
31
+ ): mod is () => MetaModuleExport | Promise<MetaModuleExport> =>
32
+ typeof mod === "function";
33
+
20
34
  /** Resolve a meta module's default export, calling it if it is a factory. */
21
- const resolveMeta = async (mod: unknown): Promise<unknown> =>
22
- typeof mod === "function" ? await (mod as () => unknown)() : mod;
35
+ const resolveMeta = async (mod: MetaModuleExport) =>
36
+ isMetaFactory(mod) ? await mod() : mod;
23
37
 
24
38
  /** A filesystem content source to scan for folder meta: its on-disk root and
25
39
  * optional route prefix. The prefix is folded into every key so meta lines up
@@ -58,18 +72,28 @@ const metaKeyFor = (prefix: string | undefined, dir: string): string => {
58
72
  * group path starts with the source prefix, so a locale directory found at a
59
73
  * source root is hoisted in front of the prefix (`docs/fr/guides/meta.ts` keys
60
74
  * to `fr/docs/guides`, not `docs/fr/guides`).
75
+ *
76
+ * `versionDirs` names the archived-version snapshot directories, which sit
77
+ * outermost on disk — a locale directory inside a snapshot is one level deeper.
78
+ * Both are hoisted, version first (`v1.0/fr/guides/meta.ts` keys to
79
+ * `v1.0/fr/<prefix>/guides`), matching navigation's version-aware meta prefix.
61
80
  */
62
81
  export const discoverFolderMeta = async (
63
82
  sources: string | FolderMetaSource[],
64
- options: { localeDirs?: readonly string[] } = {}
83
+ options: {
84
+ localeDirs?: readonly string[];
85
+ versionDirs?: readonly string[];
86
+ } = {}
65
87
  ): Promise<{
66
88
  meta: Map<string, FolderMeta>;
67
89
  shared: Map<string, FolderMeta>;
68
90
  diagnostics: Diagnostic[];
69
91
  }> => {
70
- const list: FolderMetaSource[] =
71
- typeof sources === "string" ? [{ root: sources }] : sources;
92
+ const list: FolderMetaSource[] = Array.isArray(sources)
93
+ ? sources
94
+ : [{ root: sources }];
72
95
  const localeDirs = new Set(options.localeDirs);
96
+ const versionDirs = new Set(options.versionDirs);
73
97
 
74
98
  const load = createModuleLoader();
75
99
  const meta = new Map<string, FolderMeta>();
@@ -103,6 +127,8 @@ export const discoverFolderMeta = async (
103
127
  value: await resolveMeta(await load(file)),
104
128
  };
105
129
  } catch (error) {
130
+ // SAFETY: jiti surfaces load/evaluate failures as Error
131
+ // instances, and only `message` is read downstream.
106
132
  return { error: error as Error, file, ok: false };
107
133
  }
108
134
  }
@@ -115,16 +141,20 @@ export const discoverFolderMeta = async (
115
141
  for (const { loaded, source } of perSource) {
116
142
  for (const entry of loaded) {
117
143
  const dir = relative(source.root, dirname(entry.file));
144
+ // A version snapshot dir is outermost, with a locale dir one level
145
+ // deeper; both are hoisted in front of the (prefixed) group path, in
146
+ // that order — the lookup key reads `version/locale/prefix/dir`.
118
147
  const [head, ...tail] = dir.split("/");
119
- // A locale directory sits between the source root and the folder, but the
120
- // lookup key carries the locale in front of the (prefixed) group path.
121
- const key =
122
- head && localeDirs.has(head)
123
- ? `${head}/${metaKeyFor(source.prefix, tail.join("/"))}`.replace(
124
- /\/$/u,
125
- ""
126
- )
127
- : metaKeyFor(source.prefix, dir);
148
+ const version = head && versionDirs.has(head) ? head : "";
149
+ const afterVersion = version ? tail : [head ?? "", ...tail];
150
+ const [localeHead, ...localeTail] = afterVersion;
151
+ const locale = localeHead && localeDirs.has(localeHead) ? localeHead : "";
152
+ const rest = (locale ? localeTail : afterVersion)
153
+ .filter(Boolean)
154
+ .join("/");
155
+ const key = [version, locale, metaKeyFor(source.prefix, rest)]
156
+ .filter(Boolean)
157
+ .join("/");
128
158
 
129
159
  if (!entry.ok) {
130
160
  diagnostics.push({
@@ -9,7 +9,7 @@ import type { Diagnostic, NavNode, Navigation, PageRecord } from "./types.ts";
9
9
  * covers every source (config, folder meta, frontmatter) at once.
10
10
  */
11
11
 
12
- const ICON_SHAPE_HINT =
12
+ const ICON_FORMAT_HINT =
13
13
  "Use a built-in icon name, an image path/URL, or inline SVG markup.";
14
14
 
15
15
  /** Flatten a sidebar tree to every node, descending into groups. */
@@ -80,7 +80,7 @@ const unknownIconDiagnostics = (
80
80
 
81
81
  /** Warn about icon names that aren't in Blume's set (skipping image/SVG icons). */
82
82
  export const validateNavIcons = (navigation: Navigation): Diagnostic[] =>
83
- unknownIconDiagnostics(collectIcons(navigation), ICON_SHAPE_HINT);
83
+ unknownIconDiagnostics(collectIcons(navigation), ICON_FORMAT_HINT);
84
84
 
85
85
  /**
86
86
  * Warn about unknown icons on curated `search.popular` links. Same accepted
@@ -94,7 +94,7 @@ export const validateSearchPopularIcons = (
94
94
  ? [{ icon: link.icon, where: `popular link "${link.label}"` }]
95
95
  : []
96
96
  );
97
- return unknownIconDiagnostics(icons, ICON_SHAPE_HINT);
97
+ return unknownIconDiagnostics(icons, ICON_FORMAT_HINT);
98
98
  };
99
99
 
100
100
  /** Whether an internal path resolves to a page or a section that has pages. */