@decocms/start 0.25.4 → 0.26.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decocms/start",
3
- "version": "0.25.4",
3
+ "version": "0.26.0",
4
4
  "type": "module",
5
5
  "description": "Deco framework for TanStack Start - CMS bridge, admin protocol, hooks, schema generation",
6
6
  "main": "./src/index.ts",
@@ -9,7 +9,7 @@ export {
9
9
  } from "./invoke";
10
10
  export { LIVE_CONTROLS_SCRIPT } from "./liveControls";
11
11
  export { handleMeta, setMetaData } from "./meta";
12
- export { handleRender, setRenderShell } from "./render";
12
+ export { handleRender, setPreviewWrapper, setRenderShell } from "./render";
13
13
  export {
14
14
  composeMeta,
15
15
  getRegisteredLoaders,
@@ -4,8 +4,9 @@ import { getSection } from "../cms/registry";
4
4
  import { resolveValue, WELL_KNOWN_TYPES } from "../cms/resolve";
5
5
  import { buildHtmlShell } from "../sdk/htmlShell";
6
6
  import { LIVE_CONTROLS_SCRIPT } from "./liveControls";
7
+ import { getPreviewWrapper } from "./setup";
7
8
 
8
- export { setRenderShell } from "./setup";
9
+ export { setRenderShell, setPreviewWrapper } from "./setup";
9
10
 
10
11
  function wrapInHtmlShell(sectionHtml: string): string {
11
12
  return buildHtmlShell({ body: sectionHtml, script: LIVE_CONTROLS_SCRIPT });
@@ -28,7 +29,10 @@ async function renderOneSection(section: Record<string, unknown>): Promise<strin
28
29
  const { __resolveType: _, ...sectionProps } = section;
29
30
  const { renderToString } = await import("react-dom/server");
30
31
  const mod = await sectionLoader();
31
- return renderToString(createElement(mod.default, sectionProps));
32
+ const element = createElement(mod.default, sectionProps);
33
+ const Wrapper = getPreviewWrapper();
34
+ const wrapped = Wrapper ? createElement(Wrapper, null, element) : element;
35
+ return renderToString(wrapped);
32
36
  } catch (error) {
33
37
  return `<div style="padding:8px;color:red;font-size:12px;">Error rendering ${resolveType}: ${(error as Error).message}</div>`;
34
38
  }
@@ -161,7 +165,10 @@ export async function handleRender(request: Request): Promise<Response> {
161
165
  const { __resolveType: _, ...cleanProps } = resolvedProps;
162
166
  const { renderToString } = await import("react-dom/server");
163
167
  const mod = await sectionLoader();
164
- const sectionHtml = renderToString(createElement(mod.default, cleanProps));
168
+ const element = createElement(mod.default, cleanProps);
169
+ const Wrapper = getPreviewWrapper();
170
+ const wrapped = Wrapper ? createElement(Wrapper, null, element) : element;
171
+ const sectionHtml = renderToString(wrapped);
165
172
  return new Response(wrapInHtmlShell(sectionHtml), {
166
173
  status: 200,
167
174
  headers: { "Content-Type": "text/html; charset=utf-8" },
@@ -555,6 +555,11 @@ function buildFrameworkSections(sectionAnyOf: any[]) {
555
555
  type: { type: "string", title: "Page Type" },
556
556
  image: { type: "string", title: "OG Image", format: "image-uri" },
557
557
  themeColor: { type: "string", title: "Theme Color", format: "color" },
558
+ jsonLDs: {
559
+ type: "array",
560
+ title: "JSON-LD Structured Data",
561
+ items: { type: "object", additionalProperties: true },
562
+ },
558
563
  },
559
564
  };
560
565
  manifestBlocks[SEO_TYPE] = {
@@ -563,6 +568,42 @@ function buildFrameworkSections(sectionAnyOf: any[]) {
563
568
  };
564
569
  extraAnyOf.push({ $ref: `#/definitions/${seoKey}` });
565
570
 
571
+ // --- website/sections/Seo/SeoV2.tsx ---
572
+ const SEOV2_TYPE = "website/sections/Seo/SeoV2.tsx";
573
+ const seoV2Key = toBase64(SEOV2_TYPE);
574
+ definitions[seoV2Key] = {
575
+ title: SEOV2_TYPE,
576
+ type: "object",
577
+ required: ["__resolveType"],
578
+ properties: {
579
+ __resolveType: {
580
+ type: "string",
581
+ enum: [SEOV2_TYPE],
582
+ default: SEOV2_TYPE,
583
+ },
584
+ title: { type: "string", title: "Title" },
585
+ description: { type: "string", title: "Description" },
586
+ canonical: { type: "string", title: "Canonical URL" },
587
+ favicon: { type: "string", title: "Favicon", format: "image-uri" },
588
+ noIndexing: { type: "boolean", title: "No Indexing" },
589
+ titleTemplate: { type: "string", title: "Title Template" },
590
+ descriptionTemplate: { type: "string", title: "Description Template" },
591
+ type: { type: "string", title: "Page Type" },
592
+ image: { type: "string", title: "OG Image", format: "image-uri" },
593
+ themeColor: { type: "string", title: "Theme Color", format: "color" },
594
+ jsonLDs: {
595
+ type: "array",
596
+ title: "JSON-LD Structured Data",
597
+ items: { type: "object", additionalProperties: true },
598
+ },
599
+ },
600
+ };
601
+ manifestBlocks[SEOV2_TYPE] = {
602
+ $ref: `#/definitions/${seoV2Key}`,
603
+ namespace: "website",
604
+ };
605
+ extraAnyOf.push({ $ref: `#/definitions/${seoV2Key}` });
606
+
566
607
  // --- website/flags/multivariate/section.ts ---
567
608
  const MV_SECTION_TYPE = "website/flags/multivariate/section.ts";
568
609
  const mvSectionKey = toBase64(MV_SECTION_TYPE);
@@ -25,6 +25,38 @@ let themeName = "";
25
25
  let bodyClass = "";
26
26
  let htmlLang = "en";
27
27
 
28
+ /**
29
+ * Optional React component that wraps section renders in admin previews.
30
+ * Use this to provide context that sections depend on (Router, QueryClient, etc.)
31
+ * without which renderToString would crash.
32
+ *
33
+ * The wrapper receives `{ children }` and should render them inside the
34
+ * necessary providers.
35
+ */
36
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
37
+ let previewWrapperComponent: any = null;
38
+
39
+ /**
40
+ * Register a wrapper component for admin preview renders.
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * // In site's setup.ts:
45
+ * import { setPreviewWrapper } from "@decocms/start/admin/setup";
46
+ * import { PreviewProviders } from "./components/PreviewProviders";
47
+ * setPreviewWrapper(PreviewProviders);
48
+ * ```
49
+ */
50
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
51
+ export function setPreviewWrapper(wrapper: any) {
52
+ previewWrapperComponent = wrapper;
53
+ }
54
+
55
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
56
+ export function getPreviewWrapper(): any {
57
+ return previewWrapperComponent;
58
+ }
59
+
28
60
  export function setRenderShell(opts: {
29
61
  css?: string;
30
62
  fonts?: string[];