@bettercms-ai/next 0.5.2 → 0.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.
package/dist/blocks.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { ReactElement } from 'react';
2
- import { ContentBlock, DeliveryComponent } from '@betttercms/types';
3
- import { DeliveryForm } from '@betttercms/sdk';
2
+ import { ContentBlock, DeliveryComponent } from '@bettercms-ai/types';
3
+ import { DeliveryForm } from '@bettercms-ai/sdk';
4
4
 
5
5
  /**
6
6
  * <BcmsBlocks> — render a BetterCMS page's block_json natively in React.
package/dist/blocks.js CHANGED
@@ -3,85 +3,8 @@
3
3
 
4
4
  // src/blocks.tsx
5
5
  import { useState as useState2 } from "react";
6
-
7
- // ../types/dist/index.js
8
- var SPACE_PX = { none: "0", xs: "4px", sm: "8px", md: "16px", lg: "32px", xl: "64px" };
9
- var FONT_SIZE_REM = { sm: "0.875rem", base: "1rem", lg: "1.25rem", xl: "1.5rem", "2xl": "2rem" };
10
- var WEIGHT = { normal: 400, medium: 500, semibold: 600, bold: 700 };
11
- var RADIUS_PX = { none: "0", sm: "4px", md: "8px", lg: "16px", full: "9999px" };
12
- var COLOR_HEX = { white: "#ffffff", black: "#111111", muted: "#6b7280", mutedBg: "#f3f4f6", primary: "#111111", accent: "#4f46e5" };
13
- function blockStyleToCss(style) {
14
- if (!style || typeof style !== "object") return void 0;
15
- const s = style;
16
- const css = {};
17
- if (s.paddingY && SPACE_PX[s.paddingY] != null) {
18
- css.paddingTop = SPACE_PX[s.paddingY];
19
- css.paddingBottom = SPACE_PX[s.paddingY];
20
- }
21
- if (s.paddingX && SPACE_PX[s.paddingX] != null) {
22
- css.paddingLeft = SPACE_PX[s.paddingX];
23
- css.paddingRight = SPACE_PX[s.paddingX];
24
- }
25
- if (s.marginTop && SPACE_PX[s.marginTop] != null) css.marginTop = SPACE_PX[s.marginTop];
26
- if (s.marginBottom && SPACE_PX[s.marginBottom] != null) css.marginBottom = SPACE_PX[s.marginBottom];
27
- if (s.background && COLOR_HEX[s.background]) css.background = COLOR_HEX[s.background];
28
- if (s.textColor && COLOR_HEX[s.textColor]) css.color = COLOR_HEX[s.textColor];
29
- if (s.align === "left" || s.align === "center" || s.align === "right") css.textAlign = s.align;
30
- if (s.fontSize && FONT_SIZE_REM[s.fontSize]) css.fontSize = FONT_SIZE_REM[s.fontSize];
31
- if (s.weight && WEIGHT[s.weight] != null) css.fontWeight = WEIGHT[s.weight];
32
- if (s.radius && RADIUS_PX[s.radius] != null) css.borderRadius = RADIUS_PX[s.radius];
33
- return Object.keys(css).length ? css : void 0;
34
- }
35
-
36
- // ../ui/dist/resolve.js
37
- function childArrays(block) {
38
- const p = block.props ?? {};
39
- switch (block.type) {
40
- case "columns":
41
- return Array.isArray(p.columns) ? p.columns : [];
42
- case "section":
43
- return Array.isArray(p.children) ? [p.children] : [];
44
- case "slider":
45
- return Array.isArray(p.slides) ? p.slides.map((s) => Array.isArray(s.children) ? s.children : []) : [];
46
- case "tabs":
47
- return Array.isArray(p.tabs) ? p.tabs.map((t) => Array.isArray(t.children) ? t.children : []) : [];
48
- default:
49
- return [];
50
- }
51
- }
52
- function findBlockById(blocks, id) {
53
- for (const b of blocks) {
54
- if (b?.id === id) return b;
55
- for (const child of childArrays(b)) {
56
- const found = findBlockById(child, id);
57
- if (found) return found;
58
- }
59
- }
60
- return null;
61
- }
62
- function setPath(obj, path, value) {
63
- const keys = path.split(".");
64
- let cur = obj;
65
- for (let i = 0; i < keys.length - 1; i++) {
66
- const k = keys[i];
67
- if (typeof cur[k] !== "object" || cur[k] === null) cur[k] = {};
68
- cur = cur[k];
69
- }
70
- cur[keys[keys.length - 1]] = value;
71
- }
72
- function applyOverrides(blockJson, propDefs, overrides) {
73
- if (!Array.isArray(blockJson)) return [];
74
- if (!propDefs.length || !overrides || Object.keys(overrides).length === 0) {
75
- return blockJson;
76
- }
77
- const clone = structuredClone(blockJson);
78
- for (const def of propDefs) {
79
- if (!(def.key in overrides)) continue;
80
- const target = findBlockById(clone, def.target.blockId);
81
- if (target) setPath(target, def.target.path, overrides[def.key]);
82
- }
83
- return clone;
84
- }
6
+ import { blockStyleToCss } from "@bettercms-ai/types";
7
+ import { applyOverrides } from "@bettercms-ai/ui/resolve";
85
8
 
86
9
  // src/form.tsx
87
10
  import { useEffect, useMemo, useState } from "react";
@@ -338,13 +261,15 @@ function renderBlockInner(block, ctx) {
338
261
  }
339
262
  function renderHeading({ id, props }) {
340
263
  const Tag = `h${props.level ?? 2}`;
341
- return /* @__PURE__ */ jsx2(Tag, { className: "bcms-block bcms-block--heading", children: props.text }, id);
264
+ return /* @__PURE__ */ jsx2(Tag, { className: "bcms-block bcms-block--heading", "data-bcms-field": `${id}__text`, children: props.text }, id);
342
265
  }
343
266
  function renderText({ id, props }) {
344
267
  return /* @__PURE__ */ jsx2(
345
268
  "div",
346
269
  {
347
270
  className: "bcms-block bcms-block--text",
271
+ "data-bcms-field": `${id}__html`,
272
+ "data-bcms-kind": "richtext",
348
273
  dangerouslySetInnerHTML: { __html: props.html }
349
274
  },
350
275
  id
@@ -358,7 +283,9 @@ function renderImage({ id, props }) {
358
283
  alt: props.alt,
359
284
  width: props.width,
360
285
  height: props.height,
361
- loading: "lazy"
286
+ loading: "lazy",
287
+ "data-bcms-field": `${id}__src`,
288
+ "data-bcms-kind": "image"
362
289
  }
363
290
  );
364
291
  if (!props.caption) {
@@ -375,6 +302,7 @@ function renderButton({ id, props }) {
375
302
  {
376
303
  href: props.href,
377
304
  className: `bcms-block bcms-block--button bcms-button--${props.variant ?? "primary"}`,
305
+ "data-bcms-field": `${id}__text`,
378
306
  children: props.text
379
307
  },
380
308
  id
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/blocks.tsx","../../types/src/block.ts","../../types/src/blockStyle.ts","../../ui/src/resolve.ts","../src/form.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * <BcmsBlocks> — render a BetterCMS page's block_json natively in React.\n *\n * Pass the `blocks` from a page (delivery API `/content/:slug` → `entry.blocks`)\n * plus the `forms` you read with `readForms()`. Every block type renders to plain,\n * class-driven markup (the host site owns the styling); a `form` block resolves its\n * `formId` against `forms` and renders `<BcmsForm>` inline exactly where the user\n * placed it in the page builder. This is the turnkey \"Webflow\" path — forms show up\n * wherever they were dropped on the page, with no manual wiring.\n *\n * Ships from its own \"use client\" entry (like <BcmsForm>) so it can be rendered from\n * a Next App Router Server Component without breaking the RSC boundary.\n */\n\nimport { useState, type CSSProperties, type ReactElement, type ReactNode } from \"react\";\nimport { blockStyleToCss } from \"@betttercms/types\";\nimport type {\n ContentBlock,\n HeadingBlock,\n TextBlock,\n ImageBlock,\n ButtonBlock,\n SpacerBlock,\n VideoBlock,\n ColumnsBlock,\n FormBlock,\n SectionBlock,\n NavbarBlock,\n FooterBlock,\n SliderBlock,\n TabsBlock,\n ComponentBlock,\n DeliveryComponent,\n} from \"@betttercms/types\";\nimport type { DeliveryForm } from \"@betttercms/sdk\";\nimport { applyOverrides } from \"@betttercms/ui/resolve\";\nimport { BcmsForm } from \"./form.js\";\n\nconst MAX_COMPONENT_DEPTH = 10;\n\nexport interface BcmsBlocksProps {\n /** A page's blocks (delivery API `/content/:slug` → `entry.blocks`). */\n blocks: ContentBlock[];\n /** Forms available to `form` blocks — from `readForms().forms`. */\n forms?: DeliveryForm[];\n /** Reusable components available to `component` blocks — from `readComponents()`. */\n components?: DeliveryComponent[];\n /** Turnstile site key — forwarded to `<BcmsForm>` for form blocks. */\n turnstileSiteKey?: string;\n /** API origin forwarded to `<BcmsForm>`. Defaults to the SDK default. */\n apiBase?: string;\n /** Class for the wrapping <div>. */\n className?: string;\n}\n\n/** Render a page's blocks. Unknown/empty blocks render nothing (fail-soft). */\nexport function BcmsBlocks({\n blocks,\n forms,\n components,\n turnstileSiteKey,\n apiBase,\n className,\n}: BcmsBlocksProps): ReactElement {\n const formsById = new Map((forms ?? []).map((f) => [f.id, f]));\n const componentsById = new Map((components ?? []).map((cmp) => [cmp.id, cmp]));\n const ctx: RenderCtx = { formsById, componentsById, turnstileSiteKey, apiBase, depth: 0, seen: new Set() };\n return <div className={className}>{blocks.map((block) => renderBlock(block, ctx))}</div>;\n}\n\ninterface RenderCtx {\n formsById: Map<string, DeliveryForm>;\n componentsById: Map<string, DeliveryComponent>;\n turnstileSiteKey?: string;\n apiBase?: string;\n depth: number;\n seen: Set<string>;\n}\n\nfunction renderBlock(block: ContentBlock, ctx: RenderCtx): ReactNode {\n const node = renderBlockInner(block, ctx);\n const style = blockStyleToCss(block.style);\n if (!style || node == null) return node;\n // FLO-264: honor the block's bounded style tokens in the headless render too, so\n // @betttercms/next output matches the builder canvas + hosted site.\n return (\n <div key={block.id} style={style as CSSProperties}>\n {node}\n </div>\n );\n}\n\nfunction renderBlockInner(block: ContentBlock, ctx: RenderCtx): ReactNode {\n switch (block.type) {\n case \"heading\":\n return renderHeading(block);\n case \"text\":\n return renderText(block);\n case \"image\":\n return renderImage(block);\n case \"button\":\n return renderButton(block);\n case \"spacer\":\n return renderSpacer(block);\n case \"video\":\n return renderVideo(block);\n case \"columns\":\n return renderColumns(block, ctx);\n case \"form\":\n return renderForm(block, ctx);\n case \"section\":\n return renderSection(block, ctx);\n case \"navbar\":\n return renderNavbar(block);\n case \"footer\":\n return renderFooter(block);\n case \"slider\":\n return <BcmsSlider key={block.id} block={block} ctx={ctx} />;\n case \"tabs\":\n return <BcmsTabs key={block.id} block={block} ctx={ctx} />;\n case \"component\":\n return renderComponent(block, ctx);\n default:\n return null;\n }\n}\n\nfunction renderHeading({ id, props }: HeadingBlock): ReactNode {\n const Tag = `h${props.level ?? 2}` as \"h1\" | \"h2\" | \"h3\";\n return (\n <Tag key={id} className=\"bcms-block bcms-block--heading\">\n {props.text}\n </Tag>\n );\n}\n\nfunction renderText({ id, props }: TextBlock): ReactNode {\n // `html` is sanitized server-side before storage.\n return (\n <div\n key={id}\n className=\"bcms-block bcms-block--text\"\n dangerouslySetInnerHTML={{ __html: props.html }}\n />\n );\n}\n\nfunction renderImage({ id, props }: ImageBlock): ReactNode {\n const img = (\n <img\n src={props.src}\n alt={props.alt}\n width={props.width}\n height={props.height}\n loading=\"lazy\"\n />\n );\n if (!props.caption) {\n return (\n <figure key={id} className=\"bcms-block bcms-block--image\">\n {img}\n </figure>\n );\n }\n return (\n <figure key={id} className=\"bcms-block bcms-block--image\">\n {img}\n <figcaption>{props.caption}</figcaption>\n </figure>\n );\n}\n\nfunction renderButton({ id, props }: ButtonBlock): ReactNode {\n return (\n <a\n key={id}\n href={props.href}\n className={`bcms-block bcms-block--button bcms-button--${props.variant ?? \"primary\"}`}\n >\n {props.text}\n </a>\n );\n}\n\nfunction renderSpacer({ id, props }: SpacerBlock): ReactNode {\n return (\n <div\n key={id}\n className=\"bcms-block bcms-block--spacer\"\n style={{ height: props.height }}\n aria-hidden=\"true\"\n />\n );\n}\n\nfunction renderVideo({ id, props }: VideoBlock): ReactNode {\n return (\n <video\n key={id}\n className=\"bcms-block bcms-block--video\"\n src={props.url}\n poster={props.poster}\n autoPlay={props.autoplay}\n loop={props.loop}\n controls\n />\n );\n}\n\nfunction renderColumns({ id, props }: ColumnsBlock, ctx: RenderCtx): ReactNode {\n return (\n <div\n key={id}\n className=\"bcms-block bcms-block--columns\"\n style={{\n display: \"grid\",\n gridTemplateColumns: `repeat(${props.columns.length || 1}, 1fr)`,\n gap: props.gap,\n }}\n >\n {props.columns.map((column, i) => (\n <div key={i} className=\"bcms-column\">\n {column.map((child) => renderBlock(child, ctx))}\n </div>\n ))}\n </div>\n );\n}\n\nfunction renderForm({ id, props }: FormBlock, ctx: RenderCtx): ReactNode {\n const form = ctx.formsById.get(props.formId);\n if (!form) return null; // form deleted or not delivered — render nothing\n return (\n <div key={id} className=\"bcms-block bcms-block--form\">\n <BcmsForm form={form} turnstileSiteKey={ctx.turnstileSiteKey} apiBase={ctx.apiBase} />\n </div>\n );\n}\n\nfunction renderSection({ id, props }: SectionBlock, ctx: RenderCtx): ReactNode {\n const inner = props.children.map((child) => renderBlock(child, ctx));\n return (\n <section\n key={id}\n className=\"bcms-block bcms-block--section\"\n style={{ background: props.background, padding: props.paddingY != null ? `${props.paddingY}px 0` : undefined }}\n >\n {props.maxWidth ? <div style={{ maxWidth: props.maxWidth, margin: \"0 auto\" }}>{inner}</div> : inner}\n </section>\n );\n}\n\nfunction renderNavbar({ id, props }: NavbarBlock): ReactNode {\n return (\n <nav key={id} className=\"bcms-block bcms-block--navbar\">\n {props.logo?.src ? (\n <a className=\"bcms-navbar-logo\" href={props.logo.href ?? \"/\"}>\n <img src={props.logo.src} alt={props.logo.alt} />\n </a>\n ) : null}\n <div className=\"bcms-navbar-links\">\n {props.links.map((l, i) => (\n <a key={i} href={l.href}>\n {l.label}\n </a>\n ))}\n </div>\n {props.cta ? (\n <a className=\"bcms-button bcms-button--primary\" href={props.cta.href}>\n {props.cta.text}\n </a>\n ) : null}\n </nav>\n );\n}\n\nfunction renderFooter({ id, props }: FooterBlock): ReactNode {\n return (\n <footer key={id} className=\"bcms-block bcms-block--footer\">\n <div className=\"bcms-footer-cols\">\n {props.columns.map((col, i) => (\n <div key={i} className=\"bcms-footer-col\">\n {col.heading ? <h4>{col.heading}</h4> : null}\n <ul>\n {col.links.map((l, j) => (\n <li key={j}>\n <a href={l.href}>{l.label}</a>\n </li>\n ))}\n </ul>\n </div>\n ))}\n </div>\n {props.copyright ? <p className=\"bcms-footer-copy\">{props.copyright}</p> : null}\n </footer>\n );\n}\n\nfunction BcmsSlider({ block, ctx }: { block: SliderBlock; ctx: RenderCtx }): ReactElement {\n const { slides } = block.props;\n const [i, setI] = useState(0);\n const go = (n: number) => setI((n + slides.length) % (slides.length || 1));\n return (\n <div className=\"bcms-block bcms-block--slider\" style={{ position: \"relative\" }}>\n {slides.map((s, idx) => (\n <div key={s.id} className=\"bcms-slide\" hidden={idx !== i}>\n {s.children.map((child) => renderBlock(child, ctx))}\n </div>\n ))}\n <button type=\"button\" className=\"bcms-slider-prev\" aria-label=\"Previous\" onClick={() => go(i - 1)}>\n ‹\n </button>\n <button type=\"button\" className=\"bcms-slider-next\" aria-label=\"Next\" onClick={() => go(i + 1)}>\n ›\n </button>\n </div>\n );\n}\n\nfunction BcmsTabs({ block, ctx }: { block: TabsBlock; ctx: RenderCtx }): ReactElement {\n const { tabs } = block.props;\n const [active, setActive] = useState(0);\n return (\n <div className=\"bcms-block bcms-block--tabs\">\n <div className=\"bcms-tablist\" role=\"tablist\">\n {tabs.map((t, idx) => (\n <button\n key={t.id}\n role=\"tab\"\n type=\"button\"\n className=\"bcms-tab\"\n aria-selected={idx === active}\n onClick={() => setActive(idx)}\n >\n {t.label}\n </button>\n ))}\n </div>\n {tabs.map((t, idx) => (\n <div key={t.id} role=\"tabpanel\" className=\"bcms-tabpanel\" hidden={idx !== active}>\n {t.children.map((child) => renderBlock(child, ctx))}\n </div>\n ))}\n </div>\n );\n}\n\nfunction renderComponent({ id, props }: ComponentBlock, ctx: RenderCtx): ReactNode {\n const def = ctx.componentsById.get(props.componentId);\n if (!def) return null; // not delivered — fail-soft\n if (ctx.depth >= MAX_COMPONENT_DEPTH || ctx.seen.has(def.id)) return null; // cycle/depth guard\n const resolved = applyOverrides<ContentBlock>(def.blockJson, def.props ?? [], props.overrides ?? {});\n const childCtx: RenderCtx = { ...ctx, depth: ctx.depth + 1, seen: new Set([...ctx.seen, def.id]) };\n return (\n <div key={id} className=\"bcms-block bcms-block--component\">\n {resolved.map((child) => renderBlock(child, childCtx))}\n </div>\n );\n}\n\n","/**\n * ContentBlock — a discriminated union of all CMS block types.\n * Each block has a `type` discriminator and a `props` object\n * whose shape matches the block type.\n */\n\n// ── Block style (FLO-264) ──────────────────────────────────────────────────────\n// A uniform, BOUNDED set of design tokens any block may carry (sibling to `props`).\n// Tokens map to fixed CSS values in the renderer (`styleTokensToCss`) so styling\n// reaches the live site WITHOUT arbitrary CSS. Additive + fully optional.\n\nexport type SpaceToken = \"none\" | \"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\";\nexport type ColorToken = \"none\" | \"white\" | \"black\" | \"muted\" | \"mutedBg\" | \"primary\" | \"accent\";\nexport type AlignToken = \"left\" | \"center\" | \"right\";\nexport type FontSizeToken = \"sm\" | \"base\" | \"lg\" | \"xl\" | \"2xl\";\nexport type WeightToken = \"normal\" | \"medium\" | \"semibold\" | \"bold\";\nexport type RadiusToken = \"none\" | \"sm\" | \"md\" | \"lg\" | \"full\";\n\nexport interface BlockStyle {\n paddingY?: SpaceToken;\n paddingX?: SpaceToken;\n marginTop?: SpaceToken;\n marginBottom?: SpaceToken;\n background?: ColorToken;\n textColor?: ColorToken;\n align?: AlignToken;\n fontSize?: FontSizeToken;\n weight?: WeightToken;\n radius?: RadiusToken;\n}\n\n// ── Heading ──────────────────────────────────────────────────────────────────\n\nexport interface HeadingProps {\n text: string;\n level?: 1 | 2 | 3;\n}\n\nexport interface HeadingBlock {\n type: \"heading\";\n id: string;\n style?: BlockStyle;\n props: HeadingProps;\n}\n\n// ── Text ─────────────────────────────────────────────────────────────────────\n\nexport interface TextProps {\n html: string;\n}\n\nexport interface TextBlock {\n type: \"text\";\n id: string;\n style?: BlockStyle;\n props: TextProps;\n}\n\n// ── Image ────────────────────────────────────────────────────────────────────\n\nexport interface ImageProps {\n src: string;\n alt: string;\n width?: number;\n height?: number;\n caption?: string;\n}\n\nexport interface ImageBlock {\n type: \"image\";\n id: string;\n style?: BlockStyle;\n props: ImageProps;\n}\n\n// ── Button ───────────────────────────────────────────────────────────────────\n\nexport interface ButtonProps {\n text: string;\n href: string;\n variant?: \"primary\" | \"secondary\";\n}\n\nexport interface ButtonBlock {\n type: \"button\";\n id: string;\n style?: BlockStyle;\n props: ButtonProps;\n}\n\n// ── Spacer ───────────────────────────────────────────────────────────────────\n\nexport interface SpacerProps {\n height: number; // in px\n}\n\nexport interface SpacerBlock {\n type: \"spacer\";\n id: string;\n style?: BlockStyle;\n props: SpacerProps;\n}\n\n// ── Columns ───────────────────────────────────────────────────────────────────\n\nexport interface ColumnsProps {\n columns: ContentBlock[][]; // array of column slots, each slot is an array of blocks\n gap?: number; // in px\n}\n\nexport interface ColumnsBlock {\n type: \"columns\";\n id: string;\n style?: BlockStyle;\n props: ColumnsProps;\n}\n\n// ── Video ────────────────────────────────────────────────────────────────────\n\nexport interface VideoProps {\n url: string;\n poster?: string;\n autoplay?: boolean;\n loop?: boolean;\n}\n\nexport interface VideoBlock {\n type: \"video\";\n id: string;\n style?: BlockStyle;\n props: VideoProps;\n}\n\n// ── Form ─────────────────────────────────────────────────────────────────────\n\nexport interface FormProps {\n formId: string; // references forms.id (a nanoid). Resolved against delivered forms at render time.\n}\n\nexport interface FormBlock {\n type: \"form\";\n id: string;\n style?: BlockStyle;\n props: FormProps;\n}\n\n// ── Section (recursive container) ──────────────────────────────────────────────\n\nexport interface SectionProps {\n children: ContentBlock[];\n background?: string;\n paddingY?: number; // in px\n maxWidth?: number; // in px\n}\n\nexport interface SectionBlock {\n type: \"section\";\n id: string;\n style?: BlockStyle;\n props: SectionProps;\n}\n\n// ── Navbar ─────────────────────────────────────────────────────────────────────\n\nexport interface NavLink {\n label: string;\n href: string;\n}\n\nexport interface NavbarProps {\n logo?: { src: string; alt: string; href?: string };\n links: NavLink[];\n cta?: { text: string; href: string };\n}\n\nexport interface NavbarBlock {\n type: \"navbar\";\n id: string;\n style?: BlockStyle;\n props: NavbarProps;\n}\n\n// ── Footer ─────────────────────────────────────────────────────────────────────\n\nexport interface FooterColumn {\n heading?: string;\n links: NavLink[];\n}\n\nexport interface FooterProps {\n columns: FooterColumn[];\n copyright?: string;\n}\n\nexport interface FooterBlock {\n type: \"footer\";\n id: string;\n style?: BlockStyle;\n props: FooterProps;\n}\n\n// ── Slider / Carousel (recursive container, client-hydrated) ────────────────────\n\nexport interface SlideItem {\n id: string;\n children: ContentBlock[];\n}\n\nexport interface SliderProps {\n slides: SlideItem[];\n autoplay?: boolean;\n interval?: number; // in ms\n loop?: boolean;\n}\n\nexport interface SliderBlock {\n type: \"slider\";\n id: string;\n style?: BlockStyle;\n props: SliderProps;\n}\n\n// ── Tabs (recursive container, client-hydrated) ─────────────────────────────────\n\nexport interface TabItem {\n id: string;\n label: string;\n children: ContentBlock[];\n}\n\nexport interface TabsProps {\n tabs: TabItem[];\n}\n\nexport interface TabsBlock {\n type: \"tabs\";\n id: string;\n style?: BlockStyle;\n props: TabsProps;\n}\n\n// ── Component instance (Webflow-style reusable symbol) ──────────────────────────\n// References a reusable component by id. `overrides` maps a component prop key\n// (declared in the component's `props[]` allowlist) to a value, applied to the\n// component's block tree at render time. Resolved against delivered components.\n\nexport interface ComponentProps {\n componentId: string;\n overrides?: Record<string, unknown>;\n}\n\nexport interface ComponentBlock {\n type: \"component\";\n id: string;\n style?: BlockStyle;\n props: ComponentProps;\n}\n\n// ── Union ─────────────────────────────────────────────────────────────────────\n\nexport type ContentBlock =\n | HeadingBlock\n | TextBlock\n | ImageBlock\n | ButtonBlock\n | SpacerBlock\n | ColumnsBlock\n | VideoBlock\n | FormBlock\n | SectionBlock\n | NavbarBlock\n | FooterBlock\n | SliderBlock\n | TabsBlock\n | ComponentBlock;\n\n/** Literal union of all block type discriminators. */\nexport type BlockType = ContentBlock[\"type\"];\n\n/**\n * Type guard — narrows an unknown block to `ContentBlock`.\n *\n * @example\n * const block = maybeBlock as unknown;\n * if (isBlock(block)) {\n * // block is ContentBlock here\n * }\n */\nexport function isBlock(value: unknown): value is ContentBlock {\n return (\n typeof value === \"object\" &&\n value !== null &&\n \"type\" in value &&\n typeof (value as Record<string, unknown>)[\"type\"] === \"string\"\n );\n}\n\n/**\n * Extract the `type` discriminator from a block, or `null` if not a block.\n *\n * @example\n * const block = { type: \"heading\", id: \"1\", props: { text: \"Hi\" } };\n * getBlockType(block); // \"heading\"\n * getBlockType({ not: \"a block\" }); // null\n */\nexport function getBlockType(value: unknown): BlockType | null {\n if (isBlock(value)) return value.type;\n return null;\n}\n","/**\n * FLO-264 — bounded block-style tokens → CSS. Framework-agnostic (returns a plain\n * declaration object, no React types) so every renderer shares ONE mapping: the\n * headless @betttercms/next adapter, @betttercms/ui, and (string form) the hosted\n * render-page.ts. Unknown tokens are dropped — closed set, no arbitrary CSS.\n */\nimport type { BlockStyle } from \"./block.js\";\n\nconst SPACE_PX: Record<string, string> = { none: \"0\", xs: \"4px\", sm: \"8px\", md: \"16px\", lg: \"32px\", xl: \"64px\" };\nconst FONT_SIZE_REM: Record<string, string> = { sm: \"0.875rem\", base: \"1rem\", lg: \"1.25rem\", xl: \"1.5rem\", \"2xl\": \"2rem\" };\nconst WEIGHT: Record<string, number> = { normal: 400, medium: 500, semibold: 600, bold: 700 };\nconst RADIUS_PX: Record<string, string> = { none: \"0\", sm: \"4px\", md: \"8px\", lg: \"16px\", full: \"9999px\" };\nconst COLOR_HEX: Record<string, string> = { white: \"#ffffff\", black: \"#111111\", muted: \"#6b7280\", mutedBg: \"#f3f4f6\", primary: \"#111111\", accent: \"#4f46e5\" };\n\n/** A CSS declaration object (key → value) usable as a React `style` prop, or undefined when empty. */\nexport function blockStyleToCss(style: BlockStyle | undefined): Record<string, string | number> | undefined {\n if (!style || typeof style !== \"object\") return undefined;\n const s = style as Record<string, string>;\n const css: Record<string, string | number> = {};\n if (s.paddingY && SPACE_PX[s.paddingY] != null) { css.paddingTop = SPACE_PX[s.paddingY]!; css.paddingBottom = SPACE_PX[s.paddingY]!; }\n if (s.paddingX && SPACE_PX[s.paddingX] != null) { css.paddingLeft = SPACE_PX[s.paddingX]!; css.paddingRight = SPACE_PX[s.paddingX]!; }\n if (s.marginTop && SPACE_PX[s.marginTop] != null) css.marginTop = SPACE_PX[s.marginTop]!;\n if (s.marginBottom && SPACE_PX[s.marginBottom] != null) css.marginBottom = SPACE_PX[s.marginBottom]!;\n if (s.background && COLOR_HEX[s.background]) css.background = COLOR_HEX[s.background]!;\n if (s.textColor && COLOR_HEX[s.textColor]) css.color = COLOR_HEX[s.textColor]!;\n if (s.align === \"left\" || s.align === \"center\" || s.align === \"right\") css.textAlign = s.align;\n if (s.fontSize && FONT_SIZE_REM[s.fontSize]) css.fontSize = FONT_SIZE_REM[s.fontSize]!;\n if (s.weight && WEIGHT[s.weight] != null) css.fontWeight = WEIGHT[s.weight]!;\n if (s.radius && RADIUS_PX[s.radius] != null) css.borderRadius = RADIUS_PX[s.radius]!;\n return Object.keys(css).length ? css : undefined;\n}\n","/**\n * Pure component-instance resolver — clone a component's blockJson and write each\n * declared override at its target (blockId + dotted path), honoring the props[]\n * allowlist. The single source of truth shared by the dashboard canvas preview, the\n * backend live HTML renderer, and @bettercms-ai/next's SSG output, so all three resolve\n * instances identically. React-free and exported on its own subpath (@bettercms-ai/ui/\n * resolve) so the Hono server can import it without pulling in the block components.\n */\n\n/** Minimal block shape the resolver needs — every real block type is structurally wider.\n * `props` is `unknown` (not `Record<string, unknown>`) so typed block unions like\n * @bettercms-ai/types' ContentBlock — whose `props` are specific interfaces without an\n * index signature — still satisfy the constraint. */\nexport type ResolverBlock = { type: string; id?: string; props?: unknown };\n\n/** Minimal prop-def shape — the real ComponentPropDef is structurally wider. */\nexport type ResolverPropDef = { key: string; target: { blockId: string; path: string } };\n\n/** The child block arrays a container holds, one entry per nested slot (all container types). */\nexport function childArrays<B extends ResolverBlock>(block: B): B[][] {\n const p = (block.props ?? {}) as Record<string, unknown>;\n switch (block.type) {\n case \"columns\":\n return Array.isArray(p.columns) ? (p.columns as B[][]) : [];\n case \"section\":\n return Array.isArray(p.children) ? [p.children as B[]] : [];\n case \"slider\":\n return Array.isArray(p.slides)\n ? (p.slides as { children?: B[] }[]).map((s) => (Array.isArray(s.children) ? s.children : []))\n : [];\n case \"tabs\":\n return Array.isArray(p.tabs)\n ? (p.tabs as { children?: B[] }[]).map((t) => (Array.isArray(t.children) ? t.children : []))\n : [];\n default:\n return [];\n }\n}\n\n/** Find a block by id anywhere in a tree (descends every container). */\nexport function findBlockById<B extends ResolverBlock>(blocks: B[], id: string): B | null {\n for (const b of blocks) {\n if (b?.id === id) return b;\n for (const child of childArrays(b)) {\n const found = findBlockById(child, id);\n if (found) return found;\n }\n }\n return null;\n}\n\n/** Write `value` at a dotted path (e.g. \"props.text\"), creating objects as needed. */\nexport function setPath(obj: Record<string, unknown>, path: string, value: unknown): void {\n const keys = path.split(\".\");\n let cur: Record<string, unknown> = obj;\n for (let i = 0; i < keys.length - 1; i++) {\n const k = keys[i]!;\n if (typeof cur[k] !== \"object\" || cur[k] === null) cur[k] = {};\n cur = cur[k] as Record<string, unknown>;\n }\n cur[keys[keys.length - 1]!] = value;\n}\n\n/**\n * Apply an instance's `overrides` onto a clone of a component's blockJson, honoring the\n * declared `props[]` allowlist (key → target block + path). Returns the original array\n * untouched (referentially stable) when there's nothing to override; returns [] for a\n * non-array input.\n */\nexport function applyOverrides<B extends ResolverBlock>(\n blockJson: unknown,\n propDefs: ResolverPropDef[],\n overrides: Record<string, unknown> | undefined,\n): B[] {\n if (!Array.isArray(blockJson)) return [];\n if (!propDefs.length || !overrides || Object.keys(overrides).length === 0) {\n return blockJson as B[];\n }\n const clone = structuredClone(blockJson) as B[];\n for (const def of propDefs) {\n if (!(def.key in overrides)) continue;\n const target = findBlockById(clone, def.target.blockId);\n if (target) setPath(target as unknown as Record<string, unknown>, def.target.path, overrides[def.key]);\n }\n return clone;\n}\n","\"use client\";\n\n/**\n * <BcmsForm> — render a BetterCMS form natively in React, the headless way.\n *\n * Pass a form straight from your content snapshot (`readSnapshot().forms[n]`) or\n * the delivery API. It renders real inputs (no iframe), handles conditional\n * `showIf` fields, URL-query prefill, the honeypot, an optional Turnstile widget,\n * and submits via `@bettercms-ai/sdk`'s `submitForm`. All visible markup is\n * unstyled and class-driven, so the host site owns the look.\n */\n\nimport { useEffect, useMemo, useState, type FormEvent, type ReactElement } from \"react\";\nimport {\n submitForm,\n shouldShowField,\n formInitialValues,\n type DeliveryForm,\n type DeliveryFormField,\n type FormValues,\n type FormSubmitError,\n} from \"@bettercms-ai/sdk\";\n\nconst TURNSTILE_SRC = \"https://challenges.cloudflare.com/turnstile/v0/api.js\";\n\n// Field type → native <input type>. Other types render their own element.\nconst INPUT_TYPES: Partial<Record<DeliveryFormField[\"type\"], string>> = {\n text: \"text\",\n email: \"email\",\n phone: \"tel\",\n url: \"url\",\n number: \"number\",\n date: \"date\",\n};\n\nexport interface BcmsFormProps {\n /** A form from your content snapshot or the delivery API. */\n form: DeliveryForm;\n /** API origin. Defaults to the SDK default (https://api.bettercms.ai). */\n apiBase?: string;\n /** Turnstile site key — needed to render the widget when `form.turnstileEnabled`. */\n turnstileSiteKey?: string;\n /** Called with the new submission id after a successful submit (before any redirect). */\n onSubmitted?: (submissionId: string) => void;\n /** Class for the <form> element; field wrappers/status use `bcms-*` classes. */\n className?: string;\n}\n\ntype Status = { state: \"idle\" | \"submitting\" | \"error\" | \"success\"; message?: string };\n\nfunction readPrefill(): Record<string, string> {\n if (typeof window === \"undefined\") return {};\n const out: Record<string, string> = {};\n new URLSearchParams(window.location.search).forEach((v, k) => {\n out[k] = v;\n });\n return out;\n}\n\nfunction resetTurnstile(): void {\n const t = (globalThis as { turnstile?: { reset: () => void } }).turnstile;\n if (t) {\n try {\n t.reset();\n } catch {\n /* widget not mounted yet */\n }\n }\n}\n\nexport function BcmsForm({\n form,\n apiBase,\n turnstileSiteKey,\n onSubmitted,\n className,\n}: BcmsFormProps): ReactElement {\n const showTurnstile = Boolean(form.turnstileEnabled && turnstileSiteKey);\n const [values, setValues] = useState<FormValues>(() => {\n const init = formInitialValues(form, readPrefill());\n if (form.honeypotField) init[form.honeypotField] = \"\";\n return init;\n });\n const [status, setStatus] = useState<Status>({ state: \"idle\" });\n const [fieldErrors, setFieldErrors] = useState<Record<string, string>>({});\n\n // Cloudflare auto-renders any `.cf-turnstile` element once its script is present.\n useEffect(() => {\n if (!showTurnstile || document.querySelector(`script[src=\"${TURNSTILE_SRC}\"]`)) return;\n const script = document.createElement(\"script\");\n script.src = TURNSTILE_SRC;\n script.async = true;\n script.defer = true;\n document.head.appendChild(script);\n }, [showTurnstile]);\n\n const visibleFields = useMemo(\n () => form.fields.filter((f) => f.type !== \"hidden\" && shouldShowField(f, values)),\n [form.fields, values],\n );\n\n const setValue = (key: string, value: string | boolean) =>\n setValues((prev) => ({ ...prev, [key]: value }));\n\n async function handleSubmit(e: FormEvent<HTMLFormElement>): Promise<void> {\n e.preventDefault();\n setStatus({ state: \"submitting\" });\n setFieldErrors({});\n\n const tokenEl = e.currentTarget.querySelector<HTMLInputElement>(\n '[name=\"cf-turnstile-response\"]',\n );\n const turnstileToken = tokenEl?.value || undefined;\n\n // Drop conditionally-hidden fields so a hidden required field can't block submit;\n // keep hidden-type fields and the honeypot (the API classifies bots on it).\n const data: FormValues = {};\n for (const field of form.fields) {\n if (field.type === \"hidden\" || shouldShowField(field, values)) {\n data[field.key] = values[field.key];\n }\n }\n if (form.honeypotField) data[form.honeypotField] = values[form.honeypotField] ?? \"\";\n\n try {\n const { id } = await submitForm({ formId: form.id, data, turnstileToken, baseUrl: apiBase });\n onSubmitted?.(id);\n if (form.redirectUrl) {\n window.location.href = form.redirectUrl;\n return;\n }\n const reset = formInitialValues(form);\n if (form.honeypotField) reset[form.honeypotField] = \"\";\n setValues(reset);\n setStatus({\n state: \"success\",\n message: form.successMessage ?? \"Thanks! Your submission was received.\",\n });\n resetTurnstile();\n } catch (err) {\n const e2 = err as FormSubmitError;\n if (e2.fieldErrors) setFieldErrors(e2.fieldErrors);\n setStatus({ state: \"error\", message: e2.message || \"Something went wrong. Please try again.\" });\n resetTurnstile();\n }\n }\n\n return (\n <form className={className} onSubmit={handleSubmit} noValidate>\n {visibleFields.map((field) => (\n <Field\n key={field.key}\n field={field}\n value={values[field.key]}\n error={fieldErrors[field.key]}\n onChange={(v) => setValue(field.key, v)}\n />\n ))}\n\n {form.honeypotField ? (\n <span aria-hidden=\"true\" style={{ position: \"absolute\", left: \"-9999px\" }}>\n <input\n type=\"text\"\n name={form.honeypotField}\n tabIndex={-1}\n autoComplete=\"off\"\n value={String(values[form.honeypotField] ?? \"\")}\n onChange={(e) => setValue(form.honeypotField as string, e.target.value)}\n />\n </span>\n ) : null}\n\n {showTurnstile ? <div className=\"cf-turnstile\" data-sitekey={turnstileSiteKey} /> : null}\n\n <button type=\"submit\" disabled={status.state === \"submitting\"}>\n {status.state === \"submitting\" ? \"Submitting…\" : form.submitLabel ?? \"Submit\"}\n </button>\n\n {status.message ? (\n <p\n className={`bcms-form-status bcms-form-status--${status.state}`}\n role=\"status\"\n aria-live=\"polite\"\n >\n {status.message}\n </p>\n ) : null}\n </form>\n );\n}\n\ninterface FieldProps {\n field: DeliveryFormField;\n value: string | boolean;\n error?: string;\n onChange: (value: string | boolean) => void;\n}\n\nfunction Field({ field, value, error, onChange }: FieldProps): ReactElement {\n const required = field.required;\n const errorNode = error ? (\n <span className=\"bcms-field-error\" role=\"alert\">\n {error}\n </span>\n ) : null;\n\n if (field.type === \"checkbox\" || field.type === \"consent\") {\n return (\n <label className=\"bcms-field bcms-field--checkbox\" data-field={field.key}>\n <input\n type=\"checkbox\"\n name={field.key}\n required={required}\n checked={Boolean(value)}\n onChange={(e) => onChange(e.target.checked)}\n />{\" \"}\n {field.label}\n {required ? \" *\" : \"\"}\n {errorNode}\n </label>\n );\n }\n\n const str = String(value ?? \"\");\n let control: ReactElement;\n if (field.type === \"textarea\") {\n control = (\n <textarea\n name={field.key}\n placeholder={field.placeholder}\n required={required}\n value={str}\n onChange={(e) => onChange(e.target.value)}\n />\n );\n } else if (field.type === \"select\") {\n control = (\n <select name={field.key} required={required} value={str} onChange={(e) => onChange(e.target.value)}>\n <option value=\"\" disabled>\n Choose…\n </option>\n {(field.options ?? []).map((opt) => (\n <option key={opt} value={opt}>\n {opt}\n </option>\n ))}\n </select>\n );\n } else {\n control = (\n <input\n type={INPUT_TYPES[field.type] ?? \"text\"}\n name={field.key}\n placeholder={field.placeholder}\n required={required}\n value={str}\n onChange={(e) => onChange(e.target.value)}\n />\n );\n }\n\n return (\n <p className=\"bcms-field\" data-field={field.key}>\n <label>\n {field.label}\n {required ? \" *\" : \"\"}\n </label>\n <br />\n {control}\n {errorNode}\n </p>\n );\n}\n"],"mappings":"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","names":["useState","jsx","jsxs","useState"]}
1
+ {"version":3,"sources":["../src/blocks.tsx","../src/form.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * <BcmsBlocks> — render a BetterCMS page's block_json natively in React.\n *\n * Pass the `blocks` from a page (delivery API `/content/:slug` → `entry.blocks`)\n * plus the `forms` you read with `readForms()`. Every block type renders to plain,\n * class-driven markup (the host site owns the styling); a `form` block resolves its\n * `formId` against `forms` and renders `<BcmsForm>` inline exactly where the user\n * placed it in the page builder. This is the turnkey \"Webflow\" path — forms show up\n * wherever they were dropped on the page, with no manual wiring.\n *\n * Ships from its own \"use client\" entry (like <BcmsForm>) so it can be rendered from\n * a Next App Router Server Component without breaking the RSC boundary.\n */\n\nimport { useState, type CSSProperties, type ReactElement, type ReactNode } from \"react\";\nimport { blockStyleToCss } from \"@bettercms-ai/types\";\nimport type {\n ContentBlock,\n HeadingBlock,\n TextBlock,\n ImageBlock,\n ButtonBlock,\n SpacerBlock,\n VideoBlock,\n ColumnsBlock,\n FormBlock,\n SectionBlock,\n NavbarBlock,\n FooterBlock,\n SliderBlock,\n TabsBlock,\n ComponentBlock,\n DeliveryComponent,\n} from \"@bettercms-ai/types\";\nimport type { DeliveryForm } from \"@bettercms-ai/sdk\";\nimport { applyOverrides } from \"@bettercms-ai/ui/resolve\";\nimport { BcmsForm } from \"./form.js\";\n\nconst MAX_COMPONENT_DEPTH = 10;\n\nexport interface BcmsBlocksProps {\n /** A page's blocks (delivery API `/content/:slug` → `entry.blocks`). */\n blocks: ContentBlock[];\n /** Forms available to `form` blocks — from `readForms().forms`. */\n forms?: DeliveryForm[];\n /** Reusable components available to `component` blocks — from `readComponents()`. */\n components?: DeliveryComponent[];\n /** Turnstile site key — forwarded to `<BcmsForm>` for form blocks. */\n turnstileSiteKey?: string;\n /** API origin forwarded to `<BcmsForm>`. Defaults to the SDK default. */\n apiBase?: string;\n /** Class for the wrapping <div>. */\n className?: string;\n}\n\n/** Render a page's blocks. Unknown/empty blocks render nothing (fail-soft). */\nexport function BcmsBlocks({\n blocks,\n forms,\n components,\n turnstileSiteKey,\n apiBase,\n className,\n}: BcmsBlocksProps): ReactElement {\n const formsById = new Map((forms ?? []).map((f) => [f.id, f]));\n const componentsById = new Map((components ?? []).map((cmp) => [cmp.id, cmp]));\n const ctx: RenderCtx = { formsById, componentsById, turnstileSiteKey, apiBase, depth: 0, seen: new Set() };\n return <div className={className}>{blocks.map((block) => renderBlock(block, ctx))}</div>;\n}\n\ninterface RenderCtx {\n formsById: Map<string, DeliveryForm>;\n componentsById: Map<string, DeliveryComponent>;\n turnstileSiteKey?: string;\n apiBase?: string;\n depth: number;\n seen: Set<string>;\n}\n\nfunction renderBlock(block: ContentBlock, ctx: RenderCtx): ReactNode {\n const node = renderBlockInner(block, ctx);\n const style = blockStyleToCss(block.style);\n if (!style || node == null) return node;\n // FLO-264: honor the block's bounded style tokens in the headless render too, so\n // @bettercms-ai/next output matches the builder canvas + hosted site.\n return (\n <div key={block.id} style={style as CSSProperties}>\n {node}\n </div>\n );\n}\n\nfunction renderBlockInner(block: ContentBlock, ctx: RenderCtx): ReactNode {\n switch (block.type) {\n case \"heading\":\n return renderHeading(block);\n case \"text\":\n return renderText(block);\n case \"image\":\n return renderImage(block);\n case \"button\":\n return renderButton(block);\n case \"spacer\":\n return renderSpacer(block);\n case \"video\":\n return renderVideo(block);\n case \"columns\":\n return renderColumns(block, ctx);\n case \"form\":\n return renderForm(block, ctx);\n case \"section\":\n return renderSection(block, ctx);\n case \"navbar\":\n return renderNavbar(block);\n case \"footer\":\n return renderFooter(block);\n case \"slider\":\n return <BcmsSlider key={block.id} block={block} ctx={ctx} />;\n case \"tabs\":\n return <BcmsTabs key={block.id} block={block} ctx={ctx} />;\n case \"component\":\n return renderComponent(block, ctx);\n default:\n return null;\n }\n}\n\nfunction renderHeading({ id, props }: HeadingBlock): ReactNode {\n const Tag = `h${props.level ?? 2}` as \"h1\" | \"h2\" | \"h3\";\n return (\n <Tag key={id} className=\"bcms-block bcms-block--heading\" data-bcms-field={`${id}__text`}>\n {props.text}\n </Tag>\n );\n}\n\nfunction renderText({ id, props }: TextBlock): ReactNode {\n // `html` is sanitized server-side before storage.\n return (\n <div\n key={id}\n className=\"bcms-block bcms-block--text\"\n data-bcms-field={`${id}__html`}\n data-bcms-kind=\"richtext\"\n dangerouslySetInnerHTML={{ __html: props.html }}\n />\n );\n}\n\nfunction renderImage({ id, props }: ImageBlock): ReactNode {\n const img = (\n <img\n src={props.src}\n alt={props.alt}\n width={props.width}\n height={props.height}\n loading=\"lazy\"\n // Heading/text/button have always been click-to-edit on the Visual Editor canvas because\n // they carry this attribute; the image block did not, so a block page's images were the\n // one thing you could only change from the dock. `kind=\"image\"` keeps it selection-only\n // (no contentEditable on an <img>) while letting the editor reflect a new src live.\n data-bcms-field={`${id}__src`}\n data-bcms-kind=\"image\"\n />\n );\n if (!props.caption) {\n return (\n <figure key={id} className=\"bcms-block bcms-block--image\">\n {img}\n </figure>\n );\n }\n return (\n <figure key={id} className=\"bcms-block bcms-block--image\">\n {img}\n <figcaption>{props.caption}</figcaption>\n </figure>\n );\n}\n\nfunction renderButton({ id, props }: ButtonBlock): ReactNode {\n return (\n <a\n key={id}\n href={props.href}\n className={`bcms-block bcms-block--button bcms-button--${props.variant ?? \"primary\"}`}\n data-bcms-field={`${id}__text`}\n >\n {props.text}\n </a>\n );\n}\n\nfunction renderSpacer({ id, props }: SpacerBlock): ReactNode {\n return (\n <div\n key={id}\n className=\"bcms-block bcms-block--spacer\"\n style={{ height: props.height }}\n aria-hidden=\"true\"\n />\n );\n}\n\nfunction renderVideo({ id, props }: VideoBlock): ReactNode {\n return (\n <video\n key={id}\n className=\"bcms-block bcms-block--video\"\n src={props.url}\n poster={props.poster}\n autoPlay={props.autoplay}\n loop={props.loop}\n controls\n />\n );\n}\n\nfunction renderColumns({ id, props }: ColumnsBlock, ctx: RenderCtx): ReactNode {\n return (\n <div\n key={id}\n className=\"bcms-block bcms-block--columns\"\n style={{\n display: \"grid\",\n gridTemplateColumns: `repeat(${props.columns.length || 1}, 1fr)`,\n gap: props.gap,\n }}\n >\n {props.columns.map((column, i) => (\n <div key={i} className=\"bcms-column\">\n {column.map((child) => renderBlock(child, ctx))}\n </div>\n ))}\n </div>\n );\n}\n\nfunction renderForm({ id, props }: FormBlock, ctx: RenderCtx): ReactNode {\n const form = ctx.formsById.get(props.formId);\n if (!form) return null; // form deleted or not delivered — render nothing\n return (\n <div key={id} className=\"bcms-block bcms-block--form\">\n <BcmsForm form={form} turnstileSiteKey={ctx.turnstileSiteKey} apiBase={ctx.apiBase} />\n </div>\n );\n}\n\nfunction renderSection({ id, props }: SectionBlock, ctx: RenderCtx): ReactNode {\n const inner = props.children.map((child) => renderBlock(child, ctx));\n return (\n <section\n key={id}\n className=\"bcms-block bcms-block--section\"\n style={{ background: props.background, padding: props.paddingY != null ? `${props.paddingY}px 0` : undefined }}\n >\n {props.maxWidth ? <div style={{ maxWidth: props.maxWidth, margin: \"0 auto\" }}>{inner}</div> : inner}\n </section>\n );\n}\n\nfunction renderNavbar({ id, props }: NavbarBlock): ReactNode {\n return (\n <nav key={id} className=\"bcms-block bcms-block--navbar\">\n {props.logo?.src ? (\n <a className=\"bcms-navbar-logo\" href={props.logo.href ?? \"/\"}>\n <img src={props.logo.src} alt={props.logo.alt} />\n </a>\n ) : null}\n <div className=\"bcms-navbar-links\">\n {props.links.map((l, i) => (\n <a key={i} href={l.href}>\n {l.label}\n </a>\n ))}\n </div>\n {props.cta ? (\n <a className=\"bcms-button bcms-button--primary\" href={props.cta.href}>\n {props.cta.text}\n </a>\n ) : null}\n </nav>\n );\n}\n\nfunction renderFooter({ id, props }: FooterBlock): ReactNode {\n return (\n <footer key={id} className=\"bcms-block bcms-block--footer\">\n <div className=\"bcms-footer-cols\">\n {props.columns.map((col, i) => (\n <div key={i} className=\"bcms-footer-col\">\n {col.heading ? <h4>{col.heading}</h4> : null}\n <ul>\n {col.links.map((l, j) => (\n <li key={j}>\n <a href={l.href}>{l.label}</a>\n </li>\n ))}\n </ul>\n </div>\n ))}\n </div>\n {props.copyright ? <p className=\"bcms-footer-copy\">{props.copyright}</p> : null}\n </footer>\n );\n}\n\nfunction BcmsSlider({ block, ctx }: { block: SliderBlock; ctx: RenderCtx }): ReactElement {\n const { slides } = block.props;\n const [i, setI] = useState(0);\n const go = (n: number) => setI((n + slides.length) % (slides.length || 1));\n return (\n <div className=\"bcms-block bcms-block--slider\" style={{ position: \"relative\" }}>\n {slides.map((s, idx) => (\n <div key={s.id} className=\"bcms-slide\" hidden={idx !== i}>\n {s.children.map((child) => renderBlock(child, ctx))}\n </div>\n ))}\n <button type=\"button\" className=\"bcms-slider-prev\" aria-label=\"Previous\" onClick={() => go(i - 1)}>\n ‹\n </button>\n <button type=\"button\" className=\"bcms-slider-next\" aria-label=\"Next\" onClick={() => go(i + 1)}>\n ›\n </button>\n </div>\n );\n}\n\nfunction BcmsTabs({ block, ctx }: { block: TabsBlock; ctx: RenderCtx }): ReactElement {\n const { tabs } = block.props;\n const [active, setActive] = useState(0);\n return (\n <div className=\"bcms-block bcms-block--tabs\">\n <div className=\"bcms-tablist\" role=\"tablist\">\n {tabs.map((t, idx) => (\n <button\n key={t.id}\n role=\"tab\"\n type=\"button\"\n className=\"bcms-tab\"\n aria-selected={idx === active}\n onClick={() => setActive(idx)}\n >\n {t.label}\n </button>\n ))}\n </div>\n {tabs.map((t, idx) => (\n <div key={t.id} role=\"tabpanel\" className=\"bcms-tabpanel\" hidden={idx !== active}>\n {t.children.map((child) => renderBlock(child, ctx))}\n </div>\n ))}\n </div>\n );\n}\n\nfunction renderComponent({ id, props }: ComponentBlock, ctx: RenderCtx): ReactNode {\n const def = ctx.componentsById.get(props.componentId);\n if (!def) return null; // not delivered — fail-soft\n if (ctx.depth >= MAX_COMPONENT_DEPTH || ctx.seen.has(def.id)) return null; // cycle/depth guard\n const resolved = applyOverrides<ContentBlock>(def.blockJson, def.props ?? [], props.overrides ?? {});\n const childCtx: RenderCtx = { ...ctx, depth: ctx.depth + 1, seen: new Set([...ctx.seen, def.id]) };\n return (\n <div key={id} className=\"bcms-block bcms-block--component\">\n {resolved.map((child) => renderBlock(child, childCtx))}\n </div>\n );\n}\n\n","\"use client\";\n\n/**\n * <BcmsForm> — render a BetterCMS form natively in React, the headless way.\n *\n * Pass a form straight from your content snapshot (`readSnapshot().forms[n]`) or\n * the delivery API. It renders real inputs (no iframe), handles conditional\n * `showIf` fields, URL-query prefill, the honeypot, an optional Turnstile widget,\n * and submits via `@bettercms-ai/sdk`'s `submitForm`. All visible markup is\n * unstyled and class-driven, so the host site owns the look.\n */\n\nimport { useEffect, useMemo, useState, type FormEvent, type ReactElement } from \"react\";\nimport {\n submitForm,\n shouldShowField,\n formInitialValues,\n type DeliveryForm,\n type DeliveryFormField,\n type FormValues,\n type FormSubmitError,\n} from \"@bettercms-ai/sdk\";\n\nconst TURNSTILE_SRC = \"https://challenges.cloudflare.com/turnstile/v0/api.js\";\n\n// Field type → native <input type>. Other types render their own element.\nconst INPUT_TYPES: Partial<Record<DeliveryFormField[\"type\"], string>> = {\n text: \"text\",\n email: \"email\",\n phone: \"tel\",\n url: \"url\",\n number: \"number\",\n date: \"date\",\n};\n\nexport interface BcmsFormProps {\n /** A form from your content snapshot or the delivery API. */\n form: DeliveryForm;\n /** API origin. Defaults to the SDK default (https://api.bettercms.ai). */\n apiBase?: string;\n /** Turnstile site key — needed to render the widget when `form.turnstileEnabled`. */\n turnstileSiteKey?: string;\n /** Called with the new submission id after a successful submit (before any redirect). */\n onSubmitted?: (submissionId: string) => void;\n /** Class for the <form> element; field wrappers/status use `bcms-*` classes. */\n className?: string;\n}\n\ntype Status = { state: \"idle\" | \"submitting\" | \"error\" | \"success\"; message?: string };\n\nfunction readPrefill(): Record<string, string> {\n if (typeof window === \"undefined\") return {};\n const out: Record<string, string> = {};\n new URLSearchParams(window.location.search).forEach((v, k) => {\n out[k] = v;\n });\n return out;\n}\n\nfunction resetTurnstile(): void {\n const t = (globalThis as { turnstile?: { reset: () => void } }).turnstile;\n if (t) {\n try {\n t.reset();\n } catch {\n /* widget not mounted yet */\n }\n }\n}\n\nexport function BcmsForm({\n form,\n apiBase,\n turnstileSiteKey,\n onSubmitted,\n className,\n}: BcmsFormProps): ReactElement {\n const showTurnstile = Boolean(form.turnstileEnabled && turnstileSiteKey);\n const [values, setValues] = useState<FormValues>(() => {\n const init = formInitialValues(form, readPrefill());\n if (form.honeypotField) init[form.honeypotField] = \"\";\n return init;\n });\n const [status, setStatus] = useState<Status>({ state: \"idle\" });\n const [fieldErrors, setFieldErrors] = useState<Record<string, string>>({});\n\n // Cloudflare auto-renders any `.cf-turnstile` element once its script is present.\n useEffect(() => {\n if (!showTurnstile || document.querySelector(`script[src=\"${TURNSTILE_SRC}\"]`)) return;\n const script = document.createElement(\"script\");\n script.src = TURNSTILE_SRC;\n script.async = true;\n script.defer = true;\n document.head.appendChild(script);\n }, [showTurnstile]);\n\n const visibleFields = useMemo(\n () => form.fields.filter((f) => f.type !== \"hidden\" && shouldShowField(f, values)),\n [form.fields, values],\n );\n\n const setValue = (key: string, value: string | boolean) =>\n setValues((prev) => ({ ...prev, [key]: value }));\n\n async function handleSubmit(e: FormEvent<HTMLFormElement>): Promise<void> {\n e.preventDefault();\n setStatus({ state: \"submitting\" });\n setFieldErrors({});\n\n const tokenEl = e.currentTarget.querySelector<HTMLInputElement>(\n '[name=\"cf-turnstile-response\"]',\n );\n const turnstileToken = tokenEl?.value || undefined;\n\n // Drop conditionally-hidden fields so a hidden required field can't block submit;\n // keep hidden-type fields and the honeypot (the API classifies bots on it).\n const data: FormValues = {};\n for (const field of form.fields) {\n if (field.type === \"hidden\" || shouldShowField(field, values)) {\n data[field.key] = values[field.key];\n }\n }\n if (form.honeypotField) data[form.honeypotField] = values[form.honeypotField] ?? \"\";\n\n try {\n const { id } = await submitForm({ formId: form.id, data, turnstileToken, baseUrl: apiBase });\n onSubmitted?.(id);\n if (form.redirectUrl) {\n window.location.href = form.redirectUrl;\n return;\n }\n const reset = formInitialValues(form);\n if (form.honeypotField) reset[form.honeypotField] = \"\";\n setValues(reset);\n setStatus({\n state: \"success\",\n message: form.successMessage ?? \"Thanks! Your submission was received.\",\n });\n resetTurnstile();\n } catch (err) {\n const e2 = err as FormSubmitError;\n if (e2.fieldErrors) setFieldErrors(e2.fieldErrors);\n setStatus({ state: \"error\", message: e2.message || \"Something went wrong. Please try again.\" });\n resetTurnstile();\n }\n }\n\n return (\n <form className={className} onSubmit={handleSubmit} noValidate>\n {visibleFields.map((field) => (\n <Field\n key={field.key}\n field={field}\n value={values[field.key]}\n error={fieldErrors[field.key]}\n onChange={(v) => setValue(field.key, v)}\n />\n ))}\n\n {form.honeypotField ? (\n <span aria-hidden=\"true\" style={{ position: \"absolute\", left: \"-9999px\" }}>\n <input\n type=\"text\"\n name={form.honeypotField}\n tabIndex={-1}\n autoComplete=\"off\"\n value={String(values[form.honeypotField] ?? \"\")}\n onChange={(e) => setValue(form.honeypotField as string, e.target.value)}\n />\n </span>\n ) : null}\n\n {showTurnstile ? <div className=\"cf-turnstile\" data-sitekey={turnstileSiteKey} /> : null}\n\n <button type=\"submit\" disabled={status.state === \"submitting\"}>\n {status.state === \"submitting\" ? \"Submitting…\" : form.submitLabel ?? \"Submit\"}\n </button>\n\n {status.message ? (\n <p\n className={`bcms-form-status bcms-form-status--${status.state}`}\n role=\"status\"\n aria-live=\"polite\"\n >\n {status.message}\n </p>\n ) : null}\n </form>\n );\n}\n\ninterface FieldProps {\n field: DeliveryFormField;\n value: string | boolean;\n error?: string;\n onChange: (value: string | boolean) => void;\n}\n\nfunction Field({ field, value, error, onChange }: FieldProps): ReactElement {\n const required = field.required;\n const errorNode = error ? (\n <span className=\"bcms-field-error\" role=\"alert\">\n {error}\n </span>\n ) : null;\n\n if (field.type === \"checkbox\" || field.type === \"consent\") {\n return (\n <label className=\"bcms-field bcms-field--checkbox\" data-field={field.key}>\n <input\n type=\"checkbox\"\n name={field.key}\n required={required}\n checked={Boolean(value)}\n onChange={(e) => onChange(e.target.checked)}\n />{\" \"}\n {field.label}\n {required ? \" *\" : \"\"}\n {errorNode}\n </label>\n );\n }\n\n const str = String(value ?? \"\");\n let control: ReactElement;\n if (field.type === \"textarea\") {\n control = (\n <textarea\n name={field.key}\n placeholder={field.placeholder}\n required={required}\n value={str}\n onChange={(e) => onChange(e.target.value)}\n />\n );\n } else if (field.type === \"select\") {\n control = (\n <select name={field.key} required={required} value={str} onChange={(e) => onChange(e.target.value)}>\n <option value=\"\" disabled>\n Choose…\n </option>\n {(field.options ?? []).map((opt) => (\n <option key={opt} value={opt}>\n {opt}\n </option>\n ))}\n </select>\n );\n } else {\n control = (\n <input\n type={INPUT_TYPES[field.type] ?? \"text\"}\n name={field.key}\n placeholder={field.placeholder}\n required={required}\n value={str}\n onChange={(e) => onChange(e.target.value)}\n />\n );\n }\n\n return (\n <p className=\"bcms-field\" data-field={field.key}>\n <label>\n {field.label}\n {required ? \" *\" : \"\"}\n </label>\n <br />\n {control}\n {errorNode}\n </p>\n );\n}\n"],"mappings":"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","names":["useState","jsx","jsxs","useState"]}
package/dist/index.d.ts CHANGED
@@ -4,8 +4,8 @@ import { Metadata } from 'next';
4
4
  import { SeoInput, DeliveryForm } from '@bettercms-ai/sdk';
5
5
  export { ResolvedSeo, SeoInput, resolveSeo } from '@bettercms-ai/sdk';
6
6
  export { ImageFit, ImageFormat, ImageSource, ImageUrlBuilder, imageUrl, default as imageUrlBuilder } from '@bettercms-ai/image-url';
7
- import { ContentBlock as ContentBlock$1, DeliveryComponent as DeliveryComponent$1 } from '@betttercms/types';
8
- import { DeliveryForm as DeliveryForm$1 } from '@betttercms/sdk';
7
+ import * as react_jsx_runtime from 'react/jsx-runtime';
8
+ import { ElementType, ReactNode } from 'react';
9
9
 
10
10
  /**
11
11
  * Public types for @bettercms-ai/next.
@@ -459,11 +459,11 @@ interface BcmsFormProps {
459
459
 
460
460
  interface BcmsBlocksProps {
461
461
  /** A page's blocks (delivery API `/content/:slug` → `entry.blocks`). */
462
- blocks: ContentBlock$1[];
462
+ blocks: ContentBlock[];
463
463
  /** Forms available to `form` blocks — from `readForms().forms`. */
464
- forms?: DeliveryForm$1[];
464
+ forms?: DeliveryForm[];
465
465
  /** Reusable components available to `component` blocks — from `readComponents()`. */
466
- components?: DeliveryComponent$1[];
466
+ components?: DeliveryComponent[];
467
467
  /** Turnstile site key — forwarded to `<BcmsForm>` for form blocks. */
468
468
  turnstileSiteKey?: string;
469
469
  /** API origin forwarded to `<BcmsForm>`. Defaults to the SDK default. */
@@ -472,6 +472,17 @@ interface BcmsBlocksProps {
472
472
  className?: string;
473
473
  }
474
474
 
475
+ interface BcmsFieldProps {
476
+ /** Field path in the editor grammar, e.g. `title` or `facts[2].label`. */
477
+ path: string;
478
+ /** Kind hint the editor's reflection branches on. Omit for plain text/number. */
479
+ kind?: "richtext" | "image";
480
+ /** Wrapper element. Defaults to a `display:contents` span. */
481
+ as?: ElementType;
482
+ children: ReactNode;
483
+ }
484
+ declare function BcmsField({ path, kind, as, children }: BcmsFieldProps): react_jsx_runtime.JSX.Element;
485
+
475
486
  interface BcmsLiveProps {
476
487
  /** Live SSE URL, e.g. from `defineBetterCMSLive().liveSrc`. */
477
488
  src: string;
@@ -513,4 +524,4 @@ interface VisualEditingProps {
513
524
  studioUrl?: string;
514
525
  }
515
526
 
516
- export { type BcmsBlocksProps, type BcmsFormProps, type BcmsLiveProps, type BetterCMSConfig, type BetterCMSEntry, BetterCMSError, type BetterCMSLiveConfig, type BetterCMSNext, type BetterCMSPage, type ContentSnapshot, type ContentSource, DEFAULT_SNAPSHOT_FILE, type DraftModeRouteConfig, type EntryList, type GetEntryOptions, type ListEntriesOptions, type LlmsModel, type LlmsModelField, type LlmsTxtOptions, type LlmsTxtRouteConfig, PREVIEW_TOKEN_COOKIE, type ReadOptions, type RevalidateRouteConfig, SNAPSHOT_VERSION, type SnapshotForms, type SnapshotPage, type VisualEditingProps, buildMetadata, buildSnapshot, createBetterCMS, createDisableDraftRoute, createDraftModeRoute, createRevalidateRoute, defineBetterCMSLive, fetchModels, generateLlmsTxt, getComponent, getContent, getForm, getPage, getPreviewToken, isDraftEnabled, llmsTxtRoute, parseSnapshot, readComponents, readForms, readPages, readSnapshot, serializeSnapshot, writeSnapshot };
527
+ export { type BcmsBlocksProps, BcmsField, type BcmsFieldProps, type BcmsFormProps, type BcmsLiveProps, type BetterCMSConfig, type BetterCMSEntry, BetterCMSError, type BetterCMSLiveConfig, type BetterCMSNext, type BetterCMSPage, type ContentSnapshot, type ContentSource, DEFAULT_SNAPSHOT_FILE, type DraftModeRouteConfig, type EntryList, type GetEntryOptions, type ListEntriesOptions, type LlmsModel, type LlmsModelField, type LlmsTxtOptions, type LlmsTxtRouteConfig, PREVIEW_TOKEN_COOKIE, type ReadOptions, type RevalidateRouteConfig, SNAPSHOT_VERSION, type SnapshotForms, type SnapshotPage, type VisualEditingProps, buildMetadata, buildSnapshot, createBetterCMS, createDisableDraftRoute, createDraftModeRoute, createRevalidateRoute, defineBetterCMSLive, fetchModels, generateLlmsTxt, getComponent, getContent, getForm, getPage, getPreviewToken, isDraftEnabled, llmsTxtRoute, parseSnapshot, readComponents, readForms, readPages, readSnapshot, serializeSnapshot, writeSnapshot };
package/dist/index.js CHANGED
@@ -511,6 +511,21 @@ function getComponent(idOrSlug, path) {
511
511
  );
512
512
  }
513
513
 
514
+ // src/field.tsx
515
+ import { jsx } from "react/jsx-runtime";
516
+ function BcmsField({ path, kind, as, children }) {
517
+ const Tag = as ?? "span";
518
+ return /* @__PURE__ */ jsx(
519
+ Tag,
520
+ {
521
+ "data-bcms-field": path,
522
+ "data-bcms-kind": kind,
523
+ style: as ? void 0 : { display: "contents" },
524
+ children
525
+ }
526
+ );
527
+ }
528
+
514
529
  // src/live-config.ts
515
530
  function defineBetterCMSLive(config) {
516
531
  const base = config.apiUrl.replace(/\/+$/, "");
@@ -549,6 +564,7 @@ function createRevalidateRoute(config = {}) {
549
564
  return { POST };
550
565
  }
551
566
  export {
567
+ BcmsField,
552
568
  BetterCMSError,
553
569
  DEFAULT_SNAPSHOT_FILE,
554
570
  PREVIEW_TOKEN_COOKIE,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/types.ts","../src/client.ts","../src/metadata.ts","../src/index.ts","../src/snapshot.ts","../src/llms-txt.ts","../src/preview.ts","../src/draft-route.ts","../src/forms-read.ts","../src/pages-read.ts","../src/components-read.ts","../src/live-config.ts","../src/revalidate-route.ts"],"sourcesContent":["/**\n * Public types for @bettercms-ai/next.\n *\n * `BetterCMSEntry` mirrors the envelope emitted by @bettercms-ai/codegen's generated\n * file. The adapter normalizes the raw Delivery API response into exactly this shape,\n * so `getEntry`/`listEntries` line up 1:1 with your generated model types.\n */\n\n/** Normalized error thrown by adapter reads. Mirrors the SDK's BetterCMSError, but kept\n * local so the adapter carries no heavy runtime dependency into a consumer's bundle. */\nexport class BetterCMSError extends Error {\n readonly status: number;\n readonly code: string;\n\n constructor(message: string, status: number, code: string) {\n super(message);\n this.name = \"BetterCMSError\";\n this.status = status;\n this.code = code;\n }\n}\n\n/**\n * Delivery envelope around a model's typed `fields`. Returned by `getEntry`/`listEntries`.\n * Identical to the `BetterCMSEntry<TFields>` your generated types declare.\n *\n * `publishedAt`/`updatedAt` are ISO-8601 strings, or `null` when absent — never a\n * fabricated empty string (so `new Date(...)` is safe to attempt only on non-null).\n */\nexport interface BetterCMSEntry<TFields> {\n readonly slug: string;\n readonly status: \"draft\" | \"published\";\n readonly fields: TFields;\n readonly publishedAt: string | null;\n readonly updatedAt: string | null;\n}\n\nexport interface BetterCMSConfig {\n /** Workspace slug — the `:workspace` segment of the Delivery API path. */\n workspace: string;\n /** Optional delivery API key (sent as `Authorization: Bearer`). */\n apiKey?: string;\n /** Delivery API base. Default: `https://api.bettercms.ai/api/v1/delivery`. */\n baseUrl?: string;\n /** Preview API base. Default: derived from `baseUrl` (`/delivery` → `/preview`). */\n previewBaseUrl?: string;\n /**\n * Default Next.js revalidation for reads (seconds). `false` = always fresh\n * (`cache: \"no-store\"`). Per-call `revalidate` overrides this. Default: `60`.\n */\n revalidate?: number | false;\n}\n\n/** Per-read options shared by `getEntry` and `listEntries`. */\nexport interface ReadOptions {\n /** ISR window in seconds, or `false` for `cache: \"no-store\"`. Overrides config default. */\n revalidate?: number | false;\n /** Next.js cache tags for on-demand `revalidateTag()` invalidation. */\n tags?: string[];\n /** Reference hydration depth (0 = none, 1 = direct refs, 2 = nested). */\n depth?: 0 | 1 | 2;\n /** Field projection — only these field keys are returned. */\n select?: string[];\n}\n\n/** Options for `getEntry`, including draft-preview access. */\nexport interface GetEntryOptions extends ReadOptions {\n /** Fetch draft content via the preview endpoint. Requires `previewToken`. */\n preview?: boolean;\n /** Signed preview token (from `POST .../preview-token`). */\n previewToken?: string;\n}\n\n/** Options for `listEntries`. */\nexport interface ListEntriesOptions extends ReadOptions {\n /** 1-based page number. Default: 1. */\n page?: number;\n /** Items per page (max 100). Default: 20. */\n perPage?: number;\n}\n\n/** Paginated result returned by `listEntries`. */\nexport interface EntryList<TFields> {\n items: BetterCMSEntry<TFields>[];\n page: number;\n perPage: number;\n totalItems: number;\n totalPages: number;\n hasNextPage: boolean;\n hasPreviousPage: boolean;\n}\n","/**\n * createBetterCMS — the Next.js delivery client.\n *\n * Reads run through the standard `fetch`, which the Next.js App Router patches to\n * participate in the data cache. We pass `next: { revalidate, tags }` so content is\n * cached/ISR'd and can be invalidated on publish via `revalidateTag()`.\n *\n * The raw Delivery API response is normalized into `BetterCMSEntry<TFields>` so the\n * return types match what @bettercms-ai/codegen generates.\n */\n\nimport type { ContentBlock, PageMetaJson } from \"@bettercms-ai/types\";\nimport {\n BetterCMSError,\n type BetterCMSConfig,\n type BetterCMSEntry,\n type EntryList,\n type GetEntryOptions,\n type ListEntriesOptions,\n} from \"./types.js\";\n\n/** A published page with its block_json — feed `blocks` to `<BcmsBlocks>`.\n * The SEO fields (FLO-302) feed `buildMetadata` for the route's `generateMetadata`. */\nexport interface BetterCMSPage {\n slug: string;\n title: string;\n metaTitle: string | null;\n metaDescription: string | null;\n /** Rich SEO (OG / Twitter / canonical / JSON-LD). `null` when unset. */\n metaJson: PageMetaJson | null;\n blocks: ContentBlock[];\n publishedAt: string | null;\n updatedAt: string | null;\n}\n\n/** Raw single-page shape returned by the Delivery API (`/content/:slug`). */\ninterface RawPageResponse {\n data: {\n slug: string;\n entry: {\n slug: string;\n title: string;\n metaTitle?: string | null;\n metaDescription?: string | null;\n metaJson?: PageMetaJson | null;\n blocks?: ContentBlock[];\n updatedAt?: string | null;\n };\n publishedAt: string | null;\n };\n}\n\nconst DEFAULT_BASE_URL = \"https://api.bettercms.ai/api/v1/delivery\";\nconst DEFAULT_REVALIDATE = 60;\n\n/** `fetch`'s init augmented with Next.js's `next` cache directives. (`cache` is declared\n * explicitly because this package compiles without the DOM lib.) */\ntype NextRequestInit = RequestInit & {\n cache?: \"default\" | \"no-store\" | \"force-cache\";\n next?: { revalidate?: number | false; tags?: string[] };\n};\n\n/** Raw single-entry shape returned by the Delivery API. */\ninterface RawEntry {\n id: string;\n slug: string;\n status: \"draft\" | \"published\";\n publishedAt: string | null;\n updatedAt?: string | null;\n data: Record<string, unknown>;\n}\n\ninterface RawListResponse {\n data: {\n items: RawEntry[];\n page: number;\n perPage: number;\n totalItems: number;\n totalPages: number;\n hasNextPage: boolean;\n hasPreviousPage: boolean;\n };\n}\n\nfunction statusToCode(status: number): string {\n if (status === 404) return \"CONTENT_NOT_FOUND\";\n if (status === 401) return \"UNAUTHORIZED\";\n if (status === 403) return \"FORBIDDEN\";\n if (status === 429) return \"RATE_LIMITED\";\n if (status === 422) return \"VALIDATION_ERROR\";\n return \"INTERNAL_ERROR\";\n}\n\n/** Normalize a raw delivery entry into the codegen-aligned envelope. `publishedAt`\n * and `updatedAt` are kept distinct and pass through `null` rather than being\n * conflated or fabricated as \"\" (which would parse to an Invalid Date). */\nfunction mapEntry<TFields>(raw: RawEntry): BetterCMSEntry<TFields> {\n return {\n slug: raw.slug,\n status: raw.status,\n fields: raw.data as TFields,\n publishedAt: raw.publishedAt ?? null,\n updatedAt: raw.updatedAt ?? null,\n };\n}\n\n/**\n * The typed BetterCMS reader for Next.js.\n *\n * @typeParam Schema - your generated `BetterCMSSchema` (slug → fields registry). When\n * supplied, `listEntries(\"blog\")` is typed by the model's fields and the model name\n * autocompletes. Defaults to an open record so it also works untyped.\n */\nexport interface BetterCMSNext<Schema extends Record<string, unknown>> {\n /** Fetch one published entry by its content slug. Returns `null` when not found.\n * Passing `select` narrows the result to `Partial<TFields>` (projection drops fields). */\n getEntry<TFields = unknown>(\n slug: string,\n opts: GetEntryOptions & { select: string[] },\n ): Promise<BetterCMSEntry<Partial<TFields>> | null>;\n getEntry<TFields = unknown>(\n slug: string,\n opts?: GetEntryOptions,\n ): Promise<BetterCMSEntry<TFields> | null>;\n\n /** List published entries, filtered to one model. Throws `BetterCMSError`\n * (CONTENT_NOT_FOUND) for an unknown workspace/model — an empty model returns an\n * empty page. Passing `select` narrows fields to `Partial`. */\n listEntries<M extends keyof Schema & string>(\n model: M,\n opts: ListEntriesOptions & { select: string[] },\n ): Promise<EntryList<Partial<Schema[M]>>>;\n listEntries<M extends keyof Schema & string>(\n model: M,\n opts?: ListEntriesOptions,\n ): Promise<EntryList<Schema[M]>>;\n listEntries<TFields = unknown>(\n model?: string,\n opts?: ListEntriesOptions,\n ): Promise<EntryList<TFields>>;\n\n /** Fetch one published page (with its `blocks` for `<BcmsBlocks>`) by slug.\n * Returns `null` when not found. */\n getPage(\n slug: string,\n opts?: { revalidate?: number | false; tags?: string[] },\n ): Promise<BetterCMSPage | null>;\n}\n\nexport function createBetterCMS<\n Schema extends Record<string, unknown> = Record<string, unknown>,\n>(config: BetterCMSConfig): BetterCMSNext<Schema> {\n const baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n const previewBaseUrl = (\n config.previewBaseUrl ?? baseUrl.replace(/\\/delivery$/, \"/preview\")\n ).replace(/\\/+$/, \"\");\n const defaultRevalidate = config.revalidate ?? DEFAULT_REVALIDATE;\n\n function headers(): Record<string, string> {\n const h: Record<string, string> = { Accept: \"application/json\" };\n if (config.apiKey) h[\"Authorization\"] = `Bearer ${config.apiKey}`;\n return h;\n }\n\n function cacheInit(\n revalidate: number | false | undefined,\n tags: string[] | undefined,\n forceNoStore = false,\n ): NextRequestInit {\n const rv = revalidate ?? defaultRevalidate;\n // Preview is always uncached.\n if (forceNoStore) return { headers: headers(), cache: \"no-store\" };\n if (rv === false) {\n // With tags, \"false\" means cache until `revalidateTag` (the canonical CMS\n // publish-invalidation pattern) — we must NOT drop the tags to no-store, or\n // revalidateTag could never invalidate. Without tags, it means truly fresh.\n return tags?.length\n ? { headers: headers(), next: { revalidate: false, tags } }\n : { headers: headers(), cache: \"no-store\" };\n }\n return { headers: headers(), next: { revalidate: rv, tags } };\n }\n\n async function requestJSON<T>(\n url: string,\n init: NextRequestInit,\n { nullOn404 = false }: { nullOn404?: boolean } = {},\n ): Promise<T | null> {\n let res: Response;\n try {\n res = await fetch(url, init);\n } catch (err) {\n throw new BetterCMSError(\n `Network error: ${err instanceof Error ? err.message : String(err)}`,\n 0,\n \"NETWORK_ERROR\",\n );\n }\n // A single-entry 404 is \"no such entry\" (→ null). A list 404 is a real error\n // (unknown workspace/model) and must surface, not masquerade as an empty list.\n if (res.status === 404 && nullOn404) return null;\n if (!res.ok) {\n let message = res.statusText || \"Request failed\";\n try {\n const body = (await res.json()) as { error?: string; message?: string };\n message = body.error ?? body.message ?? message;\n } catch {\n /* ignore parse errors */\n }\n throw new BetterCMSError(message, res.status, statusToCode(res.status));\n }\n return (await res.json()) as T;\n }\n\n function entryQuery(opts?: { depth?: 0 | 1 | 2; select?: string[] }): string {\n const params = new URLSearchParams();\n if (opts?.depth != null) params.set(\"depth\", String(opts.depth));\n if (opts?.select?.length) params.set(\"select\", opts.select.join(\",\"));\n const qs = params.toString();\n return qs ? `?${qs}` : \"\";\n }\n\n const client = {\n async getEntry<TFields = unknown>(slug: string, opts?: GetEntryOptions) {\n const encoded = encodeURIComponent(slug);\n const query = entryQuery(opts);\n\n // Preview: drafts are only reachable through the signed preview endpoint, and\n // must never be cached (every request re-validates the token server-side).\n if (opts?.preview) {\n if (!opts.previewToken) {\n throw new BetterCMSError(\n \"preview: true requires a previewToken\",\n 400,\n \"VALIDATION_ERROR\",\n );\n }\n const tokenParam = `token=${encodeURIComponent(opts.previewToken)}`;\n const sep = query ? \"&\" : \"?\";\n const url = `${previewBaseUrl}/${encoded}${query}${sep}${tokenParam}`;\n const body = await requestJSON<{ data: RawEntry }>(\n url,\n cacheInit(undefined, undefined, true),\n { nullOn404: true },\n );\n return body ? mapEntry<TFields>(body.data) : null;\n }\n\n const url = `${baseUrl}/${config.workspace}/content-entries/${encoded}${query}`;\n const raw = await requestJSON<RawEntry>(\n url,\n cacheInit(opts?.revalidate, opts?.tags),\n { nullOn404: true },\n );\n return raw ? mapEntry<TFields>(raw) : null;\n },\n\n async listEntries<TFields = unknown>(\n model?: string,\n opts?: ListEntriesOptions,\n ): Promise<EntryList<TFields>> {\n const params = new URLSearchParams();\n if (model) params.set(\"model\", model);\n if (opts?.page != null) params.set(\"page\", String(opts.page));\n if (opts?.perPage != null) params.set(\"perPage\", String(opts.perPage));\n if (opts?.depth != null) params.set(\"depth\", String(opts.depth));\n if (opts?.select?.length) params.set(\"select\", opts.select.join(\",\"));\n\n const url = `${baseUrl}/${config.workspace}/content-entries?${params}`;\n const body = await requestJSON<RawListResponse>(\n url,\n cacheInit(opts?.revalidate, opts?.tags),\n );\n\n const data = body?.data;\n if (!data) {\n return {\n items: [],\n page: opts?.page ?? 1,\n perPage: opts?.perPage ?? 20,\n totalItems: 0,\n totalPages: 1,\n hasNextPage: false,\n hasPreviousPage: false,\n };\n }\n return {\n items: data.items.map((r) => mapEntry<TFields>(r)),\n page: data.page,\n perPage: data.perPage,\n totalItems: data.totalItems,\n totalPages: data.totalPages,\n hasNextPage: data.hasNextPage,\n hasPreviousPage: data.hasPreviousPage,\n };\n },\n\n async getPage(\n slug: string,\n opts?: { revalidate?: number | false; tags?: string[] },\n ): Promise<BetterCMSPage | null> {\n const url = `${baseUrl}/${config.workspace}/content/${encodeURIComponent(slug)}`;\n const body = await requestJSON<RawPageResponse>(\n url,\n cacheInit(opts?.revalidate, opts?.tags),\n { nullOn404: true },\n );\n if (!body) return null;\n const { entry, publishedAt } = body.data;\n return {\n slug: entry.slug,\n title: entry.title,\n metaTitle: entry.metaTitle ?? null,\n metaDescription: entry.metaDescription ?? null,\n metaJson: entry.metaJson ?? null,\n blocks: entry.blocks ?? [],\n publishedAt: publishedAt ?? null,\n updatedAt: entry.updatedAt ?? null,\n };\n },\n };\n\n // The concrete impls use single generic signatures; the public interface exposes\n // richer overloads (select→Partial, model-keyed typing). Cast once here.\n return client as unknown as BetterCMSNext<Schema>;\n}\n","/**\n * buildMetadata — map a BetterCMS page's SEO onto a Next.js `Metadata` object.\n *\n * Wire it into a route's `generateMetadata` so per-page Meta Title / Description and\n * the rich OG / Twitter / canonical the dashboard saves drive the live `<head>`:\n *\n * import { createBetterCMS, buildMetadata } from \"@bettercms-ai/next\";\n * const bcms = createBetterCMS({ workspace: \"acme\" });\n *\n * export async function generateMetadata({ params }): Promise<Metadata> {\n * const page = await bcms.getPage(params.slug);\n * if (!page) return {};\n * return buildMetadata(page, siteDefaults); // siteDefaults optional\n * }\n *\n * JSON-LD isn't part of Next's `Metadata`; render `resolveSeo(page, defaults).jsonLd`\n * as a `<script type=\"application/ld+json\">` in the page component (see the README).\n *\n * The merge (page-over-site) is delegated to `resolveSeo` (@bettercms-ai/sdk), the same\n * resolver the Astro adapter uses — so every surface stays consistent.\n */\nimport type { Metadata } from \"next\";\nimport { resolveSeo, type SeoInput } from \"@bettercms-ai/sdk\";\nimport type { SiteSeoDefaults } from \"@bettercms-ai/types\";\n\n/** Build a Next.js `Metadata` from a BetterCMS page, layering page meta over site defaults. */\nexport function buildMetadata(page: SeoInput, siteDefaults: SiteSeoDefaults = {}): Metadata {\n const seo = resolveSeo(page, siteDefaults);\n return {\n title: seo.title || undefined,\n description: seo.description || undefined,\n alternates: seo.canonical ? { canonical: seo.canonical } : undefined,\n openGraph: {\n title: seo.og.title || undefined,\n description: seo.og.description || undefined,\n type: (seo.og.type || \"website\") as \"website\",\n url: seo.og.url || undefined,\n images: seo.og.image ? [{ url: seo.og.image }] : undefined,\n },\n twitter: {\n card: (seo.twitter.card || \"summary\") as \"summary\" | \"summary_large_image\",\n title: seo.twitter.title || undefined,\n description: seo.twitter.description || undefined,\n images: seo.twitter.image ? [seo.twitter.image] : undefined,\n site: seo.twitter.site || undefined,\n },\n };\n}\n","/**\n * @bettercms-ai/next — the BetterCMS adapter for Next.js.\n *\n * Pair with @bettercms-ai/codegen: generate `BetterCMSSchema` from your content models,\n * then `createBetterCMS<BetterCMSSchema>(...)` for typed, cache-aware reads in the App Router.\n */\n\nexport { createBetterCMS } from \"./client.js\";\nexport type { BetterCMSNext, BetterCMSPage } from \"./client.js\";\n\n// SEO (FLO-302): map a page's per-page-over-site SEO into a Next `Metadata` for\n// `generateMetadata`. `resolveSeo` (+ its types) is re-exported for JSON-LD / custom heads.\nexport { buildMetadata } from \"./metadata.js\";\nexport { resolveSeo } from \"@bettercms-ai/sdk\";\nexport type { SeoInput, ResolvedSeo } from \"@bettercms-ai/sdk\";\nexport type { PageMetaJson, SiteSeoDefaults } from \"@bettercms-ai/types\";\n\nexport { BetterCMSError } from \"./types.js\";\nexport type {\n BetterCMSConfig,\n BetterCMSEntry,\n ReadOptions,\n GetEntryOptions,\n ListEntriesOptions,\n EntryList,\n} from \"./types.js\";\n\nexport {\n getContent,\n buildSnapshot,\n writeSnapshot,\n readSnapshot,\n serializeSnapshot,\n parseSnapshot,\n SNAPSHOT_VERSION,\n DEFAULT_SNAPSHOT_FILE,\n} from \"./snapshot.js\";\nexport type { ContentSource, ContentSnapshot } from \"./snapshot.js\";\n\nexport { generateLlmsTxt, llmsTxtRoute, fetchModels } from \"./llms-txt.js\";\nexport type {\n LlmsModel,\n LlmsModelField,\n LlmsTxtOptions,\n LlmsTxtRouteConfig,\n} from \"./llms-txt.js\";\n\nexport { isDraftEnabled, getPreviewToken } from \"./preview.js\";\n\n// Draft-mode route-handler factories for the App Router.\nexport {\n createDraftModeRoute,\n createDisableDraftRoute,\n PREVIEW_TOKEN_COOKIE,\n} from \"./draft-route.js\";\nexport type { DraftModeRouteConfig } from \"./draft-route.js\";\n\n// Image URL builder — `import { imageUrlBuilder } from \"@bettercms-ai/next\"`.\nexport { default as imageUrlBuilder, imageUrl } from \"@bettercms-ai/image-url\";\nexport type {\n ImageUrlBuilder,\n ImageSource,\n ImageFormat,\n ImageFit,\n} from \"@bettercms-ai/image-url\";\n\n// Build-time forms reader for the deploy Action's snapshot (server-side).\nexport { readForms, getForm } from \"./forms-read.js\";\nexport type { SnapshotForms } from \"./forms-read.js\";\n\n// Build-time pages reader for the deploy Action's snapshot (server-side). Pair with\n// <BcmsBlocks> to render a page's block_json: readPages()/getPage(slug) → page.blocks.\nexport { readPages, getPage } from \"./pages-read.js\";\nexport type { SnapshotPage } from \"./pages-read.js\";\n\n// Build-time components reader — pass to <BcmsBlocks components={…} /> so `component`\n// blocks resolve their reusable definitions.\nexport { readComponents, getComponent } from \"./components-read.js\";\n\n// <BcmsForm> / <BcmsBlocks> are client components and ship from their own \"use client\"\n// entries so the React Server Components boundary survives bundling:\n// import { BcmsForm } from \"@bettercms-ai/next/form\";\n// import { BcmsBlocks } from \"@bettercms-ai/next/blocks\";\nexport type { BcmsFormProps } from \"./form.js\";\nexport type { BcmsBlocksProps } from \"./blocks.js\";\n\n// Live content + revalidation (mirrors next-sanity defineLive/<SanityLive>).\n// import { BcmsLive } from \"@bettercms-ai/next/live\";\nexport type { BcmsLiveProps } from \"./live.js\";\nexport { defineBetterCMSLive } from \"./live-config.js\";\nexport type { BetterCMSLiveConfig } from \"./live-config.js\";\nexport { createRevalidateRoute } from \"./revalidate-route.js\";\nexport type { RevalidateRouteConfig } from \"./revalidate-route.js\";\n\n// Visual editing overlay (client). import { VisualEditing } from \"@bettercms-ai/next/visual-editing\"\nexport type { VisualEditingProps } from \"./visual-editing.js\";\n","/**\n * Build-time content snapshot — the writer + reader of `bcms-content.json`.\n *\n * Why this exists: a static (`output: \"export\"`) build must resolve all content at\n * build time. If a page instead falls back to a live `cache: \"no-store\"` fetch, its\n * route becomes dynamic and `output: \"export\"` silently drops it — you get an empty\n * `out/`. The fix is a single build-time snapshot the pages read synchronously.\n *\n * The WRITER (`buildSnapshot`/`writeSnapshot`, used by the `bettercms-snapshot` bin)\n * and the READER (`getContent`) share ONE serializer (`serializeSnapshot`/\n * `parseSnapshot`) so the on-disk shape can never drift between the two halves.\n *\n * This is intentionally separate from {@link createBetterCMS}: that stays the live,\n * network-backed client. `getContent` is the snapshot-preferring reader a static\n * site uses. Existing consumers that deliberately rely on the live fallback are\n * unaffected — only `getContent` enforces \"snapshot or bust\".\n */\n\nimport { readFileSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { dirname, resolve } from \"node:path\";\nimport {\n BetterCMSError,\n type BetterCMSConfig,\n type BetterCMSEntry,\n type EntryList,\n type ListEntriesOptions,\n} from \"./types.js\";\nimport { createBetterCMS } from \"./client.js\";\n\n/** Bump when the on-disk shape changes incompatibly — readers reject mismatches. */\nexport const SNAPSHOT_VERSION = 1;\n/** Default snapshot filename, resolved against `process.cwd()`. */\nexport const DEFAULT_SNAPSHOT_FILE = \"bcms-content.json\";\n\n/** The full-site content snapshot. Entries are grouped by model slug so the reader\n * can serve `listEntries(model)` offline without a per-entry model tag. */\nexport interface ContentSnapshot {\n version: number;\n workspace: string;\n generatedAt: string;\n models: Record<string, BetterCMSEntry<unknown>[]>;\n}\n\n// ── Serializer (shared by writer + reader — single source of on-disk shape) ──────\n\n/** Serialize a snapshot to the canonical JSON form written to disk. */\nexport function serializeSnapshot(snapshot: ContentSnapshot): string {\n return JSON.stringify(snapshot, null, 2);\n}\n\n/** Parse + validate a snapshot JSON string. Throws on a version mismatch so a stale\n * snapshot fails loudly rather than feeding the build the wrong shape. */\nexport function parseSnapshot(json: string): ContentSnapshot {\n const parsed = JSON.parse(json) as ContentSnapshot;\n if (parsed.version !== SNAPSHOT_VERSION) {\n throw new BetterCMSError(\n `Unsupported bcms-content.json version ${parsed.version} (expected ${SNAPSHOT_VERSION}). ` +\n `Regenerate it with \"bettercms-snapshot\".`,\n 0,\n \"SNAPSHOT_VERSION_MISMATCH\",\n );\n }\n return parsed;\n}\n\n// ── Disk I/O ─────────────────────────────────────────────────────────────────────\n\n/** Write a snapshot to `path` (relative paths resolve against cwd), creating dirs. */\nexport function writeSnapshot(path: string, snapshot: ContentSnapshot): void {\n const out = resolve(process.cwd(), path);\n mkdirSync(dirname(out), { recursive: true });\n writeFileSync(out, serializeSnapshot(snapshot), \"utf8\");\n}\n\n/** Read + parse a snapshot from `path`. Returns `null` only when the file is absent;\n * a present-but-corrupt/stale file throws (callers must not treat that as \"missing\"). */\nexport function readSnapshot(path: string): ContentSnapshot | null {\n const out = resolve(process.cwd(), path);\n let json: string;\n try {\n json = readFileSync(out, \"utf8\");\n } catch (err) {\n if ((err as NodeJS.ErrnoException)?.code === \"ENOENT\") return null;\n throw err;\n }\n return parseSnapshot(json);\n}\n\n// ── Writer: build a snapshot from the live API ────────────────────────────────────\n\n/** Minimal client surface `buildSnapshot` needs — lets tests inject a fake. Kept\n * non-generic so a simple fake (and the live client's matching overload) both fit. */\ntype ListOnly = {\n listEntries(model?: string, opts?: ListEntriesOptions): Promise<EntryList<unknown>>;\n};\n\n/**\n * Page through every model's published entries and assemble a full-site snapshot.\n * `modelSlugs` come from the Management API (`fetchModels`) so the snapshot covers\n * exactly this project's schema. Pass `client` in tests; defaults to the live client.\n */\nexport async function buildSnapshot(opts: {\n config: BetterCMSConfig;\n modelSlugs: string[];\n client?: ListOnly;\n /** Stamped into `generatedAt`; injectable for deterministic tests. */\n now?: string;\n}): Promise<ContentSnapshot> {\n const client = opts.client ?? createBetterCMS(opts.config);\n const models: Record<string, BetterCMSEntry<unknown>[]> = {};\n for (const slug of opts.modelSlugs) {\n const all: BetterCMSEntry<unknown>[] = [];\n let page = 1;\n for (;;) {\n // revalidate:false → bypass any data cache; this is a one-shot build fetch.\n const res = await client.listEntries(slug, { page, perPage: 100, revalidate: false });\n all.push(...res.items);\n if (!res.hasNextPage) break;\n page += 1;\n }\n models[slug] = all;\n }\n return {\n version: SNAPSHOT_VERSION,\n workspace: opts.config.workspace,\n generatedAt: opts.now ?? new Date().toISOString(),\n models,\n };\n}\n\n// ── Reader: getContent (snapshot-preferring, loud on absence) ─────────────────────\n\n/** Snapshot-backed content reader. Async so it can transparently fall back to the\n * live client in preview without changing the call sites. */\nexport interface ContentSource {\n getEntry<TFields = unknown>(slug: string): Promise<BetterCMSEntry<TFields> | null>;\n listEntries<TFields = unknown>(model?: string): Promise<EntryList<TFields>>;\n}\n\n/** `BETTERCMS_PREVIEW` truthy → read live (uncached), never the snapshot. */\nfunction isPreview(): boolean {\n const v = (\n globalThis as { process?: { env?: Record<string, string | undefined> } }\n ).process?.env?.BETTERCMS_PREVIEW;\n return v != null && v !== \"\" && v !== \"0\" && v !== \"false\";\n}\n\n/** Parsed snapshots keyed by resolved path — read+parse once per build process. */\nconst snapshotCache = new Map<string, ContentSnapshot>();\n\n/** Test-only: clear the module-scope snapshot cache between cases. */\nexport function __resetSnapshotCacheForTests(): void {\n snapshotCache.clear();\n}\n\nfunction emptyPage<TFields>(items: BetterCMSEntry<TFields>[]): EntryList<TFields> {\n return {\n items,\n page: 1,\n perPage: items.length,\n totalItems: items.length,\n totalPages: 1,\n hasNextPage: false,\n hasPreviousPage: false,\n };\n}\n\n/**\n * The reader a static site should use. In a normal (export) build it reads the\n * memoized `bcms-content.json` and serves entries from memory. If the snapshot is\n * absent it THROWS a clear, actionable error — the whole point is to turn the old\n * silent empty-`out/` failure into a loud build error. Set `BETTERCMS_PREVIEW=1`\n * for a preview build that reads live instead.\n */\nexport function getContent<\n Schema extends Record<string, unknown> = Record<string, unknown>,\n>(config: BetterCMSConfig, opts: { snapshotPath?: string } = {}): ContentSource {\n const path = opts.snapshotPath ?? DEFAULT_SNAPSHOT_FILE;\n\n if (isPreview()) {\n const live = createBetterCMS<Schema>(config);\n return {\n getEntry: (slug) => live.getEntry(slug, { revalidate: false }),\n listEntries: (model) => live.listEntries(model, { revalidate: false }),\n };\n }\n\n const resolved = resolve(process.cwd(), path);\n function load(): ContentSnapshot {\n const cached = snapshotCache.get(resolved);\n if (cached) return cached;\n const snap = readSnapshot(path);\n if (!snap) {\n throw new BetterCMSError(\n `BetterCMS content snapshot \"${path}\" not found. A static (output: \"export\") build reads ` +\n `content from this snapshot; without it pages fall back to a live no-store fetch, become ` +\n `dynamic routes, and output: \"export\" silently drops them (empty out/). Run ` +\n `\"bettercms-snapshot\" before \"next build\" (make it your build:preview prestep), or set ` +\n `BETTERCMS_PREVIEW=1 to read live in a preview build.`,\n 0,\n \"SNAPSHOT_MISSING\",\n );\n }\n snapshotCache.set(resolved, snap);\n return snap;\n }\n\n return {\n async getEntry<TFields = unknown>(slug: string) {\n const snap = load();\n for (const entries of Object.values(snap.models)) {\n const found = entries.find((e) => e.slug === slug);\n if (found) return found as BetterCMSEntry<TFields>;\n }\n return null;\n },\n async listEntries<TFields = unknown>(model?: string) {\n const snap = load();\n const items = (\n model ? (snap.models[model] ?? []) : Object.values(snap.models).flat()\n ) as BetterCMSEntry<TFields>[];\n return emptyPage(items);\n },\n };\n}\n","/**\n * llms.txt generation — the AI-discovery surface of the deterministic floor.\n *\n * `generateLlmsTxt` is pure (models in → string out), so it is trivially testable and\n * deterministic. `llmsTxtRoute` wraps it as an App Router `route.ts` handler. `fetchModels`\n * pulls the live schema from the Management API for sites that want a self-updating file.\n *\n * Output follows the llmstxt.org convention: an H1 title, a `>` summary, then sections.\n */\n\n/** Minimal field shape needed to describe a model in llms.txt. */\nexport interface LlmsModelField {\n key: string;\n label?: string;\n type: string;\n required?: boolean;\n}\n\n/** Minimal model shape — a subset of the Management API model row. */\nexport interface LlmsModel {\n slug: string;\n name?: string;\n description?: string | null;\n fields: LlmsModelField[];\n}\n\nexport interface LlmsTxtOptions {\n /** H1 title. Default: \"BetterCMS content\". */\n title?: string;\n /** One-line `>` summary under the title. */\n description?: string;\n /** Delivery API base used to render example endpoints. */\n baseUrl?: string;\n /** Workspace slug used in example endpoints. */\n workspace?: string;\n /** Extra free-form lines appended under a \"## Notes\" section. */\n notes?: string[];\n}\n\n// Model name/slug/description and field key/label/type are author/agent-controlled\n// (the same content the sibling codegen package treats as hostile via escapeJsDoc).\n// llms.txt is fed to AI crawlers, so unescaped values could forge headings/blockquotes\n// or break out of code spans — a structure-spoofing / prompt-injection vector.\n\n/** Collapse to a single line — kills newline-based block injection. */\nfunction oneLine(text: string): string {\n return text.replace(/[\\r\\n]+/g, \" \").trim();\n}\n\n/** For text rendered OUTSIDE a code span (titles, descriptions, labels): single-line +\n * escape a leading markdown block-control char so it can't start a heading/list/quote. */\nfunction inlineText(text: string): string {\n return oneLine(text).replace(/^([#>\\-*+=|])/, \"\\\\$1\");\n}\n\n/** For text rendered INSIDE a code span: strip backticks (which would close the span)\n * and newlines. */\nfunction codeSpan(text: string): string {\n return oneLine(text).replace(/`/g, \"\");\n}\n\nfunction fieldLine(f: LlmsModelField): string {\n const req = f.required ? \", required\" : \"\";\n const label = f.label && f.label !== f.key ? ` — ${inlineText(f.label)}` : \"\";\n return ` - \\`${codeSpan(f.key)}\\` (${codeSpan(f.type)}${req})${label}`;\n}\n\n/**\n * Render an llms.txt document describing the available content models. Deterministic:\n * models are sorted by slug; field order is preserved as authored.\n */\nexport function generateLlmsTxt(models: LlmsModel[], opts: LlmsTxtOptions = {}): string {\n const title = opts.title ?? \"BetterCMS content\";\n const sorted = [...models].sort((a, b) =>\n a.slug < b.slug ? -1 : a.slug > b.slug ? 1 : 0,\n );\n\n const lines: string[] = [`# ${inlineText(title)}`, \"\"];\n if (opts.description) {\n lines.push(`> ${inlineText(opts.description)}`, \"\");\n }\n\n const base =\n opts.baseUrl && opts.workspace\n ? `${opts.baseUrl.replace(/\\/+$/, \"\")}/${opts.workspace}/content-entries`\n : undefined;\n\n lines.push(\"## Content models\", \"\");\n if (sorted.length === 0) {\n lines.push(\"_No content models are defined yet._\", \"\");\n }\n for (const model of sorted) {\n const name = model.name ?? model.slug;\n lines.push(`### ${inlineText(name)} (\\`${codeSpan(model.slug)}\\`)`);\n if (model.description) lines.push(\"\", inlineText(model.description));\n if (base) {\n lines.push(\"\", `- List: \\`GET ${base}?model=${codeSpan(model.slug)}\\``);\n lines.push(`- Entry: \\`GET ${base}/{slug}\\``);\n }\n if (model.fields.length) {\n lines.push(\"\", \"Fields:\");\n for (const f of model.fields) lines.push(fieldLine(f));\n }\n lines.push(\"\");\n }\n\n if (opts.notes?.length) {\n lines.push(\"## Notes\", \"\");\n for (const note of opts.notes) lines.push(`- ${inlineText(note)}`);\n lines.push(\"\");\n }\n\n return lines.join(\"\\n\");\n}\n\n// ── Integration SKILL.md ────────────────────────────────────────────────────────\n// A framework-agnostic agent skill (frontmatter + lean body) that teaches an AI how\n// to READ this project's content from the Delivery API. Unlike llms.txt it does NOT\n// dump every field — it points at the live types endpoint so the schema can't go\n// stale, and names content types by slug only. Drop at .claude/skills/<name>/SKILL.md.\n\n/** A content type named in the skill — slug + kind only (no fields). */\nexport interface SkillContentType {\n kind: \"page\" | \"model\";\n slug: string;\n /** \"singleton\" | \"dynamic\" for pages; omitted for models. */\n pageType?: string | null;\n}\n\nexport interface SkillOptions {\n /** Human project name (skill title + description). */\n projectName: string;\n /** Project slug → the skill's kebab-case `name:`. */\n projectSlug: string;\n /** Delivery + management API base, e.g. https://api.bettercms.ai/api/v1. */\n apiUrl: string;\n /** Workspace slug used in the delivery paths. */\n workspace: string;\n /** Where to mint a content:read delivery key. */\n mintUrl: string;\n /** Key-authed live TypeScript types endpoint. */\n typesUrl: string;\n /** This project's content types (pages + models), by slug — no field dump. */\n contentTypes: SkillContentType[];\n}\n\n/**\n * Render a concise, framework-agnostic SKILL.md describing how to read THIS\n * project's content from the BetterCMS Delivery API. Deterministic: content types\n * sorted by slug. Author-controlled strings (project name, slugs, pageType) are\n * injection-escaped, same as {@link generateLlmsTxt}.\n */\nexport type SkillTarget = \"claude\" | \"cursor\" | \"agents\";\n\nexport interface SkillVariant {\n /** The agent/IDE this variant targets. */\n target: SkillTarget;\n /** Human label for a tab/picker. */\n label: string;\n /** Suggested path to save the file, e.g. `.claude/skills/<name>/SKILL.md`. */\n filename: string;\n /** Full file content (target-specific frontmatter + the shared body). */\n content: string;\n}\n\n/** The shared, tool-agnostic instruction body — no frontmatter, no save hint. */\nfunction skillBody(opts: SkillOptions): string {\n const proj = inlineText(opts.projectName);\n const api = opts.apiUrl.replace(/\\/+$/, \"\");\n const deliveryBase = `${api}/delivery/${codeSpan(opts.workspace)}`;\n const sorted = [...opts.contentTypes].sort((a, b) =>\n a.slug < b.slug ? -1 : a.slug > b.slug ? 1 : 0,\n );\n\n const lines: string[] = [\n `# BetterCMS — ${proj}`,\n \"\",\n `${proj}'s content lives in BetterCMS. Read it through the Delivery API with a project ` +\n \"`content:read` key. Pull field shapes live from the types endpoint below — don't hardcode \" +\n \"them, the schema can change.\",\n \"\",\n \"## Auth\",\n \"Send `X-API-Key: <key>` on every read. Mint a long-lived `content:read` key (reads PUBLISHED \" +\n `content, never expires) at ${opts.mintUrl}. For drafts, mint a \\`content:read:draft\\` key ` +\n \"(force-expired to ≤1h), then publish and switch to the long-lived key.\",\n \"\",\n \"## Content types\",\n ];\n if (sorted.length === 0) {\n lines.push(\"_None yet — create pages or models first (e.g. via the BetterCMS MCP)._\");\n } else {\n for (const ct of sorted) {\n const kind =\n ct.kind === \"page\"\n ? `page${ct.pageType ? ` (${codeSpan(ct.pageType)})` : \"\"}`\n : \"model\";\n lines.push(`- \\`${codeSpan(ct.slug)}\\` — ${kind}`);\n }\n }\n lines.push(\n \"\",\n `## Reading (Delivery API · base \\`${deliveryBase}\\`)`,\n \"- Page: `GET .../content/{slug}`\",\n \"- Entries (list): `GET .../content-entries?model={slug}`\",\n \"- Entry: `GET .../content-entries/{slug}`\",\n \"\",\n \"## Typed client\",\n `- Live TypeScript types: \\`GET ${opts.typesUrl}\\` — authoritative field shapes; generate/refresh from here.`,\n \"- `npm i @bettercms-ai/next @bettercms-ai/codegen`, then construct the client. \" +\n \"`baseUrl` + `workspace` are required — there is no `apiUrl`/`endpoint` option:\",\n \"\",\n \"```ts\",\n 'import { createBetterCMS } from \"@bettercms-ai/next\";',\n \"const cms = createBetterCMS<BetterCMSSchema>({\",\n ` baseUrl: \"${api}/delivery\",`,\n ` workspace: \"${codeSpan(opts.workspace)}\",`,\n \" apiKey: process.env.BETTERCMS_API_KEY,\",\n \"});\",\n \"```\",\n \"\",\n \"## Field shapes you'll read\",\n \"Canonical READ shapes the Delivery API returns. Per-field types come from the types \" +\n \"endpoint; these container shapes are stable:\",\n '- **richtext** → `{ html, value: { root: … }, format: \"lexical-…\" }`. Render `html`, ' +\n \"or walk `value.root` for structured rendering.\",\n \"- **image** → `{ url, altText?, width?, height?, id? }` — an object, use `.url` (the \" +\n \"write value is a bare CDN URL string, but reads are normalized to this object).\",\n \"- **array + zones** → `{ nonRepeatable?: { <key>: value }, repeatable?: Array<{ <key>: \" +\n \"value }> }`. `nonRepeatable` is one fixed block; `repeatable` is a list — map over it.\",\n '- **array (primitive)** → a flat list (e.g. `string[]`). `type: \"array\"` alone is ' +\n \"ambiguous — the generated type tells you which.\",\n \"\",\n \"Example entry `fields` (a `sections` zone field + a `tags` primitive array):\",\n \"```jsonc\",\n \"{\",\n ' \"sections\": {',\n ' \"nonRepeatable\": { \"eyebrow\": \"New\", \"heading\": \"Welcome\" },',\n ' \"repeatable\": [{ \"label\": \"Docs\", \"url\": \"/docs\", \"icon\": { \"url\": \"https://cdn…\" } }]',\n \" },\",\n ' \"tags\": [\"alpha\", \"beta\"]',\n \"}\",\n \"```\",\n \"\",\n \"## Live Preview bindings (makes the site visually editable)\",\n \"Generate a bindings module alongside the types and spread it so the dashboard's Live \" +\n \"Preview can edit each field in place:\",\n \"```bash\",\n `BETTERCMS_API_KEY=<key> npx bettercms-codegen --api-url ${api} --bindings-out src/bettercms.bindings.generated.ts`,\n \"```\",\n \"```tsx\",\n 'import { bcms } from \"./bettercms.bindings.generated\";',\n \"// scalar: <h1 {...bcms.<model>.<field>}>{entry.fields.field}</h1>\",\n \"// list item: <article {...bcms.<model>.<arr>.$(i)}><h3 {...bcms.<model>.<arr>.<sub>(i)}>{item.sub}</h3></article>\",\n \"// primitive list:<li {...bcms.<model>.<arr>.value(i)}>{tag}</li>\",\n \"```\",\n \"Bindings emit attributes only when the platform builds with `BCMS_ANNOTATE` (preview \" +\n \"builds); production ships nothing extra, so spreading them is always safe.\",\n );\n return lines.join(\"\\n\");\n}\n\nfunction skillDescription(proj: string): string {\n return (\n `Read ${proj}'s content from BetterCMS via the Delivery API — auth, listing and fetching ` +\n \"pages and content entries, pulling live TypeScript types, and the @bettercms-ai/next typed \" +\n \"client. Use whenever fetching, rendering, or typing this project's CMS content.\"\n );\n}\n\n/**\n * Render the integration skill for a specific agent `target`. The instruction body is\n * identical across tools; only the frontmatter (and where you save it) differ:\n * - claude → Claude Code skill (`name` + `description`)\n * - cursor → Cursor `.mdc` rule (`description` + `globs` + `alwaysApply`, \"Agent Requested\")\n * - agents → plain Markdown, no frontmatter (drop into a repo-root `AGENTS.md`)\n */\nexport function generateSkill(opts: SkillOptions, target: SkillTarget = \"claude\"): string {\n const name = `bettercms-${codeSpan(opts.projectSlug)}`;\n const desc = skillDescription(inlineText(opts.projectName));\n const body = skillBody(opts);\n\n let frontmatter = \"\";\n if (target === \"claude\") {\n frontmatter = `---\\nname: ${name}\\ndescription: ${desc}\\n---\\n\\n`;\n } else if (target === \"cursor\") {\n frontmatter = `---\\ndescription: ${desc}\\nglobs:\\nalwaysApply: false\\n---\\n\\n`;\n }\n return `${frontmatter}${body}\\n`;\n}\n\nconst TARGET_META: Record<SkillTarget, { label: string; file: (name: string) => string }> = {\n claude: { label: \"Claude Code\", file: (n) => `.claude/skills/${n}/SKILL.md` },\n cursor: { label: \"Cursor\", file: (n) => `.cursor/rules/${n}.mdc` },\n agents: { label: \"AGENTS.md (generic)\", file: () => \"AGENTS.md\" },\n};\n\n/** Render the skill for every supported agent target — drives a tabbed picker in the UI. */\nexport function generateSkillVariants(opts: SkillOptions): SkillVariant[] {\n const name = `bettercms-${codeSpan(opts.projectSlug)}`;\n return (Object.keys(TARGET_META) as SkillTarget[]).map((target) => ({\n target,\n label: TARGET_META[target].label,\n filename: TARGET_META[target].file(name),\n content: generateSkill(opts, target),\n }));\n}\n\n/** Options for {@link llmsTxtRoute}. Provide one of `models` or `getModels`. */\nexport interface LlmsTxtRouteConfig extends LlmsTxtOptions {\n /** Static model list (e.g. fetched at build time). */\n models?: LlmsModel[];\n /** Dynamic provider — called per request (wrap in your own caching if needed). */\n getModels?: () => Promise<LlmsModel[]> | LlmsModel[];\n /** Cache-Control header value. Default: `public, max-age=3600`. */\n cacheControl?: string;\n}\n\n/**\n * Build an App Router route handler that serves llms.txt as `text/plain`.\n *\n * ```ts\n * // app/llms.txt/route.ts\n * import { llmsTxtRoute } from \"@bettercms-ai/next\";\n * export const GET = llmsTxtRoute({ models, title: \"Acme\", workspace: \"acme\" });\n * ```\n */\nexport function llmsTxtRoute(config: LlmsTxtRouteConfig): () => Promise<Response> {\n const { models, getModels, cacheControl, ...opts } = config;\n return async () => {\n const resolved = getModels ? await getModels() : (models ?? []);\n const body = generateLlmsTxt(resolved, opts);\n return new Response(body, {\n status: 200,\n headers: {\n \"Content-Type\": \"text/plain; charset=utf-8\",\n \"Cache-Control\": cacheControl ?? \"public, max-age=3600\",\n },\n });\n };\n}\n\n/**\n * Fetch content models from the Management API (`GET /management/content/models`).\n * The key is project-scoped server-side, so this returns exactly this site's schema.\n * Dependency-free so it runs in any runtime (Edge route, build script, Node).\n */\nexport async function fetchModels(opts: {\n /** Management API base, e.g. \"https://api.bettercms.ai/api/v1\". */\n apiUrl: string;\n /** A management-scoped key (content:manage). */\n apiKey: string;\n /** Optional fetch override (testing / custom runtime). */\n fetchImpl?: typeof fetch;\n}): Promise<LlmsModel[]> {\n const doFetch = opts.fetchImpl ?? fetch;\n const base = opts.apiUrl.replace(/\\/+$/, \"\");\n const res = await doFetch(`${base}/management/content/models`, {\n headers: { Authorization: `Bearer ${opts.apiKey}`, Accept: \"application/json\" },\n });\n if (!res.ok) {\n throw new Error(`Management API returned ${res.status} ${res.statusText}`);\n }\n const body = (await res.json()) as { data?: LlmsModel[] };\n return (body.data ?? []).map((m) => ({\n slug: m.slug,\n name: m.name,\n description: m.description ?? null,\n fields: m.fields ?? [],\n }));\n}\n","/**\n * Draft-preview helpers. `next/headers` is imported dynamically so this module is safe\n * to load outside a Next.js request scope (tests, build scripts) without throwing.\n */\n\n/** True when Next.js Draft Mode is enabled for the current request. */\nexport async function isDraftEnabled(): Promise<boolean> {\n const { draftMode } = await import(\"next/headers\");\n const dm = await draftMode();\n return dm.isEnabled;\n}\n\n/**\n * The preview token stored by `createDraftModeRoute`, or null. Pass it to\n * `getEntry(slug, { preview: true, previewToken })` to read drafts.\n */\nexport async function getPreviewToken(): Promise<string | null> {\n const { cookies } = await import(\"next/headers\");\n const jar = await cookies();\n return jar.get(\"bcms-preview-token\")?.value ?? null;\n}\n","/**\n * createDraftModeRoute — an App Router route-handler factory for entering draft\n * preview. Mount it at e.g. `app/api/draft/route.ts`:\n *\n * import { createDraftModeRoute } from \"@bettercms-ai/next\";\n * export const { GET } = createDraftModeRoute({ apiUrl: \"https://api.bettercms.ai\" });\n *\n * Visiting `/api/draft?token=<jwt>&redirect=/path` validates the preview token\n * against the backend (the signature authority), enables Next Draft Mode, stores\n * the token in an httpOnly cookie, and redirects. Reads can then pass the token\n * via `getPreviewToken()` to `getEntry({ preview: true, previewToken })`.\n */\n\nexport const PREVIEW_TOKEN_COOKIE = \"bcms-preview-token\";\n\nexport interface DraftModeRouteConfig {\n /** Backend base, e.g. `https://api.bettercms.ai`. */\n apiUrl: string;\n}\n\n/** Only same-origin relative paths. Blocks \"//evil.com\", \"https://evil.com\", etc. */\nfunction safeRedirectPath(to: string): string {\n return to.startsWith(\"/\") && !to.startsWith(\"//\") ? to : \"/\";\n}\n\n/** Decode a preview-token JWT payload (UNVERIFIED) to read its `entrySlug`. */\nfunction decodeTokenSlug(token: string): string | null {\n try {\n const part = token.split(\".\")[1];\n if (!part) return null;\n const json = JSON.parse(\n Buffer.from(part, \"base64url\").toString(\"utf8\"),\n ) as { entrySlug?: string };\n return json.entrySlug ?? null;\n } catch {\n return null;\n }\n}\n\nexport function createDraftModeRoute(config: DraftModeRouteConfig) {\n const apiUrl = config.apiUrl.replace(/\\/+$/, \"\");\n\n async function GET(request: Request): Promise<Response> {\n const url = new URL(request.url);\n const token = url.searchParams.get(\"token\");\n const redirectTo = url.searchParams.get(\"redirect\") ?? \"/\";\n\n if (!token) return new Response(\"Missing token\", { status: 400 });\n const slug = decodeTokenSlug(token);\n if (!slug) return new Response(\"Malformed token\", { status: 400 });\n\n const res = await fetch(\n `${apiUrl}/api/v1/preview/${encodeURIComponent(slug)}?token=${encodeURIComponent(token)}`,\n );\n if (!res.ok) return new Response(\"Invalid or expired token\", { status: 401 });\n\n const { draftMode, cookies } = await import(\"next/headers\");\n (await draftMode()).enable();\n (await cookies()).set(PREVIEW_TOKEN_COOKIE, token, {\n httpOnly: true,\n sameSite: \"lax\",\n secure: url.protocol === \"https:\",\n path: \"/\",\n maxAge: 60 * 60 * 24,\n });\n\n return Response.redirect(new URL(safeRedirectPath(redirectTo), url.origin), 302);\n }\n\n return { GET };\n}\n\n/** Exit draft mode and clear the preview-token cookie. */\nexport function createDisableDraftRoute() {\n async function GET(request: Request): Promise<Response> {\n const url = new URL(request.url);\n const redirectTo = url.searchParams.get(\"redirect\") ?? \"/\";\n const { draftMode, cookies } = await import(\"next/headers\");\n (await draftMode()).disable();\n (await cookies()).delete(PREVIEW_TOKEN_COOKIE);\n return Response.redirect(new URL(safeRedirectPath(redirectTo), url.origin), 302);\n }\n return { GET };\n}\n","/**\n * Build-time reader for the forms the deploy Action injects into the content\n * snapshot (schema \"bcms-content/v1\": `{ ...collections, forms, turnstileSiteKey }`).\n *\n * This is intentionally separate from `readSnapshot()` — that reads the\n * `@bettercms-ai/next` \"bettercms-snapshot\" format (`{ version, models }`), which is a\n * different file shape. Connected-repo sites on <name>.bettercms.site get the Action\n * snapshot, so forms live here. Server/build-time only (uses node:fs).\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport type { DeliveryForm } from \"@bettercms-ai/sdk\";\n\nexport interface SnapshotForms {\n forms: DeliveryForm[];\n /** Public Turnstile site key for any form with `turnstileEnabled`, or null. */\n turnstileSiteKey: string | null;\n}\n\n/**\n * Read forms from the Action's `bcms-content.json`. Missing file or no forms →\n * an empty set (a content-only build never throws). `path` resolves against cwd.\n */\nexport function readForms(path = \"bcms-content.json\"): SnapshotForms {\n let raw: string;\n try {\n raw = readFileSync(resolve(process.cwd(), path), \"utf8\");\n } catch {\n return { forms: [], turnstileSiteKey: null };\n }\n const snap = JSON.parse(raw) as { forms?: DeliveryForm[]; turnstileSiteKey?: string | null };\n return { forms: snap.forms ?? [], turnstileSiteKey: snap.turnstileSiteKey ?? null };\n}\n\n/** Find a single form by `id` or `name` from the Action snapshot. */\nexport function getForm(idOrName: string, path?: string): DeliveryForm | undefined {\n return readForms(path).forms.find((f) => f.id === idOrName || f.name === idOrName);\n}\n","/**\n * Build-time reader for the pages the deploy Action injects into the content\n * snapshot (schema \"bcms-content/v1\": `{ ...collections, forms, pages, turnstileSiteKey }`).\n *\n * Sibling to `forms-read.ts`. Connected-repo sites on <name>.bettercms.site get the\n * Action snapshot, so published pages (with their block_json) live here. Render a page\n * with `<BcmsBlocks blocks={page.blocks} forms={readForms().forms} />`. The same data\n * is also available at runtime via `createBetterCMS(...).getPage(slug)`.\n * Server/build-time only (uses node:fs).\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport type { ContentBlock, PageMetaJson } from \"@bettercms-ai/types\";\n\nexport interface SnapshotPage {\n slug: string;\n title: string;\n metaTitle: string | null;\n metaDescription: string | null;\n /** Rich SEO (OG / Twitter / canonical / JSON-LD) — FLO-302. Feed to `buildMetadata`. */\n metaJson: PageMetaJson | null;\n /** The page's block_json — pass to `<BcmsBlocks blocks={...} />`. */\n blocks: ContentBlock[];\n updatedAt: string | null;\n publishedAt: string | null;\n}\n\n/**\n * Read published pages from the Action's `bcms-content.json`. Missing file or no\n * pages → an empty list (a pages-less build never throws). `path` resolves against cwd.\n */\nexport function readPages(path = \"bcms-content.json\"): SnapshotPage[] {\n let raw: string;\n try {\n raw = readFileSync(resolve(process.cwd(), path), \"utf8\");\n } catch {\n return [];\n }\n const snap = JSON.parse(raw) as { pages?: SnapshotPage[] };\n return snap.pages ?? [];\n}\n\n/** Find a single published page by `slug` from the Action snapshot. */\nexport function getPage(slug: string, path?: string): SnapshotPage | undefined {\n return readPages(path).find((p) => p.slug === slug);\n}\n","/**\n * Build-time reader for the reusable components the deploy Action injects into the\n * content snapshot (`bcms-content.json` → `components`). Mirrors `forms-read.ts`.\n *\n * Pass the result to `<BcmsBlocks components={readComponents()} … />` so `component`\n * blocks resolve their definitions. Server/build-time only (uses node:fs).\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport type { DeliveryComponent } from \"@bettercms-ai/types\";\n\n/**\n * Read components from the Action's `bcms-content.json`. Missing file or no\n * components → an empty array (a content-only build never throws).\n */\nexport function readComponents(path = \"bcms-content.json\"): DeliveryComponent[] {\n let raw: string;\n try {\n raw = readFileSync(resolve(process.cwd(), path), \"utf8\");\n } catch {\n return [];\n }\n const snap = JSON.parse(raw) as { components?: DeliveryComponent[] };\n return snap.components ?? [];\n}\n\n/** Find a single component by `id`, `slug`, or `name` from the Action snapshot. */\nexport function getComponent(idOrSlug: string, path?: string): DeliveryComponent | undefined {\n return readComponents(path).find(\n (c) => c.id === idOrSlug || c.slug === idOrSlug || c.name === idOrSlug,\n );\n}\n","/**\n * defineBetterCMSLive — build the live SSE URL for `<BcmsLive>` from your config.\n * Server-safe (no React); pass the returned `liveSrc` to the client component.\n */\nexport interface BetterCMSLiveConfig {\n /** Backend base, e.g. `https://api.bettercms.ai`. */\n apiUrl: string;\n workspace: string;\n /** A content:read key — exposed to the browser via the SSE URL query. */\n apiKey: string;\n}\n\nexport function defineBetterCMSLive(config: BetterCMSLiveConfig): { liveSrc: string } {\n const base = config.apiUrl.replace(/\\/+$/, \"\");\n const liveSrc = `${base}/api/v1/delivery/${config.workspace}/live?key=${encodeURIComponent(config.apiKey)}`;\n return { liveSrc };\n}\n","/**\n * createRevalidateRoute — App Router POST handler that receives the backend's\n * revalidation webhook and calls `revalidateTag()` for each affected tag. This is\n * the production live path (publish → backend webhook → revalidate ISR cache).\n *\n * import { createRevalidateRoute } from \"@bettercms-ai/next\";\n * export const { POST } = createRevalidateRoute({ secret: process.env.BCMS_REVALIDATE_SECRET });\n *\n * When `secret` is set, the `X-BetterCMS-Signature` HMAC-SHA256 header is verified\n * against the raw body (the same secret configured on the project in the dashboard).\n */\nimport { createHmac, timingSafeEqual } from \"node:crypto\";\n\nexport interface RevalidateRouteConfig {\n /** Shared secret matching the project's revalidation webhook secret. */\n secret?: string;\n}\n\nfunction safeEqual(a: string, b: string): boolean {\n const ab = Buffer.from(a);\n const bb = Buffer.from(b);\n return ab.length === bb.length && timingSafeEqual(ab, bb);\n}\n\nexport function createRevalidateRoute(config: RevalidateRouteConfig = {}) {\n async function POST(request: Request): Promise<Response> {\n const body = await request.text();\n\n if (config.secret) {\n const provided = request.headers.get(\"x-bettercms-signature\") ?? \"\";\n const expected = `sha256=${createHmac(\"sha256\", config.secret).update(body).digest(\"hex\")}`;\n if (!safeEqual(provided, expected)) {\n return new Response(\"Invalid signature\", { status: 401 });\n }\n }\n\n let payload: { data?: { tags?: string[] } };\n try {\n payload = JSON.parse(body);\n } catch {\n return new Response(\"Invalid JSON\", { status: 400 });\n }\n\n const tags = payload.data?.tags ?? [];\n const { revalidateTag } = await import(\"next/cache\");\n for (const tag of tags) revalidateTag(tag);\n\n return Response.json({ revalidated: true, tags });\n }\n\n return { POST };\n}\n"],"mappings":"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","names":["url","resolveSeo","default","readFileSync","resolve","readFileSync","resolve","readFileSync","resolve"]}
1
+ {"version":3,"sources":["../src/types.ts","../src/client.ts","../src/metadata.ts","../src/index.ts","../src/snapshot.ts","../src/llms-txt.ts","../src/preview.ts","../src/draft-route.ts","../src/forms-read.ts","../src/pages-read.ts","../src/components-read.ts","../src/field.tsx","../src/live-config.ts","../src/revalidate-route.ts"],"sourcesContent":["/**\n * Public types for @bettercms-ai/next.\n *\n * `BetterCMSEntry` mirrors the envelope emitted by @bettercms-ai/codegen's generated\n * file. The adapter normalizes the raw Delivery API response into exactly this shape,\n * so `getEntry`/`listEntries` line up 1:1 with your generated model types.\n */\n\n/** Normalized error thrown by adapter reads. Mirrors the SDK's BetterCMSError, but kept\n * local so the adapter carries no heavy runtime dependency into a consumer's bundle. */\nexport class BetterCMSError extends Error {\n readonly status: number;\n readonly code: string;\n\n constructor(message: string, status: number, code: string) {\n super(message);\n this.name = \"BetterCMSError\";\n this.status = status;\n this.code = code;\n }\n}\n\n/**\n * Delivery envelope around a model's typed `fields`. Returned by `getEntry`/`listEntries`.\n * Identical to the `BetterCMSEntry<TFields>` your generated types declare.\n *\n * `publishedAt`/`updatedAt` are ISO-8601 strings, or `null` when absent — never a\n * fabricated empty string (so `new Date(...)` is safe to attempt only on non-null).\n */\nexport interface BetterCMSEntry<TFields> {\n readonly slug: string;\n readonly status: \"draft\" | \"published\";\n readonly fields: TFields;\n readonly publishedAt: string | null;\n readonly updatedAt: string | null;\n}\n\nexport interface BetterCMSConfig {\n /** Workspace slug — the `:workspace` segment of the Delivery API path. */\n workspace: string;\n /** Optional delivery API key (sent as `Authorization: Bearer`). */\n apiKey?: string;\n /** Delivery API base. Default: `https://api.bettercms.ai/api/v1/delivery`. */\n baseUrl?: string;\n /** Preview API base. Default: derived from `baseUrl` (`/delivery` → `/preview`). */\n previewBaseUrl?: string;\n /**\n * Default Next.js revalidation for reads (seconds). `false` = always fresh\n * (`cache: \"no-store\"`). Per-call `revalidate` overrides this. Default: `60`.\n */\n revalidate?: number | false;\n}\n\n/** Per-read options shared by `getEntry` and `listEntries`. */\nexport interface ReadOptions {\n /** ISR window in seconds, or `false` for `cache: \"no-store\"`. Overrides config default. */\n revalidate?: number | false;\n /** Next.js cache tags for on-demand `revalidateTag()` invalidation. */\n tags?: string[];\n /** Reference hydration depth (0 = none, 1 = direct refs, 2 = nested). */\n depth?: 0 | 1 | 2;\n /** Field projection — only these field keys are returned. */\n select?: string[];\n}\n\n/** Options for `getEntry`, including draft-preview access. */\nexport interface GetEntryOptions extends ReadOptions {\n /** Fetch draft content via the preview endpoint. Requires `previewToken`. */\n preview?: boolean;\n /** Signed preview token (from `POST .../preview-token`). */\n previewToken?: string;\n}\n\n/** Options for `listEntries`. */\nexport interface ListEntriesOptions extends ReadOptions {\n /** 1-based page number. Default: 1. */\n page?: number;\n /** Items per page (max 100). Default: 20. */\n perPage?: number;\n}\n\n/** Paginated result returned by `listEntries`. */\nexport interface EntryList<TFields> {\n items: BetterCMSEntry<TFields>[];\n page: number;\n perPage: number;\n totalItems: number;\n totalPages: number;\n hasNextPage: boolean;\n hasPreviousPage: boolean;\n}\n","/**\n * createBetterCMS — the Next.js delivery client.\n *\n * Reads run through the standard `fetch`, which the Next.js App Router patches to\n * participate in the data cache. We pass `next: { revalidate, tags }` so content is\n * cached/ISR'd and can be invalidated on publish via `revalidateTag()`.\n *\n * The raw Delivery API response is normalized into `BetterCMSEntry<TFields>` so the\n * return types match what @bettercms-ai/codegen generates.\n */\n\nimport type { ContentBlock, PageMetaJson } from \"@bettercms-ai/types\";\nimport {\n BetterCMSError,\n type BetterCMSConfig,\n type BetterCMSEntry,\n type EntryList,\n type GetEntryOptions,\n type ListEntriesOptions,\n} from \"./types.js\";\n\n/** A published page with its block_json — feed `blocks` to `<BcmsBlocks>`.\n * The SEO fields (FLO-302) feed `buildMetadata` for the route's `generateMetadata`. */\nexport interface BetterCMSPage {\n slug: string;\n title: string;\n metaTitle: string | null;\n metaDescription: string | null;\n /** Rich SEO (OG / Twitter / canonical / JSON-LD). `null` when unset. */\n metaJson: PageMetaJson | null;\n blocks: ContentBlock[];\n publishedAt: string | null;\n updatedAt: string | null;\n}\n\n/** Raw single-page shape returned by the Delivery API (`/content/:slug`). */\ninterface RawPageResponse {\n data: {\n slug: string;\n entry: {\n slug: string;\n title: string;\n metaTitle?: string | null;\n metaDescription?: string | null;\n metaJson?: PageMetaJson | null;\n blocks?: ContentBlock[];\n updatedAt?: string | null;\n };\n publishedAt: string | null;\n };\n}\n\nconst DEFAULT_BASE_URL = \"https://api.bettercms.ai/api/v1/delivery\";\nconst DEFAULT_REVALIDATE = 60;\n\n/** `fetch`'s init augmented with Next.js's `next` cache directives. (`cache` is declared\n * explicitly because this package compiles without the DOM lib.) */\ntype NextRequestInit = RequestInit & {\n cache?: \"default\" | \"no-store\" | \"force-cache\";\n next?: { revalidate?: number | false; tags?: string[] };\n};\n\n/** Raw single-entry shape returned by the Delivery API. */\ninterface RawEntry {\n id: string;\n slug: string;\n status: \"draft\" | \"published\";\n publishedAt: string | null;\n updatedAt?: string | null;\n data: Record<string, unknown>;\n}\n\ninterface RawListResponse {\n data: {\n items: RawEntry[];\n page: number;\n perPage: number;\n totalItems: number;\n totalPages: number;\n hasNextPage: boolean;\n hasPreviousPage: boolean;\n };\n}\n\nfunction statusToCode(status: number): string {\n if (status === 404) return \"CONTENT_NOT_FOUND\";\n if (status === 401) return \"UNAUTHORIZED\";\n if (status === 403) return \"FORBIDDEN\";\n if (status === 429) return \"RATE_LIMITED\";\n if (status === 422) return \"VALIDATION_ERROR\";\n return \"INTERNAL_ERROR\";\n}\n\n/** Normalize a raw delivery entry into the codegen-aligned envelope. `publishedAt`\n * and `updatedAt` are kept distinct and pass through `null` rather than being\n * conflated or fabricated as \"\" (which would parse to an Invalid Date). */\nfunction mapEntry<TFields>(raw: RawEntry): BetterCMSEntry<TFields> {\n return {\n slug: raw.slug,\n status: raw.status,\n fields: raw.data as TFields,\n publishedAt: raw.publishedAt ?? null,\n updatedAt: raw.updatedAt ?? null,\n };\n}\n\n/**\n * The typed BetterCMS reader for Next.js.\n *\n * @typeParam Schema - your generated `BetterCMSSchema` (slug → fields registry). When\n * supplied, `listEntries(\"blog\")` is typed by the model's fields and the model name\n * autocompletes. Defaults to an open record so it also works untyped.\n */\nexport interface BetterCMSNext<Schema extends Record<string, unknown>> {\n /** Fetch one published entry by its content slug. Returns `null` when not found.\n * Passing `select` narrows the result to `Partial<TFields>` (projection drops fields). */\n getEntry<TFields = unknown>(\n slug: string,\n opts: GetEntryOptions & { select: string[] },\n ): Promise<BetterCMSEntry<Partial<TFields>> | null>;\n getEntry<TFields = unknown>(\n slug: string,\n opts?: GetEntryOptions,\n ): Promise<BetterCMSEntry<TFields> | null>;\n\n /** List published entries, filtered to one model. Throws `BetterCMSError`\n * (CONTENT_NOT_FOUND) for an unknown workspace/model — an empty model returns an\n * empty page. Passing `select` narrows fields to `Partial`. */\n listEntries<M extends keyof Schema & string>(\n model: M,\n opts: ListEntriesOptions & { select: string[] },\n ): Promise<EntryList<Partial<Schema[M]>>>;\n listEntries<M extends keyof Schema & string>(\n model: M,\n opts?: ListEntriesOptions,\n ): Promise<EntryList<Schema[M]>>;\n listEntries<TFields = unknown>(\n model?: string,\n opts?: ListEntriesOptions,\n ): Promise<EntryList<TFields>>;\n\n /** Fetch one published page (with its `blocks` for `<BcmsBlocks>`) by slug.\n * Returns `null` when not found. */\n getPage(\n slug: string,\n opts?: { revalidate?: number | false; tags?: string[] },\n ): Promise<BetterCMSPage | null>;\n}\n\nexport function createBetterCMS<\n Schema extends Record<string, unknown> = Record<string, unknown>,\n>(config: BetterCMSConfig): BetterCMSNext<Schema> {\n const baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n const previewBaseUrl = (\n config.previewBaseUrl ?? baseUrl.replace(/\\/delivery$/, \"/preview\")\n ).replace(/\\/+$/, \"\");\n const defaultRevalidate = config.revalidate ?? DEFAULT_REVALIDATE;\n\n function headers(): Record<string, string> {\n const h: Record<string, string> = { Accept: \"application/json\" };\n if (config.apiKey) h[\"Authorization\"] = `Bearer ${config.apiKey}`;\n return h;\n }\n\n function cacheInit(\n revalidate: number | false | undefined,\n tags: string[] | undefined,\n forceNoStore = false,\n ): NextRequestInit {\n const rv = revalidate ?? defaultRevalidate;\n // Preview is always uncached.\n if (forceNoStore) return { headers: headers(), cache: \"no-store\" };\n if (rv === false) {\n // With tags, \"false\" means cache until `revalidateTag` (the canonical CMS\n // publish-invalidation pattern) — we must NOT drop the tags to no-store, or\n // revalidateTag could never invalidate. Without tags, it means truly fresh.\n return tags?.length\n ? { headers: headers(), next: { revalidate: false, tags } }\n : { headers: headers(), cache: \"no-store\" };\n }\n return { headers: headers(), next: { revalidate: rv, tags } };\n }\n\n async function requestJSON<T>(\n url: string,\n init: NextRequestInit,\n { nullOn404 = false }: { nullOn404?: boolean } = {},\n ): Promise<T | null> {\n let res: Response;\n try {\n res = await fetch(url, init);\n } catch (err) {\n throw new BetterCMSError(\n `Network error: ${err instanceof Error ? err.message : String(err)}`,\n 0,\n \"NETWORK_ERROR\",\n );\n }\n // A single-entry 404 is \"no such entry\" (→ null). A list 404 is a real error\n // (unknown workspace/model) and must surface, not masquerade as an empty list.\n if (res.status === 404 && nullOn404) return null;\n if (!res.ok) {\n let message = res.statusText || \"Request failed\";\n try {\n const body = (await res.json()) as { error?: string; message?: string };\n message = body.error ?? body.message ?? message;\n } catch {\n /* ignore parse errors */\n }\n throw new BetterCMSError(message, res.status, statusToCode(res.status));\n }\n return (await res.json()) as T;\n }\n\n function entryQuery(opts?: { depth?: 0 | 1 | 2; select?: string[] }): string {\n const params = new URLSearchParams();\n if (opts?.depth != null) params.set(\"depth\", String(opts.depth));\n if (opts?.select?.length) params.set(\"select\", opts.select.join(\",\"));\n const qs = params.toString();\n return qs ? `?${qs}` : \"\";\n }\n\n const client = {\n async getEntry<TFields = unknown>(slug: string, opts?: GetEntryOptions) {\n const encoded = encodeURIComponent(slug);\n const query = entryQuery(opts);\n\n // Preview: drafts are only reachable through the signed preview endpoint, and\n // must never be cached (every request re-validates the token server-side).\n if (opts?.preview) {\n if (!opts.previewToken) {\n throw new BetterCMSError(\n \"preview: true requires a previewToken\",\n 400,\n \"VALIDATION_ERROR\",\n );\n }\n const tokenParam = `token=${encodeURIComponent(opts.previewToken)}`;\n const sep = query ? \"&\" : \"?\";\n const url = `${previewBaseUrl}/${encoded}${query}${sep}${tokenParam}`;\n const body = await requestJSON<{ data: RawEntry }>(\n url,\n cacheInit(undefined, undefined, true),\n { nullOn404: true },\n );\n return body ? mapEntry<TFields>(body.data) : null;\n }\n\n const url = `${baseUrl}/${config.workspace}/content-entries/${encoded}${query}`;\n const raw = await requestJSON<RawEntry>(\n url,\n cacheInit(opts?.revalidate, opts?.tags),\n { nullOn404: true },\n );\n return raw ? mapEntry<TFields>(raw) : null;\n },\n\n async listEntries<TFields = unknown>(\n model?: string,\n opts?: ListEntriesOptions,\n ): Promise<EntryList<TFields>> {\n const params = new URLSearchParams();\n if (model) params.set(\"model\", model);\n if (opts?.page != null) params.set(\"page\", String(opts.page));\n if (opts?.perPage != null) params.set(\"perPage\", String(opts.perPage));\n if (opts?.depth != null) params.set(\"depth\", String(opts.depth));\n if (opts?.select?.length) params.set(\"select\", opts.select.join(\",\"));\n\n const url = `${baseUrl}/${config.workspace}/content-entries?${params}`;\n const body = await requestJSON<RawListResponse>(\n url,\n cacheInit(opts?.revalidate, opts?.tags),\n );\n\n const data = body?.data;\n if (!data) {\n return {\n items: [],\n page: opts?.page ?? 1,\n perPage: opts?.perPage ?? 20,\n totalItems: 0,\n totalPages: 1,\n hasNextPage: false,\n hasPreviousPage: false,\n };\n }\n return {\n items: data.items.map((r) => mapEntry<TFields>(r)),\n page: data.page,\n perPage: data.perPage,\n totalItems: data.totalItems,\n totalPages: data.totalPages,\n hasNextPage: data.hasNextPage,\n hasPreviousPage: data.hasPreviousPage,\n };\n },\n\n async getPage(\n slug: string,\n opts?: { revalidate?: number | false; tags?: string[] },\n ): Promise<BetterCMSPage | null> {\n const url = `${baseUrl}/${config.workspace}/content/${encodeURIComponent(slug)}`;\n const body = await requestJSON<RawPageResponse>(\n url,\n cacheInit(opts?.revalidate, opts?.tags),\n { nullOn404: true },\n );\n if (!body) return null;\n const { entry, publishedAt } = body.data;\n return {\n slug: entry.slug,\n title: entry.title,\n metaTitle: entry.metaTitle ?? null,\n metaDescription: entry.metaDescription ?? null,\n metaJson: entry.metaJson ?? null,\n blocks: entry.blocks ?? [],\n publishedAt: publishedAt ?? null,\n updatedAt: entry.updatedAt ?? null,\n };\n },\n };\n\n // The concrete impls use single generic signatures; the public interface exposes\n // richer overloads (select→Partial, model-keyed typing). Cast once here.\n return client as unknown as BetterCMSNext<Schema>;\n}\n","/**\n * buildMetadata — map a BetterCMS page's SEO onto a Next.js `Metadata` object.\n *\n * Wire it into a route's `generateMetadata` so per-page Meta Title / Description and\n * the rich OG / Twitter / canonical the dashboard saves drive the live `<head>`:\n *\n * import { createBetterCMS, buildMetadata } from \"@bettercms-ai/next\";\n * const bcms = createBetterCMS({ workspace: \"acme\" });\n *\n * export async function generateMetadata({ params }): Promise<Metadata> {\n * const page = await bcms.getPage(params.slug);\n * if (!page) return {};\n * return buildMetadata(page, siteDefaults); // siteDefaults optional\n * }\n *\n * JSON-LD isn't part of Next's `Metadata`; render `resolveSeo(page, defaults).jsonLd`\n * as a `<script type=\"application/ld+json\">` in the page component (see the README).\n *\n * The merge (page-over-site) is delegated to `resolveSeo` (@bettercms-ai/sdk), the same\n * resolver the Astro adapter uses — so every surface stays consistent.\n */\nimport type { Metadata } from \"next\";\nimport { resolveSeo, type SeoInput } from \"@bettercms-ai/sdk\";\nimport type { SiteSeoDefaults } from \"@bettercms-ai/types\";\n\n/** Build a Next.js `Metadata` from a BetterCMS page, layering page meta over site defaults. */\nexport function buildMetadata(page: SeoInput, siteDefaults: SiteSeoDefaults = {}): Metadata {\n const seo = resolveSeo(page, siteDefaults);\n return {\n title: seo.title || undefined,\n description: seo.description || undefined,\n alternates: seo.canonical ? { canonical: seo.canonical } : undefined,\n openGraph: {\n title: seo.og.title || undefined,\n description: seo.og.description || undefined,\n type: (seo.og.type || \"website\") as \"website\",\n url: seo.og.url || undefined,\n images: seo.og.image ? [{ url: seo.og.image }] : undefined,\n },\n twitter: {\n card: (seo.twitter.card || \"summary\") as \"summary\" | \"summary_large_image\",\n title: seo.twitter.title || undefined,\n description: seo.twitter.description || undefined,\n images: seo.twitter.image ? [seo.twitter.image] : undefined,\n site: seo.twitter.site || undefined,\n },\n };\n}\n","/**\n * @bettercms-ai/next — the BetterCMS adapter for Next.js.\n *\n * Pair with @bettercms-ai/codegen: generate `BetterCMSSchema` from your content models,\n * then `createBetterCMS<BetterCMSSchema>(...)` for typed, cache-aware reads in the App Router.\n */\n\nexport { createBetterCMS } from \"./client.js\";\nexport type { BetterCMSNext, BetterCMSPage } from \"./client.js\";\n\n// SEO (FLO-302): map a page's per-page-over-site SEO into a Next `Metadata` for\n// `generateMetadata`. `resolveSeo` (+ its types) is re-exported for JSON-LD / custom heads.\nexport { buildMetadata } from \"./metadata.js\";\nexport { resolveSeo } from \"@bettercms-ai/sdk\";\nexport type { SeoInput, ResolvedSeo } from \"@bettercms-ai/sdk\";\nexport type { PageMetaJson, SiteSeoDefaults } from \"@bettercms-ai/types\";\n\nexport { BetterCMSError } from \"./types.js\";\nexport type {\n BetterCMSConfig,\n BetterCMSEntry,\n ReadOptions,\n GetEntryOptions,\n ListEntriesOptions,\n EntryList,\n} from \"./types.js\";\n\nexport {\n getContent,\n buildSnapshot,\n writeSnapshot,\n readSnapshot,\n serializeSnapshot,\n parseSnapshot,\n SNAPSHOT_VERSION,\n DEFAULT_SNAPSHOT_FILE,\n} from \"./snapshot.js\";\nexport type { ContentSource, ContentSnapshot } from \"./snapshot.js\";\n\nexport { generateLlmsTxt, llmsTxtRoute, fetchModels } from \"./llms-txt.js\";\nexport type {\n LlmsModel,\n LlmsModelField,\n LlmsTxtOptions,\n LlmsTxtRouteConfig,\n} from \"./llms-txt.js\";\n\nexport { isDraftEnabled, getPreviewToken } from \"./preview.js\";\n\n// Draft-mode route-handler factories for the App Router.\nexport {\n createDraftModeRoute,\n createDisableDraftRoute,\n PREVIEW_TOKEN_COOKIE,\n} from \"./draft-route.js\";\nexport type { DraftModeRouteConfig } from \"./draft-route.js\";\n\n// Image URL builder — `import { imageUrlBuilder } from \"@bettercms-ai/next\"`.\nexport { default as imageUrlBuilder, imageUrl } from \"@bettercms-ai/image-url\";\nexport type {\n ImageUrlBuilder,\n ImageSource,\n ImageFormat,\n ImageFit,\n} from \"@bettercms-ai/image-url\";\n\n// Build-time forms reader for the deploy Action's snapshot (server-side).\nexport { readForms, getForm } from \"./forms-read.js\";\nexport type { SnapshotForms } from \"./forms-read.js\";\n\n// Build-time pages reader for the deploy Action's snapshot (server-side). Pair with\n// <BcmsBlocks> to render a page's block_json: readPages()/getPage(slug) → page.blocks.\nexport { readPages, getPage } from \"./pages-read.js\";\nexport type { SnapshotPage } from \"./pages-read.js\";\n\n// Build-time components reader — pass to <BcmsBlocks components={…} /> so `component`\n// blocks resolve their reusable definitions.\nexport { readComponents, getComponent } from \"./components-read.js\";\n\n// <BcmsForm> / <BcmsBlocks> are client components and ship from their own \"use client\"\n// entries so the React Server Components boundary survives bundling:\n// import { BcmsForm } from \"@bettercms-ai/next/form\";\n// import { BcmsBlocks } from \"@bettercms-ai/next/blocks\";\nexport type { BcmsFormProps } from \"./form.js\";\nexport type { BcmsBlocksProps } from \"./blocks.js\";\n\n// <BcmsField> tags an entry field's value so the visual editor binds click-to-edit\n// deterministically. Server-safe (no \"use client\"): import from \"@bettercms-ai/next\".\nexport { BcmsField } from \"./field.js\";\nexport type { BcmsFieldProps } from \"./field.js\";\n\n// Live content + revalidation (mirrors next-sanity defineLive/<SanityLive>).\n// import { BcmsLive } from \"@bettercms-ai/next/live\";\nexport type { BcmsLiveProps } from \"./live.js\";\nexport { defineBetterCMSLive } from \"./live-config.js\";\nexport type { BetterCMSLiveConfig } from \"./live-config.js\";\nexport { createRevalidateRoute } from \"./revalidate-route.js\";\nexport type { RevalidateRouteConfig } from \"./revalidate-route.js\";\n\n// Visual editing overlay (client). import { VisualEditing } from \"@bettercms-ai/next/visual-editing\"\nexport type { VisualEditingProps } from \"./visual-editing.js\";\n","/**\n * Build-time content snapshot — the writer + reader of `bcms-content.json`.\n *\n * Why this exists: a static (`output: \"export\"`) build must resolve all content at\n * build time. If a page instead falls back to a live `cache: \"no-store\"` fetch, its\n * route becomes dynamic and `output: \"export\"` silently drops it — you get an empty\n * `out/`. The fix is a single build-time snapshot the pages read synchronously.\n *\n * The WRITER (`buildSnapshot`/`writeSnapshot`, used by the `bettercms-snapshot` bin)\n * and the READER (`getContent`) share ONE serializer (`serializeSnapshot`/\n * `parseSnapshot`) so the on-disk shape can never drift between the two halves.\n *\n * This is intentionally separate from {@link createBetterCMS}: that stays the live,\n * network-backed client. `getContent` is the snapshot-preferring reader a static\n * site uses. Existing consumers that deliberately rely on the live fallback are\n * unaffected — only `getContent` enforces \"snapshot or bust\".\n */\n\nimport { readFileSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { dirname, resolve } from \"node:path\";\nimport {\n BetterCMSError,\n type BetterCMSConfig,\n type BetterCMSEntry,\n type EntryList,\n type ListEntriesOptions,\n} from \"./types.js\";\nimport { createBetterCMS } from \"./client.js\";\n\n/** Bump when the on-disk shape changes incompatibly — readers reject mismatches. */\nexport const SNAPSHOT_VERSION = 1;\n/** Default snapshot filename, resolved against `process.cwd()`. */\nexport const DEFAULT_SNAPSHOT_FILE = \"bcms-content.json\";\n\n/** The full-site content snapshot. Entries are grouped by model slug so the reader\n * can serve `listEntries(model)` offline without a per-entry model tag. */\nexport interface ContentSnapshot {\n version: number;\n workspace: string;\n generatedAt: string;\n models: Record<string, BetterCMSEntry<unknown>[]>;\n}\n\n// ── Serializer (shared by writer + reader — single source of on-disk shape) ──────\n\n/** Serialize a snapshot to the canonical JSON form written to disk. */\nexport function serializeSnapshot(snapshot: ContentSnapshot): string {\n return JSON.stringify(snapshot, null, 2);\n}\n\n/** Parse + validate a snapshot JSON string. Throws on a version mismatch so a stale\n * snapshot fails loudly rather than feeding the build the wrong shape. */\nexport function parseSnapshot(json: string): ContentSnapshot {\n const parsed = JSON.parse(json) as ContentSnapshot;\n if (parsed.version !== SNAPSHOT_VERSION) {\n throw new BetterCMSError(\n `Unsupported bcms-content.json version ${parsed.version} (expected ${SNAPSHOT_VERSION}). ` +\n `Regenerate it with \"bettercms-snapshot\".`,\n 0,\n \"SNAPSHOT_VERSION_MISMATCH\",\n );\n }\n return parsed;\n}\n\n// ── Disk I/O ─────────────────────────────────────────────────────────────────────\n\n/** Write a snapshot to `path` (relative paths resolve against cwd), creating dirs. */\nexport function writeSnapshot(path: string, snapshot: ContentSnapshot): void {\n const out = resolve(process.cwd(), path);\n mkdirSync(dirname(out), { recursive: true });\n writeFileSync(out, serializeSnapshot(snapshot), \"utf8\");\n}\n\n/** Read + parse a snapshot from `path`. Returns `null` only when the file is absent;\n * a present-but-corrupt/stale file throws (callers must not treat that as \"missing\"). */\nexport function readSnapshot(path: string): ContentSnapshot | null {\n const out = resolve(process.cwd(), path);\n let json: string;\n try {\n json = readFileSync(out, \"utf8\");\n } catch (err) {\n if ((err as NodeJS.ErrnoException)?.code === \"ENOENT\") return null;\n throw err;\n }\n return parseSnapshot(json);\n}\n\n// ── Writer: build a snapshot from the live API ────────────────────────────────────\n\n/** Minimal client surface `buildSnapshot` needs — lets tests inject a fake. Kept\n * non-generic so a simple fake (and the live client's matching overload) both fit. */\ntype ListOnly = {\n listEntries(model?: string, opts?: ListEntriesOptions): Promise<EntryList<unknown>>;\n};\n\n/**\n * Page through every model's published entries and assemble a full-site snapshot.\n * `modelSlugs` come from the Management API (`fetchModels`) so the snapshot covers\n * exactly this project's schema. Pass `client` in tests; defaults to the live client.\n */\nexport async function buildSnapshot(opts: {\n config: BetterCMSConfig;\n modelSlugs: string[];\n client?: ListOnly;\n /** Stamped into `generatedAt`; injectable for deterministic tests. */\n now?: string;\n}): Promise<ContentSnapshot> {\n const client = opts.client ?? createBetterCMS(opts.config);\n const models: Record<string, BetterCMSEntry<unknown>[]> = {};\n for (const slug of opts.modelSlugs) {\n const all: BetterCMSEntry<unknown>[] = [];\n let page = 1;\n for (;;) {\n // revalidate:false → bypass any data cache; this is a one-shot build fetch.\n const res = await client.listEntries(slug, { page, perPage: 100, revalidate: false });\n all.push(...res.items);\n if (!res.hasNextPage) break;\n page += 1;\n }\n models[slug] = all;\n }\n return {\n version: SNAPSHOT_VERSION,\n workspace: opts.config.workspace,\n generatedAt: opts.now ?? new Date().toISOString(),\n models,\n };\n}\n\n// ── Reader: getContent (snapshot-preferring, loud on absence) ─────────────────────\n\n/** Snapshot-backed content reader. Async so it can transparently fall back to the\n * live client in preview without changing the call sites. */\nexport interface ContentSource {\n getEntry<TFields = unknown>(slug: string): Promise<BetterCMSEntry<TFields> | null>;\n listEntries<TFields = unknown>(model?: string): Promise<EntryList<TFields>>;\n}\n\n/** `BETTERCMS_PREVIEW` truthy → read live (uncached), never the snapshot. */\nfunction isPreview(): boolean {\n const v = (\n globalThis as { process?: { env?: Record<string, string | undefined> } }\n ).process?.env?.BETTERCMS_PREVIEW;\n return v != null && v !== \"\" && v !== \"0\" && v !== \"false\";\n}\n\n/** Parsed snapshots keyed by resolved path — read+parse once per build process. */\nconst snapshotCache = new Map<string, ContentSnapshot>();\n\n/** Test-only: clear the module-scope snapshot cache between cases. */\nexport function __resetSnapshotCacheForTests(): void {\n snapshotCache.clear();\n}\n\nfunction emptyPage<TFields>(items: BetterCMSEntry<TFields>[]): EntryList<TFields> {\n return {\n items,\n page: 1,\n perPage: items.length,\n totalItems: items.length,\n totalPages: 1,\n hasNextPage: false,\n hasPreviousPage: false,\n };\n}\n\n/**\n * The reader a static site should use. In a normal (export) build it reads the\n * memoized `bcms-content.json` and serves entries from memory. If the snapshot is\n * absent it THROWS a clear, actionable error — the whole point is to turn the old\n * silent empty-`out/` failure into a loud build error. Set `BETTERCMS_PREVIEW=1`\n * for a preview build that reads live instead.\n */\nexport function getContent<\n Schema extends Record<string, unknown> = Record<string, unknown>,\n>(config: BetterCMSConfig, opts: { snapshotPath?: string } = {}): ContentSource {\n const path = opts.snapshotPath ?? DEFAULT_SNAPSHOT_FILE;\n\n if (isPreview()) {\n const live = createBetterCMS<Schema>(config);\n return {\n getEntry: (slug) => live.getEntry(slug, { revalidate: false }),\n listEntries: (model) => live.listEntries(model, { revalidate: false }),\n };\n }\n\n const resolved = resolve(process.cwd(), path);\n function load(): ContentSnapshot {\n const cached = snapshotCache.get(resolved);\n if (cached) return cached;\n const snap = readSnapshot(path);\n if (!snap) {\n throw new BetterCMSError(\n `BetterCMS content snapshot \"${path}\" not found. A static (output: \"export\") build reads ` +\n `content from this snapshot; without it pages fall back to a live no-store fetch, become ` +\n `dynamic routes, and output: \"export\" silently drops them (empty out/). Run ` +\n `\"bettercms-snapshot\" before \"next build\" (make it your build:preview prestep), or set ` +\n `BETTERCMS_PREVIEW=1 to read live in a preview build.`,\n 0,\n \"SNAPSHOT_MISSING\",\n );\n }\n snapshotCache.set(resolved, snap);\n return snap;\n }\n\n return {\n async getEntry<TFields = unknown>(slug: string) {\n const snap = load();\n for (const entries of Object.values(snap.models)) {\n const found = entries.find((e) => e.slug === slug);\n if (found) return found as BetterCMSEntry<TFields>;\n }\n return null;\n },\n async listEntries<TFields = unknown>(model?: string) {\n const snap = load();\n const items = (\n model ? (snap.models[model] ?? []) : Object.values(snap.models).flat()\n ) as BetterCMSEntry<TFields>[];\n return emptyPage(items);\n },\n };\n}\n","/**\n * llms.txt generation — the AI-discovery surface of the deterministic floor.\n *\n * `generateLlmsTxt` is pure (models in → string out), so it is trivially testable and\n * deterministic. `llmsTxtRoute` wraps it as an App Router `route.ts` handler. `fetchModels`\n * pulls the live schema from the Management API for sites that want a self-updating file.\n *\n * Output follows the llmstxt.org convention: an H1 title, a `>` summary, then sections.\n */\n\n/** Minimal field shape needed to describe a model in llms.txt. */\nexport interface LlmsModelField {\n key: string;\n label?: string;\n type: string;\n required?: boolean;\n}\n\n/** Minimal model shape — a subset of the Management API model row. */\nexport interface LlmsModel {\n slug: string;\n name?: string;\n description?: string | null;\n fields: LlmsModelField[];\n}\n\nexport interface LlmsTxtOptions {\n /** H1 title. Default: \"BetterCMS content\". */\n title?: string;\n /** One-line `>` summary under the title. */\n description?: string;\n /** Delivery API base used to render example endpoints. */\n baseUrl?: string;\n /** Workspace slug used in example endpoints. */\n workspace?: string;\n /** Extra free-form lines appended under a \"## Notes\" section. */\n notes?: string[];\n}\n\n// Model name/slug/description and field key/label/type are author/agent-controlled\n// (the same content the sibling codegen package treats as hostile via escapeJsDoc).\n// llms.txt is fed to AI crawlers, so unescaped values could forge headings/blockquotes\n// or break out of code spans — a structure-spoofing / prompt-injection vector.\n\n/** Collapse to a single line — kills newline-based block injection. */\nfunction oneLine(text: string): string {\n return text.replace(/[\\r\\n]+/g, \" \").trim();\n}\n\n/** For text rendered OUTSIDE a code span (titles, descriptions, labels): single-line +\n * escape a leading markdown block-control char so it can't start a heading/list/quote. */\nfunction inlineText(text: string): string {\n return oneLine(text).replace(/^([#>\\-*+=|])/, \"\\\\$1\");\n}\n\n/** For text rendered INSIDE a code span: strip backticks (which would close the span)\n * and newlines. */\nfunction codeSpan(text: string): string {\n return oneLine(text).replace(/`/g, \"\");\n}\n\nfunction fieldLine(f: LlmsModelField): string {\n const req = f.required ? \", required\" : \"\";\n const label = f.label && f.label !== f.key ? ` — ${inlineText(f.label)}` : \"\";\n return ` - \\`${codeSpan(f.key)}\\` (${codeSpan(f.type)}${req})${label}`;\n}\n\n/**\n * Render an llms.txt document describing the available content models. Deterministic:\n * models are sorted by slug; field order is preserved as authored.\n */\nexport function generateLlmsTxt(models: LlmsModel[], opts: LlmsTxtOptions = {}): string {\n const title = opts.title ?? \"BetterCMS content\";\n const sorted = [...models].sort((a, b) =>\n a.slug < b.slug ? -1 : a.slug > b.slug ? 1 : 0,\n );\n\n const lines: string[] = [`# ${inlineText(title)}`, \"\"];\n if (opts.description) {\n lines.push(`> ${inlineText(opts.description)}`, \"\");\n }\n\n const base =\n opts.baseUrl && opts.workspace\n ? `${opts.baseUrl.replace(/\\/+$/, \"\")}/${opts.workspace}/content-entries`\n : undefined;\n\n lines.push(\"## Content models\", \"\");\n if (sorted.length === 0) {\n lines.push(\"_No content models are defined yet._\", \"\");\n }\n for (const model of sorted) {\n const name = model.name ?? model.slug;\n lines.push(`### ${inlineText(name)} (\\`${codeSpan(model.slug)}\\`)`);\n if (model.description) lines.push(\"\", inlineText(model.description));\n if (base) {\n lines.push(\"\", `- List: \\`GET ${base}?model=${codeSpan(model.slug)}\\``);\n lines.push(`- Entry: \\`GET ${base}/{slug}\\``);\n }\n if (model.fields.length) {\n lines.push(\"\", \"Fields:\");\n for (const f of model.fields) lines.push(fieldLine(f));\n }\n lines.push(\"\");\n }\n\n if (opts.notes?.length) {\n lines.push(\"## Notes\", \"\");\n for (const note of opts.notes) lines.push(`- ${inlineText(note)}`);\n lines.push(\"\");\n }\n\n return lines.join(\"\\n\");\n}\n\n// ── Integration SKILL.md ────────────────────────────────────────────────────────\n// A framework-agnostic agent skill (frontmatter + lean body) that teaches an AI how\n// to READ this project's content from the Delivery API. Unlike llms.txt it does NOT\n// dump every field — it points at the live types endpoint so the schema can't go\n// stale, and names content types by slug only. Drop at .claude/skills/<name>/SKILL.md.\n\n/** A content type named in the skill — slug + kind only (no fields). */\nexport interface SkillContentType {\n kind: \"page\" | \"model\";\n slug: string;\n /** \"singleton\" | \"dynamic\" for pages; omitted for models. */\n pageType?: string | null;\n}\n\nexport interface SkillOptions {\n /** Human project name (skill title + description). */\n projectName: string;\n /** Project slug → the skill's kebab-case `name:`. */\n projectSlug: string;\n /** Delivery + management API base, e.g. https://api.bettercms.ai/api/v1. */\n apiUrl: string;\n /** Workspace slug used in the delivery paths. */\n workspace: string;\n /** Where to mint a content:read delivery key. */\n mintUrl: string;\n /** Key-authed live TypeScript types endpoint. */\n typesUrl: string;\n /** This project's content types (pages + models), by slug — no field dump. */\n contentTypes: SkillContentType[];\n}\n\n/**\n * Render a concise, framework-agnostic SKILL.md describing how to read THIS\n * project's content from the BetterCMS Delivery API. Deterministic: content types\n * sorted by slug. Author-controlled strings (project name, slugs, pageType) are\n * injection-escaped, same as {@link generateLlmsTxt}.\n */\nexport type SkillTarget = \"claude\" | \"cursor\" | \"agents\";\n\nexport interface SkillVariant {\n /** The agent/IDE this variant targets. */\n target: SkillTarget;\n /** Human label for a tab/picker. */\n label: string;\n /** Suggested path to save the file, e.g. `.claude/skills/<name>/SKILL.md`. */\n filename: string;\n /** Full file content (target-specific frontmatter + the shared body). */\n content: string;\n}\n\n/** The shared, tool-agnostic instruction body — no frontmatter, no save hint. */\nfunction skillBody(opts: SkillOptions): string {\n const proj = inlineText(opts.projectName);\n const api = opts.apiUrl.replace(/\\/+$/, \"\");\n const deliveryBase = `${api}/delivery/${codeSpan(opts.workspace)}`;\n const sorted = [...opts.contentTypes].sort((a, b) =>\n a.slug < b.slug ? -1 : a.slug > b.slug ? 1 : 0,\n );\n\n const lines: string[] = [\n `# BetterCMS — ${proj}`,\n \"\",\n `${proj}'s content lives in BetterCMS. Read it through the Delivery API with a project ` +\n \"`content:read` key. Pull field shapes live from the types endpoint below — don't hardcode \" +\n \"them, the schema can change.\",\n \"\",\n \"## Auth\",\n \"Send `X-API-Key: <key>` on every read. Mint a long-lived `content:read` key (reads PUBLISHED \" +\n `content, never expires) at ${opts.mintUrl}. For drafts, mint a \\`content:read:draft\\` key ` +\n \"(force-expired to ≤1h), then publish and switch to the long-lived key.\",\n \"\",\n \"## Content types\",\n ];\n if (sorted.length === 0) {\n lines.push(\"_None yet — create pages or models first (e.g. via the BetterCMS MCP)._\");\n } else {\n for (const ct of sorted) {\n const kind =\n ct.kind === \"page\"\n ? `page${ct.pageType ? ` (${codeSpan(ct.pageType)})` : \"\"}`\n : \"model\";\n lines.push(`- \\`${codeSpan(ct.slug)}\\` — ${kind}`);\n }\n }\n lines.push(\n \"\",\n `## Reading (Delivery API · base \\`${deliveryBase}\\`)`,\n \"- Page: `GET .../content/{slug}`\",\n \"- Entries (list): `GET .../content-entries?model={slug}`\",\n \"- Entry: `GET .../content-entries/{slug}`\",\n \"\",\n \"## Typed client\",\n `- Live TypeScript types: \\`GET ${opts.typesUrl}\\` — authoritative field shapes; generate/refresh from here.`,\n \"- `npm i @bettercms-ai/next @bettercms-ai/codegen`, then construct the client. \" +\n \"`baseUrl` + `workspace` are required — there is no `apiUrl`/`endpoint` option:\",\n \"\",\n \"```ts\",\n 'import { createBetterCMS } from \"@bettercms-ai/next\";',\n \"const cms = createBetterCMS<BetterCMSSchema>({\",\n ` baseUrl: \"${api}/delivery\",`,\n ` workspace: \"${codeSpan(opts.workspace)}\",`,\n \" apiKey: process.env.BETTERCMS_API_KEY,\",\n \"});\",\n \"```\",\n \"\",\n \"## Field shapes you'll read\",\n \"Canonical READ shapes the Delivery API returns. Per-field types come from the types \" +\n \"endpoint; these container shapes are stable:\",\n '- **richtext** → `{ html, value: { root: … }, format: \"lexical-…\" }`. Render `html`, ' +\n \"or walk `value.root` for structured rendering.\",\n \"- **image** → `{ url, altText?, width?, height?, id? }` — an object, use `.url` (the \" +\n \"write value is a bare CDN URL string, but reads are normalized to this object).\",\n \"- **array + zones** → `{ nonRepeatable?: { <key>: value }, repeatable?: Array<{ <key>: \" +\n \"value }> }`. `nonRepeatable` is one fixed block; `repeatable` is a list — map over it.\",\n '- **array (primitive)** → a flat list (e.g. `string[]`). `type: \"array\"` alone is ' +\n \"ambiguous — the generated type tells you which.\",\n \"\",\n \"Example entry `fields` (a `sections` zone field + a `tags` primitive array):\",\n \"```jsonc\",\n \"{\",\n ' \"sections\": {',\n ' \"nonRepeatable\": { \"eyebrow\": \"New\", \"heading\": \"Welcome\" },',\n ' \"repeatable\": [{ \"label\": \"Docs\", \"url\": \"/docs\", \"icon\": { \"url\": \"https://cdn…\" } }]',\n \" },\",\n ' \"tags\": [\"alpha\", \"beta\"]',\n \"}\",\n \"```\",\n \"\",\n \"## Live Preview bindings (makes the site visually editable)\",\n \"Generate a bindings module alongside the types and spread it so the dashboard's Live \" +\n \"Preview can edit each field in place:\",\n \"```bash\",\n `BETTERCMS_API_KEY=<key> npx bettercms-codegen --api-url ${api} --bindings-out src/bettercms.bindings.generated.ts`,\n \"```\",\n \"```tsx\",\n 'import { bcms } from \"./bettercms.bindings.generated\";',\n \"// scalar: <h1 {...bcms.<model>.<field>}>{entry.fields.field}</h1>\",\n \"// list item: <article {...bcms.<model>.<arr>.$(i)}><h3 {...bcms.<model>.<arr>.<sub>(i)}>{item.sub}</h3></article>\",\n \"// primitive list:<li {...bcms.<model>.<arr>.value(i)}>{tag}</li>\",\n \"```\",\n \"Bindings emit attributes only when the platform builds with `BCMS_ANNOTATE` (preview \" +\n \"builds); production ships nothing extra, so spreading them is always safe.\",\n );\n return lines.join(\"\\n\");\n}\n\nfunction skillDescription(proj: string): string {\n return (\n `Read ${proj}'s content from BetterCMS via the Delivery API — auth, listing and fetching ` +\n \"pages and content entries, pulling live TypeScript types, and the @bettercms-ai/next typed \" +\n \"client. Use whenever fetching, rendering, or typing this project's CMS content.\"\n );\n}\n\n/**\n * Render the integration skill for a specific agent `target`. The instruction body is\n * identical across tools; only the frontmatter (and where you save it) differ:\n * - claude → Claude Code skill (`name` + `description`)\n * - cursor → Cursor `.mdc` rule (`description` + `globs` + `alwaysApply`, \"Agent Requested\")\n * - agents → plain Markdown, no frontmatter (drop into a repo-root `AGENTS.md`)\n */\nexport function generateSkill(opts: SkillOptions, target: SkillTarget = \"claude\"): string {\n const name = `bettercms-${codeSpan(opts.projectSlug)}`;\n const desc = skillDescription(inlineText(opts.projectName));\n const body = skillBody(opts);\n\n let frontmatter = \"\";\n if (target === \"claude\") {\n frontmatter = `---\\nname: ${name}\\ndescription: ${desc}\\n---\\n\\n`;\n } else if (target === \"cursor\") {\n frontmatter = `---\\ndescription: ${desc}\\nglobs:\\nalwaysApply: false\\n---\\n\\n`;\n }\n return `${frontmatter}${body}\\n`;\n}\n\nconst TARGET_META: Record<SkillTarget, { label: string; file: (name: string) => string }> = {\n claude: { label: \"Claude Code\", file: (n) => `.claude/skills/${n}/SKILL.md` },\n cursor: { label: \"Cursor\", file: (n) => `.cursor/rules/${n}.mdc` },\n agents: { label: \"AGENTS.md (generic)\", file: () => \"AGENTS.md\" },\n};\n\n/** Render the skill for every supported agent target — drives a tabbed picker in the UI. */\nexport function generateSkillVariants(opts: SkillOptions): SkillVariant[] {\n const name = `bettercms-${codeSpan(opts.projectSlug)}`;\n return (Object.keys(TARGET_META) as SkillTarget[]).map((target) => ({\n target,\n label: TARGET_META[target].label,\n filename: TARGET_META[target].file(name),\n content: generateSkill(opts, target),\n }));\n}\n\n/** Options for {@link llmsTxtRoute}. Provide one of `models` or `getModels`. */\nexport interface LlmsTxtRouteConfig extends LlmsTxtOptions {\n /** Static model list (e.g. fetched at build time). */\n models?: LlmsModel[];\n /** Dynamic provider — called per request (wrap in your own caching if needed). */\n getModels?: () => Promise<LlmsModel[]> | LlmsModel[];\n /** Cache-Control header value. Default: `public, max-age=3600`. */\n cacheControl?: string;\n}\n\n/**\n * Build an App Router route handler that serves llms.txt as `text/plain`.\n *\n * ```ts\n * // app/llms.txt/route.ts\n * import { llmsTxtRoute } from \"@bettercms-ai/next\";\n * export const GET = llmsTxtRoute({ models, title: \"Acme\", workspace: \"acme\" });\n * ```\n */\nexport function llmsTxtRoute(config: LlmsTxtRouteConfig): () => Promise<Response> {\n const { models, getModels, cacheControl, ...opts } = config;\n return async () => {\n const resolved = getModels ? await getModels() : (models ?? []);\n const body = generateLlmsTxt(resolved, opts);\n return new Response(body, {\n status: 200,\n headers: {\n \"Content-Type\": \"text/plain; charset=utf-8\",\n \"Cache-Control\": cacheControl ?? \"public, max-age=3600\",\n },\n });\n };\n}\n\n/**\n * Fetch content models from the Management API (`GET /management/content/models`).\n * The key is project-scoped server-side, so this returns exactly this site's schema.\n * Dependency-free so it runs in any runtime (Edge route, build script, Node).\n */\nexport async function fetchModels(opts: {\n /** Management API base, e.g. \"https://api.bettercms.ai/api/v1\". */\n apiUrl: string;\n /** A management-scoped key (content:manage). */\n apiKey: string;\n /** Optional fetch override (testing / custom runtime). */\n fetchImpl?: typeof fetch;\n}): Promise<LlmsModel[]> {\n const doFetch = opts.fetchImpl ?? fetch;\n const base = opts.apiUrl.replace(/\\/+$/, \"\");\n const res = await doFetch(`${base}/management/content/models`, {\n headers: { Authorization: `Bearer ${opts.apiKey}`, Accept: \"application/json\" },\n });\n if (!res.ok) {\n throw new Error(`Management API returned ${res.status} ${res.statusText}`);\n }\n const body = (await res.json()) as { data?: LlmsModel[] };\n return (body.data ?? []).map((m) => ({\n slug: m.slug,\n name: m.name,\n description: m.description ?? null,\n fields: m.fields ?? [],\n }));\n}\n","/**\n * Draft-preview helpers. `next/headers` is imported dynamically so this module is safe\n * to load outside a Next.js request scope (tests, build scripts) without throwing.\n */\n\n/** True when Next.js Draft Mode is enabled for the current request. */\nexport async function isDraftEnabled(): Promise<boolean> {\n const { draftMode } = await import(\"next/headers\");\n const dm = await draftMode();\n return dm.isEnabled;\n}\n\n/**\n * The preview token stored by `createDraftModeRoute`, or null. Pass it to\n * `getEntry(slug, { preview: true, previewToken })` to read drafts.\n */\nexport async function getPreviewToken(): Promise<string | null> {\n const { cookies } = await import(\"next/headers\");\n const jar = await cookies();\n return jar.get(\"bcms-preview-token\")?.value ?? null;\n}\n","/**\n * createDraftModeRoute — an App Router route-handler factory for entering draft\n * preview. Mount it at e.g. `app/api/draft/route.ts`:\n *\n * import { createDraftModeRoute } from \"@bettercms-ai/next\";\n * export const { GET } = createDraftModeRoute({ apiUrl: \"https://api.bettercms.ai\" });\n *\n * Visiting `/api/draft?token=<jwt>&redirect=/path` validates the preview token\n * against the backend (the signature authority), enables Next Draft Mode, stores\n * the token in an httpOnly cookie, and redirects. Reads can then pass the token\n * via `getPreviewToken()` to `getEntry({ preview: true, previewToken })`.\n */\n\nexport const PREVIEW_TOKEN_COOKIE = \"bcms-preview-token\";\n\nexport interface DraftModeRouteConfig {\n /** Backend base, e.g. `https://api.bettercms.ai`. */\n apiUrl: string;\n}\n\n/** Only same-origin relative paths. Blocks \"//evil.com\", \"https://evil.com\", etc. */\nfunction safeRedirectPath(to: string): string {\n return to.startsWith(\"/\") && !to.startsWith(\"//\") ? to : \"/\";\n}\n\n/** Decode a preview-token JWT payload (UNVERIFIED) to read its `entrySlug`. */\nfunction decodeTokenSlug(token: string): string | null {\n try {\n const part = token.split(\".\")[1];\n if (!part) return null;\n const json = JSON.parse(\n Buffer.from(part, \"base64url\").toString(\"utf8\"),\n ) as { entrySlug?: string };\n return json.entrySlug ?? null;\n } catch {\n return null;\n }\n}\n\nexport function createDraftModeRoute(config: DraftModeRouteConfig) {\n const apiUrl = config.apiUrl.replace(/\\/+$/, \"\");\n\n async function GET(request: Request): Promise<Response> {\n const url = new URL(request.url);\n const token = url.searchParams.get(\"token\");\n const redirectTo = url.searchParams.get(\"redirect\") ?? \"/\";\n\n if (!token) return new Response(\"Missing token\", { status: 400 });\n const slug = decodeTokenSlug(token);\n if (!slug) return new Response(\"Malformed token\", { status: 400 });\n\n const res = await fetch(\n `${apiUrl}/api/v1/preview/${encodeURIComponent(slug)}?token=${encodeURIComponent(token)}`,\n );\n if (!res.ok) return new Response(\"Invalid or expired token\", { status: 401 });\n\n const { draftMode, cookies } = await import(\"next/headers\");\n (await draftMode()).enable();\n (await cookies()).set(PREVIEW_TOKEN_COOKIE, token, {\n httpOnly: true,\n sameSite: \"lax\",\n secure: url.protocol === \"https:\",\n path: \"/\",\n maxAge: 60 * 60 * 24,\n });\n\n return Response.redirect(new URL(safeRedirectPath(redirectTo), url.origin), 302);\n }\n\n return { GET };\n}\n\n/** Exit draft mode and clear the preview-token cookie. */\nexport function createDisableDraftRoute() {\n async function GET(request: Request): Promise<Response> {\n const url = new URL(request.url);\n const redirectTo = url.searchParams.get(\"redirect\") ?? \"/\";\n const { draftMode, cookies } = await import(\"next/headers\");\n (await draftMode()).disable();\n (await cookies()).delete(PREVIEW_TOKEN_COOKIE);\n return Response.redirect(new URL(safeRedirectPath(redirectTo), url.origin), 302);\n }\n return { GET };\n}\n","/**\n * Build-time reader for the forms the deploy Action injects into the content\n * snapshot (schema \"bcms-content/v1\": `{ ...collections, forms, turnstileSiteKey }`).\n *\n * This is intentionally separate from `readSnapshot()` — that reads the\n * `@bettercms-ai/next` \"bettercms-snapshot\" format (`{ version, models }`), which is a\n * different file shape. Connected-repo sites on <name>.bettercms.site get the Action\n * snapshot, so forms live here. Server/build-time only (uses node:fs).\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport type { DeliveryForm } from \"@bettercms-ai/sdk\";\n\nexport interface SnapshotForms {\n forms: DeliveryForm[];\n /** Public Turnstile site key for any form with `turnstileEnabled`, or null. */\n turnstileSiteKey: string | null;\n}\n\n/**\n * Read forms from the Action's `bcms-content.json`. Missing file or no forms →\n * an empty set (a content-only build never throws). `path` resolves against cwd.\n */\nexport function readForms(path = \"bcms-content.json\"): SnapshotForms {\n let raw: string;\n try {\n raw = readFileSync(resolve(process.cwd(), path), \"utf8\");\n } catch {\n return { forms: [], turnstileSiteKey: null };\n }\n const snap = JSON.parse(raw) as { forms?: DeliveryForm[]; turnstileSiteKey?: string | null };\n return { forms: snap.forms ?? [], turnstileSiteKey: snap.turnstileSiteKey ?? null };\n}\n\n/** Find a single form by `id` or `name` from the Action snapshot. */\nexport function getForm(idOrName: string, path?: string): DeliveryForm | undefined {\n return readForms(path).forms.find((f) => f.id === idOrName || f.name === idOrName);\n}\n","/**\n * Build-time reader for the pages the deploy Action injects into the content\n * snapshot (schema \"bcms-content/v1\": `{ ...collections, forms, pages, turnstileSiteKey }`).\n *\n * Sibling to `forms-read.ts`. Connected-repo sites on <name>.bettercms.site get the\n * Action snapshot, so published pages (with their block_json) live here. Render a page\n * with `<BcmsBlocks blocks={page.blocks} forms={readForms().forms} />`. The same data\n * is also available at runtime via `createBetterCMS(...).getPage(slug)`.\n * Server/build-time only (uses node:fs).\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport type { ContentBlock, PageMetaJson } from \"@bettercms-ai/types\";\n\nexport interface SnapshotPage {\n slug: string;\n title: string;\n metaTitle: string | null;\n metaDescription: string | null;\n /** Rich SEO (OG / Twitter / canonical / JSON-LD) — FLO-302. Feed to `buildMetadata`. */\n metaJson: PageMetaJson | null;\n /** The page's block_json — pass to `<BcmsBlocks blocks={...} />`. */\n blocks: ContentBlock[];\n updatedAt: string | null;\n publishedAt: string | null;\n}\n\n/**\n * Read published pages from the Action's `bcms-content.json`. Missing file or no\n * pages → an empty list (a pages-less build never throws). `path` resolves against cwd.\n */\nexport function readPages(path = \"bcms-content.json\"): SnapshotPage[] {\n let raw: string;\n try {\n raw = readFileSync(resolve(process.cwd(), path), \"utf8\");\n } catch {\n return [];\n }\n const snap = JSON.parse(raw) as { pages?: SnapshotPage[] };\n return snap.pages ?? [];\n}\n\n/** Find a single published page by `slug` from the Action snapshot. */\nexport function getPage(slug: string, path?: string): SnapshotPage | undefined {\n return readPages(path).find((p) => p.slug === slug);\n}\n","/**\n * Build-time reader for the reusable components the deploy Action injects into the\n * content snapshot (`bcms-content.json` → `components`). Mirrors `forms-read.ts`.\n *\n * Pass the result to `<BcmsBlocks components={readComponents()} … />` so `component`\n * blocks resolve their definitions. Server/build-time only (uses node:fs).\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport type { DeliveryComponent } from \"@bettercms-ai/types\";\n\n/**\n * Read components from the Action's `bcms-content.json`. Missing file or no\n * components → an empty array (a content-only build never throws).\n */\nexport function readComponents(path = \"bcms-content.json\"): DeliveryComponent[] {\n let raw: string;\n try {\n raw = readFileSync(resolve(process.cwd(), path), \"utf8\");\n } catch {\n return [];\n }\n const snap = JSON.parse(raw) as { components?: DeliveryComponent[] };\n return snap.components ?? [];\n}\n\n/** Find a single component by `id`, `slug`, or `name` from the Action snapshot. */\nexport function getComponent(idOrSlug: string, path?: string): DeliveryComponent | undefined {\n return readComponents(path).find(\n (c) => c.id === idOrSlug || c.slug === idOrSlug || c.name === idOrSlug,\n );\n}\n","/**\n * <BcmsField> — tag an entry field's rendered value with its identity so the\n * BetterCMS visual editor can bind click-to-edit deterministically (mirrors\n * Sanity's `data-sanity` / BaseHub's `data-basehub`). Entry-backed templates\n * hand-render field values (`f.title`, `f.body.html`), so the field `path` is\n * declared here by the author — the delivery payload carries no source-map.\n *\n * Server-safe (no hooks): renders in a Next Server Component. By default it wraps\n * children in a `display:contents` span, invisible to layout/GSAP; pass `as` to\n * carry the attributes on a real element instead.\n *\n * `path` follows the editor grammar: `title` · `hero.title` · `facts[2].label`\n * (dotted keys, at most one array index). For images, put the attributes on the\n * `<img>` itself (`kind=\"image\"` reflection patches `.src`), not on a wrapper:\n * <img src={f.cover.url} data-bcms-field=\"cover\" data-bcms-kind=\"image\" />\n */\nimport type { ElementType, ReactNode } from \"react\";\n\nexport interface BcmsFieldProps {\n /** Field path in the editor grammar, e.g. `title` or `facts[2].label`. */\n path: string;\n /** Kind hint the editor's reflection branches on. Omit for plain text/number. */\n kind?: \"richtext\" | \"image\";\n /** Wrapper element. Defaults to a `display:contents` span. */\n as?: ElementType;\n children: ReactNode;\n}\n\nexport function BcmsField({ path, kind, as, children }: BcmsFieldProps) {\n const Tag = as ?? \"span\";\n return (\n <Tag\n data-bcms-field={path}\n data-bcms-kind={kind}\n style={as ? undefined : { display: \"contents\" }}\n >\n {children}\n </Tag>\n );\n}\n","/**\n * defineBetterCMSLive — build the live SSE URL for `<BcmsLive>` from your config.\n * Server-safe (no React); pass the returned `liveSrc` to the client component.\n */\nexport interface BetterCMSLiveConfig {\n /** Backend base, e.g. `https://api.bettercms.ai`. */\n apiUrl: string;\n workspace: string;\n /** A content:read key — exposed to the browser via the SSE URL query. */\n apiKey: string;\n}\n\nexport function defineBetterCMSLive(config: BetterCMSLiveConfig): { liveSrc: string } {\n const base = config.apiUrl.replace(/\\/+$/, \"\");\n const liveSrc = `${base}/api/v1/delivery/${config.workspace}/live?key=${encodeURIComponent(config.apiKey)}`;\n return { liveSrc };\n}\n","/**\n * createRevalidateRoute — App Router POST handler that receives the backend's\n * revalidation webhook and calls `revalidateTag()` for each affected tag. This is\n * the production live path (publish → backend webhook → revalidate ISR cache).\n *\n * import { createRevalidateRoute } from \"@bettercms-ai/next\";\n * export const { POST } = createRevalidateRoute({ secret: process.env.BCMS_REVALIDATE_SECRET });\n *\n * When `secret` is set, the `X-BetterCMS-Signature` HMAC-SHA256 header is verified\n * against the raw body (the same secret configured on the project in the dashboard).\n */\nimport { createHmac, timingSafeEqual } from \"node:crypto\";\n\nexport interface RevalidateRouteConfig {\n /** Shared secret matching the project's revalidation webhook secret. */\n secret?: string;\n}\n\nfunction safeEqual(a: string, b: string): boolean {\n const ab = Buffer.from(a);\n const bb = Buffer.from(b);\n return ab.length === bb.length && timingSafeEqual(ab, bb);\n}\n\nexport function createRevalidateRoute(config: RevalidateRouteConfig = {}) {\n async function POST(request: Request): Promise<Response> {\n const body = await request.text();\n\n if (config.secret) {\n const provided = request.headers.get(\"x-bettercms-signature\") ?? \"\";\n const expected = `sha256=${createHmac(\"sha256\", config.secret).update(body).digest(\"hex\")}`;\n if (!safeEqual(provided, expected)) {\n return new Response(\"Invalid signature\", { status: 401 });\n }\n }\n\n let payload: { data?: { tags?: string[] } };\n try {\n payload = JSON.parse(body);\n } catch {\n return new Response(\"Invalid JSON\", { status: 400 });\n }\n\n const tags = payload.data?.tags ?? [];\n const { revalidateTag } = await import(\"next/cache\");\n for (const tag of tags) revalidateTag(tag);\n\n return Response.json({ revalidated: true, tags });\n }\n\n return { POST };\n}\n"],"mappings":"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","names":["url","resolveSeo","default","readFileSync","resolve","readFileSync","resolve","readFileSync","resolve"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bettercms-ai/next",
3
- "version": "0.5.2",
3
+ "version": "0.7.0",
4
4
  "type": "module",
5
5
  "description": "The BetterCMS adapter for Next.js — typed, cache-aware content reads (getEntry/listEntries), draft preview, build-time content snapshot, and llms.txt generation for the App Router.",
6
6
  "bin": {