@bettercms-ai/astro 0.4.0 → 0.5.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.
@@ -1,24 +1,51 @@
1
1
  ---
2
2
  /**
3
- * <BcmsBlocks> — renders a page's blockJson tree natively in Astro. Covers all
4
- * 8 block types (heading, text, image, button, spacer, video, columns, form),
5
- * with `columns` recursing via Astro.self and `form` resolving from the `forms`
6
- * prop. Class names mirror the server-side renderer.
3
+ * <BcmsBlocks> — renders a page's blockJson tree natively in Astro. Covers
4
+ * heading, text, image, button, spacer, video, columns, form and `component`,
5
+ * with `columns` and `component` recursing via Astro.self and `form`/`component`
6
+ * resolving from the `forms`/`components` props. Class names mirror the
7
+ * server-side renderer.
8
+ *
9
+ * `component` was previously absent, so every placed component instance hit
10
+ * `default: return null` — a silently blank section on Astro sites while the
11
+ * dashboard canvas and the Next adapter both rendered it. Resolution mirrors
12
+ * render-page.ts: the same shared `applyOverrides`, the same depth/cycle guard,
13
+ * the same fail-soft on a missing definition.
7
14
  */
8
15
  import type { ContentBlock } from "@bettercms-ai/types";
9
- import type { DeliveryForm } from "@bettercms-ai/sdk";
16
+ import type { DeliveryComponent, DeliveryForm } from "@bettercms-ai/sdk";
17
+ import { applyOverrides } from "@bettercms-ai/ui/resolve";
10
18
  import BcmsImage from "./BcmsImage.astro";
11
19
  import BcmsForm from "./BcmsForm.astro";
12
20
 
13
21
  interface Props {
14
22
  blocks: ContentBlock[];
15
23
  forms?: DeliveryForm[];
24
+ /** Definitions for `component` blocks — pass `readComponents()` from @bettercms-ai/next
25
+ * or fetch `/delivery/:workspace/components`. Absent ⇒ instances render nothing. */
26
+ components?: DeliveryComponent[];
16
27
  turnstileSiteKey?: string | null;
28
+ /** Recursion depth for nested components. Internal; do not pass. */
29
+ depth?: number;
30
+ /** Component ids already on this branch — cycle guard. Internal; do not pass. */
31
+ seen?: string[];
17
32
  class?: string;
18
33
  }
19
34
 
20
- const { blocks = [], forms = [], turnstileSiteKey, class: className } = Astro.props;
35
+ /** Matches render-page.ts and @bettercms-ai/next; a component tree deeper than this is a bug. */
36
+ const MAX_COMPONENT_DEPTH = 10;
37
+
38
+ const {
39
+ blocks = [],
40
+ forms = [],
41
+ components = [],
42
+ turnstileSiteKey,
43
+ depth = 0,
44
+ seen = [],
45
+ class: className,
46
+ } = Astro.props;
21
47
  const formMap = new Map(forms.map((f) => [f.id, f]));
48
+ const componentMap = new Map(components.map((c) => [c.id, c]));
22
49
 
23
50
  const clamp = (n: number, min: number, max: number) => Math.min(max, Math.max(min, n));
24
51
  const anyProps = (b: ContentBlock) => (b as { props?: Record<string, any> }).props ?? {};
@@ -69,7 +96,14 @@ const anyProps = (b: ContentBlock) => (b as { props?: Record<string, any> }).pro
69
96
  >
70
97
  {cols.map((col) => (
71
98
  <div class="bcms-col">
72
- <Astro.self blocks={col} forms={forms} turnstileSiteKey={turnstileSiteKey} />
99
+ <Astro.self
100
+ blocks={col}
101
+ forms={forms}
102
+ components={components}
103
+ turnstileSiteKey={turnstileSiteKey}
104
+ depth={depth}
105
+ seen={seen}
106
+ />
73
107
  </div>
74
108
  ))}
75
109
  </div>
@@ -79,6 +113,30 @@ const anyProps = (b: ContentBlock) => (b as { props?: Record<string, any> }).pro
79
113
  const form = formMap.get(p.formId);
80
114
  return form ? <BcmsForm form={form} turnstileSiteKey={turnstileSiteKey} /> : null;
81
115
  }
116
+ case "component": {
117
+ const def = componentMap.get(String(p.componentId ?? ""));
118
+ // Fail soft on a deleted/unknown component, exactly like the server renderer —
119
+ // a dangling reference must never break the build or the page.
120
+ if (!def) return null;
121
+ // A component's blockJson can embed other components, and nothing validates
122
+ // against a cycle at write time, so the guard has to live at render.
123
+ if (depth >= MAX_COMPONENT_DEPTH || seen.includes(def.id)) return null;
124
+ const resolved = applyOverrides<ContentBlock>(
125
+ def.blockJson,
126
+ def.props ?? [],
127
+ (p.overrides as Record<string, unknown>) ?? {},
128
+ );
129
+ return (
130
+ <Astro.self
131
+ blocks={resolved}
132
+ forms={forms}
133
+ components={components}
134
+ turnstileSiteKey={turnstileSiteKey}
135
+ depth={depth + 1}
136
+ seen={[...seen, def.id]}
137
+ />
138
+ );
139
+ }
82
140
  default:
83
141
  return null;
84
142
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bettercms-ai/astro",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "type": "module",
5
5
  "description": "The BetterCMS adapter for Astro — a `bettercms()` integration, a `bettercms:client` virtual module, typed content loaders, draft preview, and native .astro rendering components.",
6
6
  "main": "./dist/index.js",
@@ -49,7 +49,8 @@
49
49
  "dependencies": {
50
50
  "@bettercms-ai/image-url": "^0.1.0",
51
51
  "@bettercms-ai/sdk": "^1.5.0",
52
- "@bettercms-ai/types": "^1.4.0"
52
+ "@bettercms-ai/types": "^1.4.0",
53
+ "@bettercms-ai/ui": "^0.4.0"
53
54
  },
54
55
  "peerDependencies": {
55
56
  "astro": ">=4"