blume 0.6.0 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/dist/cli/index.js +6070 -5718
  2. package/dist/cli/index.js.map +41 -40
  3. package/dist/types/core/config-input.d.ts +749 -0
  4. package/dist/types/core/config.d.ts +126 -3
  5. package/dist/types/core/schema.d.ts +10 -27
  6. package/dist/types/core/sources/types.d.ts +6 -0
  7. package/dist/types/index.d.ts +2 -1
  8. package/docs/advanced/changelog.mdx +10 -2
  9. package/docs/configuration/index.mdx +0 -2
  10. package/docs/content/syntax.mdx +4 -8
  11. package/package.json +1 -1
  12. package/src/astro/generate.ts +59 -24
  13. package/src/astro/markdown-negotiation.ts +12 -3
  14. package/src/astro/templates.ts +94 -12
  15. package/src/cli/commands/build.ts +26 -1
  16. package/src/cli/commands/dev.ts +30 -14
  17. package/src/cli/commands/doctor.ts +35 -7
  18. package/src/cli/commands/sync.ts +14 -2
  19. package/src/cli/dev-lock.ts +40 -10
  20. package/src/cli/env.ts +5 -1
  21. package/src/components/content/Update.astro +12 -2
  22. package/src/components/content/changelog-element.ts +62 -0
  23. package/src/components/islands/ask-ai.tsx +3 -1
  24. package/src/components/islands/hooks.ts +5 -1
  25. package/src/components/layout/Header.astro +10 -2
  26. package/src/components/layout/NavSelector.astro +5 -3
  27. package/src/components/layout/PageLayout.astro +2 -1
  28. package/src/components/layout/ReferenceLayout.astro +1 -0
  29. package/src/components/layout/RootLayout.astro +47 -10
  30. package/src/components/layout/Search.astro +8 -3
  31. package/src/components/layout/nav-utils.ts +7 -3
  32. package/src/core/config-input.ts +923 -0
  33. package/src/core/config.ts +126 -3
  34. package/src/core/i18n.ts +6 -5
  35. package/src/core/links.ts +16 -1
  36. package/src/core/meta.ts +112 -52
  37. package/src/core/navigation.ts +15 -5
  38. package/src/core/project-graph.ts +68 -2
  39. package/src/core/schema.ts +8 -14
  40. package/src/core/sources/assets.ts +21 -5
  41. package/src/core/sources/cache.ts +19 -1
  42. package/src/core/sources/github-releases.ts +9 -3
  43. package/src/core/sources/mdx-remote.ts +14 -4
  44. package/src/core/sources/normalize.ts +13 -1
  45. package/src/core/sources/notion.ts +43 -7
  46. package/src/core/sources/resolve.ts +44 -1
  47. package/src/core/sources/sanity.ts +9 -3
  48. package/src/core/sources/types.ts +6 -0
  49. package/src/deploy/adapter-output.ts +82 -0
  50. package/src/deploy/rss.ts +3 -1
  51. package/src/index.ts +1 -1
  52. package/src/markdown/code-title.ts +11 -4
  53. package/src/markdown/index.ts +28 -30
  54. package/src/markdown/math.ts +3 -2
  55. package/src/markdown/package-commands.ts +13 -0
  56. package/src/og/card.ts +3 -1
  57. package/src/openapi/model.ts +2 -1
  58. package/src/openapi/parse.ts +9 -1
  59. package/src/openapi/references.ts +11 -1
  60. package/src/openapi/render-mdx.ts +30 -3
  61. package/src/openapi/source.ts +3 -1
  62. package/src/registry/eject.ts +21 -14
  63. package/src/search/documents.ts +4 -1
  64. package/src/theme/entry.ts +10 -3
  65. package/src/theme/icons.ts +7 -11
@@ -25,7 +25,13 @@ const ENTITIES: Record<string, string> = {
25
25
  // SDK…" is common spec prose). Entity-escape the keyword's first letter so the
26
26
  // construct can't match; it still renders as the literal word.
27
27
  const MDX_ESM_KEYWORD = /^(?<keyword>import|export)\b/gmu;
28
- const mdxSafe = (text: string): string =>
28
+ // Backtick code inline spans and fences alike — is already literal in MDX,
29
+ // and entities are NOT decoded inside it, so escaping there would render the
30
+ // entity text verbatim (`/pets/&#123;petId&#125;`). Matching any balanced
31
+ // backtick run covers `code`, ``code``, and ```fences``` in one shot.
32
+ const BACKTICK_CODE = /(?<bt>`+)[\s\S]*?\k<bt>/gu;
33
+
34
+ const escapeProse = (text: string): string =>
29
35
  text
30
36
  .replace(MDX_UNSAFE, (char) => ENTITIES[char] ?? char)
31
37
  .replace(
@@ -33,6 +39,19 @@ const mdxSafe = (text: string): string =>
33
39
  (keyword) => `&#${keyword.codePointAt(0)};${keyword.slice(1)}`
34
40
  );
35
41
 
42
+ /** Escape MDX-special syntax in prose while leaving backtick code verbatim. */
43
+ const mdxSafe = (text: string): string => {
44
+ let out = "";
45
+ let cursor = 0;
46
+ for (const match of text.matchAll(BACKTICK_CODE)) {
47
+ const start = match.index ?? 0;
48
+ out += escapeProse(text.slice(cursor, start));
49
+ out += match[0];
50
+ cursor = start + match[0].length;
51
+ }
52
+ return out + escapeProse(text.slice(cursor));
53
+ };
54
+
36
55
  /** Frontmatter + body for one operation or overview page. */
37
56
  export interface RenderedPage {
38
57
  data: Record<string, unknown>;
@@ -80,8 +99,16 @@ export const overviewMdx = (spec: ApiSpecData): RenderedPage => {
80
99
  // markdown pipeline gives them ids, permalink anchors, and table-of-contents
81
100
  // entries; only the operation-link list defers to a component.
82
101
  const operations = Object.values(spec.operations);
83
- const sections = [...spec.tags];
84
- const known = new Set(spec.tags.map((tag) => tag.slug));
102
+ // Dedupe by slug: two declared tags that slugify identically (`Store` and
103
+ // `store`) must render one section, not the same operation list twice.
104
+ const sections: typeof spec.tags = [];
105
+ const known = new Set<string>();
106
+ for (const tag of spec.tags) {
107
+ if (!known.has(tag.slug)) {
108
+ known.add(tag.slug);
109
+ sections.push(tag);
110
+ }
111
+ }
85
112
  for (const operation of operations) {
86
113
  if (!known.has(operation.tagSlug)) {
87
114
  known.add(operation.tagSlug);
@@ -59,8 +59,10 @@ const specEntries = (
59
59
  );
60
60
  // Overview last so an operation sets the section's routePath before the index
61
61
  // page is inserted (the group's routePath is derived from its first child).
62
+ // A root-mounted reference refs `index.mdx`, not `/index.mdx`.
63
+ const base = routeToRef(spec.route);
62
64
  entries.push(
63
- toEntry(overviewMdx(spec), `${routeToRef(spec.route)}/index.mdx`)
65
+ toEntry(overviewMdx(spec), base ? `${base}/index.mdx` : "index.mdx")
64
66
  );
65
67
  return entries;
66
68
  };
@@ -12,6 +12,7 @@ import {
12
12
  buildRuntimeData,
13
13
  collectStaged,
14
14
  detectNeedsReact,
15
+ detectUsesMath,
15
16
  } from "../astro/generate.ts";
16
17
  import { discoverIslands } from "../astro/islands.ts";
17
18
  import { customOgRoutes, discoverPages, routeIsTaken } from "../astro/pages.ts";
@@ -102,19 +103,25 @@ export const eject = async (root: string): Promise<string[]> => {
102
103
  const exportPdf = config.export.pdf;
103
104
  const exportEpub = config.export.epub;
104
105
 
105
- const [pages, needsReactRaw, userTheme, rawMarkdown, islands, examples] =
106
- await Promise.all([
107
- context.pagesRoot
108
- ? discoverPages(context.pagesRoot)
109
- : Promise.resolve([]),
110
- detectNeedsReact(root),
111
- context.themeFile
112
- ? readFile(context.themeFile, "utf-8")
113
- : Promise.resolve(""),
114
- buildRawMarkdown(project),
115
- discoverIslands(root),
116
- discoverExamples(root, config.examples),
117
- ]);
106
+ const [
107
+ pages,
108
+ needsReactRaw,
109
+ usesMath,
110
+ userTheme,
111
+ rawMarkdown,
112
+ islands,
113
+ examples,
114
+ ] = await Promise.all([
115
+ context.pagesRoot ? discoverPages(context.pagesRoot) : Promise.resolve([]),
116
+ detectNeedsReact(root),
117
+ detectUsesMath(root),
118
+ context.themeFile
119
+ ? readFile(context.themeFile, "utf-8")
120
+ : Promise.resolve(""),
121
+ buildRawMarkdown(project),
122
+ discoverIslands(root),
123
+ discoverExamples(root, config.examples),
124
+ ]);
118
125
  // Island/example frameworks drive which Astro renderers the ejected config
119
126
  // wires in; React also switches on for project `.tsx`/`.jsx` and Ask AI.
120
127
  const frameworks = new Set<string>([
@@ -192,7 +199,7 @@ export const eject = async (root: string): Promise<string[]> => {
192
199
  askEnabled,
193
200
  exportEpub,
194
201
  exportPdf,
195
- mathEnabled: config.markdown.math,
202
+ mathEnabled: usesMath,
196
203
  needsReact,
197
204
  }),
198
205
  path: join(srcDir, "pages", "[...slug].astro"),
@@ -39,7 +39,10 @@ export interface SearchRecord {
39
39
 
40
40
  const CODE_FENCE = /```[\s\S]*?```/gu;
41
41
  const INLINE_CODE = /`(?<code>[^`]+)`/gu;
42
- const HTML_OR_JSX = /<[^>]+>/gu;
42
+ // Tag-shaped only: a name (or closing slash/fragment) right after `<`, and no
43
+ // newline inside. A bare `<` in prose ("costs < 5 credits") must not swallow
44
+ // everything up to some later `>` — potentially whole paragraphs.
45
+ const HTML_OR_JSX = /<\/?[a-zA-Z][^\n<>]*>|<\/?>/gu;
43
46
  const IMAGE = /!\[[^\]]*\]\([^)]*\)/gu;
44
47
  const LINK = /\[(?<text>[^\]]*)\]\([^)]*\)/gu;
45
48
  const HEADING_MARK = /^#{1,6}\s+/gmu;
@@ -435,6 +435,13 @@ blume-diff {
435
435
  font-size: 0.8125rem;
436
436
  }
437
437
 
438
+ /* GFM renders cells as <td><code> directly, which the descendant form alone
439
+ never matches (a cell is not its own descendant). */
440
+ .prose :where(td, th) > code,
441
+ .prose :where(td, th) :not(pre) > code {
442
+ white-space: nowrap;
443
+ }
444
+
438
445
  blume-tabs pre,
439
446
  .not-prose > div > pre {
440
447
  background: var(--blume-code-background);
@@ -610,9 +617,9 @@ pre:has(.line.focused):hover .line:not(.focused) {
610
617
  content: none;
611
618
  }
612
619
 
613
- /* Inline code highlighting (markdown.code.inline): Shiki colors the tokens of a
614
- \`code\`{:lang} snippet via the same dual-theme CSS variables as fenced blocks,
615
- keeping the inline pill background. */
620
+ /* Inline code highlighting: Shiki colors the tokens of a \`code\`{:lang} snippet
621
+ via the same dual-theme CSS variables as fenced blocks, keeping the inline
622
+ pill background. Always on — it only fires on the trailing {:lang} marker. */
616
623
  .prose code.blume-inline-code span {
617
624
  color: var(--shiki-light);
618
625
  }
@@ -79,14 +79,10 @@ export const resolveIcon = (name: string): ResolvedIcon | null => {
79
79
  return fromSet(DEFAULT_SET, normalized);
80
80
  };
81
81
 
82
- /** Whether a name resolves to a known Lucide icon. */
83
- export const hasIcon = (name: string): boolean => {
84
- if (resolveIcon(name)) {
85
- return true;
86
- }
87
- const normalized = normalize(name);
88
- const bare = normalized.includes(":")
89
- ? normalized.slice(normalized.indexOf(":") + 1)
90
- : normalized;
91
- return Object.values(SETS).some((set) => getIconData(set, bare) !== null);
92
- };
82
+ /**
83
+ * Whether a name resolves to a renderable icon. Exactly mirrors
84
+ * {@link resolveIcon}: a laxer check (e.g. matching the bare name under an
85
+ * unknown prefix) would let callers suppress their fallback and diagnostics
86
+ * for a name `<Icon>` then renders as nothing.
87
+ */
88
+ export const hasIcon = (name: string): boolean => resolveIcon(name) !== null;