@decocms/start 0.19.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.
Files changed (185) hide show
  1. package/.cursor/skills/deco-api-call-dedup/SKILL.md +443 -0
  2. package/.cursor/skills/deco-apps-architecture/SKILL.md +255 -0
  3. package/.cursor/skills/deco-apps-architecture/app-pattern.md +288 -0
  4. package/.cursor/skills/deco-apps-architecture/commerce-types.md +239 -0
  5. package/.cursor/skills/deco-apps-architecture/new-app-guide.md +268 -0
  6. package/.cursor/skills/deco-apps-architecture/scripts-codegen.md +148 -0
  7. package/.cursor/skills/deco-apps-architecture/shared-utils.md +181 -0
  8. package/.cursor/skills/deco-apps-architecture/vtex-deep-structure.md +253 -0
  9. package/.cursor/skills/deco-apps-architecture/website-app.md +169 -0
  10. package/.cursor/skills/deco-apps-vtex-porting/SKILL.md +189 -0
  11. package/.cursor/skills/deco-apps-vtex-porting/adaptation-patterns.md +335 -0
  12. package/.cursor/skills/deco-apps-vtex-porting/commerce-porting.md +155 -0
  13. package/.cursor/skills/deco-apps-vtex-porting/cookie-auth-patterns.md +148 -0
  14. package/.cursor/skills/deco-apps-vtex-porting/structure-map.md +234 -0
  15. package/.cursor/skills/deco-apps-vtex-porting/transform-mapping.md +99 -0
  16. package/.cursor/skills/deco-apps-vtex-porting/website-porting.md +194 -0
  17. package/.cursor/skills/deco-apps-vtex-review/SKILL.md +234 -0
  18. package/.cursor/skills/deco-async-rendering-architecture/SKILL.md +270 -0
  19. package/.cursor/skills/deco-async-rendering-site-guide/SKILL.md +417 -0
  20. package/.cursor/skills/deco-cms-layout-caching/SKILL.md +293 -0
  21. package/.cursor/skills/deco-cms-route-config/SKILL.md +388 -0
  22. package/.cursor/skills/deco-core-architecture/SKILL.md +185 -0
  23. package/.cursor/skills/deco-core-architecture/blocks.md +196 -0
  24. package/.cursor/skills/deco-core-architecture/deco-vs-deco-start.md +191 -0
  25. package/.cursor/skills/deco-core-architecture/engine.md +220 -0
  26. package/.cursor/skills/deco-core-architecture/hooks-components.md +157 -0
  27. package/.cursor/skills/deco-core-architecture/plugins-clients.md +136 -0
  28. package/.cursor/skills/deco-core-architecture/runtime.md +116 -0
  29. package/.cursor/skills/deco-core-architecture/site-usage.md +165 -0
  30. package/.cursor/skills/deco-e2e-testing/SKILL.md +372 -0
  31. package/.cursor/skills/deco-e2e-testing/discovery.md +337 -0
  32. package/.cursor/skills/deco-e2e-testing/scripts/scaffold.sh +81 -0
  33. package/.cursor/skills/deco-e2e-testing/selectors.md +175 -0
  34. package/.cursor/skills/deco-e2e-testing/templates/package.json +18 -0
  35. package/.cursor/skills/deco-e2e-testing/templates/playwright.config.ts +65 -0
  36. package/.cursor/skills/deco-e2e-testing/templates/scripts/baseline.ts +279 -0
  37. package/.cursor/skills/deco-e2e-testing/templates/scripts/run-e2e.ts +194 -0
  38. package/.cursor/skills/deco-e2e-testing/templates/specs/ecommerce-flow.spec.ts +612 -0
  39. package/.cursor/skills/deco-e2e-testing/templates/tsconfig.json +12 -0
  40. package/.cursor/skills/deco-e2e-testing/templates/utils/metrics-collector.ts +918 -0
  41. package/.cursor/skills/deco-e2e-testing/troubleshooting.md +602 -0
  42. package/.cursor/skills/deco-edge-caching/SKILL.md +316 -0
  43. package/.cursor/skills/deco-full-analysis/SKILL.md +898 -0
  44. package/.cursor/skills/deco-full-analysis/checklists/asset-optimization.md +251 -0
  45. package/.cursor/skills/deco-full-analysis/checklists/bug-fix.md +189 -0
  46. package/.cursor/skills/deco-full-analysis/checklists/cache-strategy.md +144 -0
  47. package/.cursor/skills/deco-full-analysis/checklists/dependency-update.md +150 -0
  48. package/.cursor/skills/deco-full-analysis/checklists/hydration-fix.md +191 -0
  49. package/.cursor/skills/deco-full-analysis/checklists/image-optimization.md +180 -0
  50. package/.cursor/skills/deco-full-analysis/checklists/loader-optimization.md +165 -0
  51. package/.cursor/skills/deco-full-analysis/checklists/seo-fix.md +183 -0
  52. package/.cursor/skills/deco-full-analysis/checklists/site-cleanup.md +281 -0
  53. package/.cursor/skills/deco-full-analysis/discovery.md +548 -0
  54. package/.cursor/skills/deco-incident-debugging/SKILL.md +378 -0
  55. package/.cursor/skills/deco-incident-debugging/headless-mode.md +510 -0
  56. package/.cursor/skills/deco-incident-debugging/learnings-index.md +227 -0
  57. package/.cursor/skills/deco-incident-debugging/triage-workflow.md +312 -0
  58. package/.cursor/skills/deco-islands-migration/SKILL.md +251 -0
  59. package/.cursor/skills/deco-loader-n-plus-1-detector/SKILL.md +275 -0
  60. package/.cursor/skills/deco-performance-audit/SKILL.md +530 -0
  61. package/.cursor/skills/deco-performance-audit/tools-reference.md +428 -0
  62. package/.cursor/skills/deco-performance-audit/workflow.md +457 -0
  63. package/.cursor/skills/deco-server-functions-invoke/SKILL.md +92 -0
  64. package/.cursor/skills/deco-server-functions-invoke/architecture.md +166 -0
  65. package/.cursor/skills/deco-server-functions-invoke/generator.md +122 -0
  66. package/.cursor/skills/deco-server-functions-invoke/problem.md +98 -0
  67. package/.cursor/skills/deco-server-functions-invoke/troubleshooting.md +110 -0
  68. package/.cursor/skills/deco-site-deployment/SKILL.md +396 -0
  69. package/.cursor/skills/deco-site-memory-debugging/SKILL.md +121 -0
  70. package/.cursor/skills/deco-site-memory-debugging/cdp-connection.md +222 -0
  71. package/.cursor/skills/deco-site-memory-debugging/memory-analysis.md +362 -0
  72. package/.cursor/skills/deco-site-patterns/SKILL.md +124 -0
  73. package/.cursor/skills/deco-site-patterns/app-composition.md +337 -0
  74. package/.cursor/skills/deco-site-patterns/client-patterns.md +341 -0
  75. package/.cursor/skills/deco-site-patterns/cms-wiring.md +230 -0
  76. package/.cursor/skills/deco-site-patterns/section-patterns.md +340 -0
  77. package/.cursor/skills/deco-site-scaling-tuning/SKILL.md +240 -0
  78. package/.cursor/skills/deco-site-scaling-tuning/analysis-scripts.md +267 -0
  79. package/.cursor/skills/deco-start-architecture/SKILL.md +218 -0
  80. package/.cursor/skills/deco-start-architecture/admin-protocol.md +156 -0
  81. package/.cursor/skills/deco-start-architecture/cms-resolution.md +201 -0
  82. package/.cursor/skills/deco-start-architecture/code-quality.md +158 -0
  83. package/.cursor/skills/deco-start-architecture/gap-analysis.md +129 -0
  84. package/.cursor/skills/deco-start-architecture/sdk-utilities.md +197 -0
  85. package/.cursor/skills/deco-start-architecture/worker-entry-caching.md +154 -0
  86. package/.cursor/skills/deco-startup-analysis/SKILL.md +248 -0
  87. package/.cursor/skills/deco-storefront-test-checklist/SKILL.md +369 -0
  88. package/.cursor/skills/deco-tanstack-hydration-fixes/SKILL.md +468 -0
  89. package/.cursor/skills/deco-tanstack-navigation/SKILL.md +681 -0
  90. package/.cursor/skills/deco-tanstack-search/SKILL.md +411 -0
  91. package/.cursor/skills/deco-tanstack-storefront-patterns/SKILL.md +1013 -0
  92. package/.cursor/skills/deco-to-tanstack-migration/SKILL.md +518 -0
  93. package/.cursor/skills/deco-to-tanstack-migration/references/codemod-commands.md +174 -0
  94. package/.cursor/skills/deco-to-tanstack-migration/references/commerce/README.md +78 -0
  95. package/.cursor/skills/deco-to-tanstack-migration/references/deco-framework/README.md +128 -0
  96. package/.cursor/skills/deco-to-tanstack-migration/references/gotchas.md +719 -0
  97. package/.cursor/skills/deco-to-tanstack-migration/references/imports/README.md +70 -0
  98. package/.cursor/skills/deco-to-tanstack-migration/references/platform-hooks/README.md +154 -0
  99. package/.cursor/skills/deco-to-tanstack-migration/references/signals/README.md +220 -0
  100. package/.cursor/skills/deco-to-tanstack-migration/references/vite-config/README.md +78 -0
  101. package/.cursor/skills/deco-to-tanstack-migration/templates/package-json.md +55 -0
  102. package/.cursor/skills/deco-to-tanstack-migration/templates/root-route.md +110 -0
  103. package/.cursor/skills/deco-to-tanstack-migration/templates/router.md +96 -0
  104. package/.cursor/skills/deco-to-tanstack-migration/templates/setup-ts.md +167 -0
  105. package/.cursor/skills/deco-to-tanstack-migration/templates/vite-config.md +122 -0
  106. package/.cursor/skills/deco-to-tanstack-migration/templates/worker-entry.md +67 -0
  107. package/.cursor/skills/deco-typescript-fixes/SKILL.md +178 -0
  108. package/.cursor/skills/deco-typescript-fixes/common-fixes.md +330 -0
  109. package/.cursor/skills/deco-typescript-fixes/strategy.md +148 -0
  110. package/.cursor/skills/deco-variant-selection-perf/SKILL.md +272 -0
  111. package/.cursor/skills/deco-vtex-fetch-cache/SKILL.md +225 -0
  112. package/.cursor/skills/find-skills/SKILL.md +133 -0
  113. package/.cursor/skills/incident-report/SKILL.md +179 -0
  114. package/.cursor/skills/incident-report/references/5-whys.md +75 -0
  115. package/.cursor/skills/incident-report/templates/client-report.md +187 -0
  116. package/.cursor/skills/incident-report/templates/internal-report.md +206 -0
  117. package/.cursor/skills/template-skill/SKILL.md +38 -0
  118. package/.github/workflows/release.yml +32 -0
  119. package/.releaserc.json +25 -0
  120. package/CLAUDE.md +135 -0
  121. package/GAP_ANALYSIS.md +224 -0
  122. package/GAP_ANALYSIS_V2.md +1013 -0
  123. package/biome.json +39 -0
  124. package/knip.json +5 -0
  125. package/package.json +87 -0
  126. package/scripts/generate-blocks.ts +69 -0
  127. package/scripts/generate-invoke.ts +378 -0
  128. package/scripts/generate-schema.ts +657 -0
  129. package/src/admin/cors.ts +29 -0
  130. package/src/admin/decofile.ts +72 -0
  131. package/src/admin/index.ts +24 -0
  132. package/src/admin/invoke.ts +163 -0
  133. package/src/admin/liveControls.ts +29 -0
  134. package/src/admin/meta.ts +70 -0
  135. package/src/admin/render.ts +205 -0
  136. package/src/admin/schema.ts +686 -0
  137. package/src/admin/setup.ts +44 -0
  138. package/src/cms/index.ts +59 -0
  139. package/src/cms/loader.ts +180 -0
  140. package/src/cms/registry.ts +162 -0
  141. package/src/cms/resolve.ts +1005 -0
  142. package/src/cms/sectionLoaders.ts +294 -0
  143. package/src/hooks/DecoPageRenderer.tsx +444 -0
  144. package/src/hooks/LazySection.tsx +109 -0
  145. package/src/hooks/LiveControls.tsx +108 -0
  146. package/src/hooks/SectionErrorFallback.tsx +85 -0
  147. package/src/hooks/index.ts +8 -0
  148. package/src/index.ts +5 -0
  149. package/src/matchers/builtins.ts +184 -0
  150. package/src/matchers/posthog.ts +154 -0
  151. package/src/middleware/decoState.ts +55 -0
  152. package/src/middleware/healthMetrics.ts +131 -0
  153. package/src/middleware/index.ts +80 -0
  154. package/src/middleware/liveness.ts +21 -0
  155. package/src/middleware/observability.ts +205 -0
  156. package/src/routes/adminRoutes.ts +83 -0
  157. package/src/routes/cmsRoute.ts +302 -0
  158. package/src/routes/components.tsx +34 -0
  159. package/src/routes/index.ts +15 -0
  160. package/src/sdk/analytics.ts +72 -0
  161. package/src/sdk/cacheHeaders.ts +268 -0
  162. package/src/sdk/cachedLoader.ts +206 -0
  163. package/src/sdk/clx.ts +3 -0
  164. package/src/sdk/cookie.ts +39 -0
  165. package/src/sdk/createInvoke.ts +57 -0
  166. package/src/sdk/csp.ts +59 -0
  167. package/src/sdk/env.ts +27 -0
  168. package/src/sdk/index.ts +63 -0
  169. package/src/sdk/instrumentedFetch.ts +137 -0
  170. package/src/sdk/invoke.ts +133 -0
  171. package/src/sdk/mergeCacheControl.ts +150 -0
  172. package/src/sdk/redirects.ts +217 -0
  173. package/src/sdk/requestContext.ts +184 -0
  174. package/src/sdk/serverTimings.ts +68 -0
  175. package/src/sdk/signal.ts +41 -0
  176. package/src/sdk/sitemap.ts +143 -0
  177. package/src/sdk/urlUtils.ts +117 -0
  178. package/src/sdk/useDevice.ts +82 -0
  179. package/src/sdk/useId.ts +7 -0
  180. package/src/sdk/useScript.ts +101 -0
  181. package/src/sdk/workerEntry.ts +703 -0
  182. package/src/sdk/wrapCaughtErrors.ts +107 -0
  183. package/src/types/index.ts +39 -0
  184. package/src/types/widgets.ts +13 -0
  185. package/tsconfig.json +13 -0
@@ -0,0 +1,686 @@
1
+ /**
2
+ * Framework-level schema definitions and runtime meta composition.
3
+ *
4
+ * The schema generator (scripts/generate-schema.ts) only produces section
5
+ * schemas from site TypeScript files. Framework-managed block types -- pages,
6
+ * loaders, matchers, flags -- have their schemas defined here and injected
7
+ * at runtime via composeMeta().
8
+ *
9
+ * This keeps the generator focused on site-specific concerns while the
10
+ * framework owns the schemas for its own block types.
11
+ */
12
+
13
+ export interface MetaResponse {
14
+ major: number;
15
+ version: string;
16
+ namespace: string;
17
+ site: string;
18
+ manifest: {
19
+ blocks: Record<string, Record<string, { $ref: string; namespace?: string }>>;
20
+ };
21
+ schema: {
22
+ definitions: Record<string, any>;
23
+ root: Record<string, any>;
24
+ };
25
+ platform?: string;
26
+ cloudProvider?: string;
27
+ etag?: string;
28
+ }
29
+
30
+ /**
31
+ * Standard base64 encoding that matches the browser's btoa().
32
+ * The admin uses btoa(resolveType) in some code paths to construct
33
+ * definition refs, so our keys MUST include the = padding.
34
+ */
35
+ function toBase64(str: string): string {
36
+ if (typeof btoa === "function") return btoa(str);
37
+ return Buffer.from(str).toString("base64");
38
+ }
39
+
40
+ // The admin's deRefUntil and ArrayFieldTemplate look for the LITERAL
41
+ // string "Resolvable" (not base64-encoded). Both keys are needed:
42
+ // - literal for admin detection
43
+ // - base64 for backward compat with any code that does btoa("Resolvable")
44
+ const RESOLVABLE_LITERAL_KEY = "Resolvable";
45
+ const RESOLVABLE_B64_KEY = toBase64("Resolvable");
46
+
47
+ function buildResolvableDefinition() {
48
+ return {
49
+ title: "Select from saved",
50
+ type: "object",
51
+ required: ["__resolveType"],
52
+ additionalProperties: true,
53
+ properties: { __resolveType: { type: "string" } },
54
+ };
55
+ }
56
+
57
+ // ---------------------------------------------------------------------------
58
+ // Loader definitions — dynamic registry
59
+ // ---------------------------------------------------------------------------
60
+
61
+ export interface LoaderConfig {
62
+ key: string;
63
+ title: string;
64
+ namespace: string;
65
+ propsSchema: Record<string, any>;
66
+ /** Tags for property matching (e.g., "product-list" enables injection into Product[] props). */
67
+ tags?: string[];
68
+ }
69
+
70
+ const loaderRegistry: LoaderConfig[] = [];
71
+
72
+ /** Register a single loader schema for the admin. */
73
+ export function registerLoaderSchema(config: LoaderConfig) {
74
+ const idx = loaderRegistry.findIndex((l) => l.key === config.key);
75
+ if (idx >= 0) {
76
+ loaderRegistry[idx] = config;
77
+ } else {
78
+ loaderRegistry.push(config);
79
+ }
80
+ }
81
+
82
+ /** Register multiple loader schemas at once. */
83
+ export function registerLoaderSchemas(configs: LoaderConfig[]) {
84
+ for (const config of configs) registerLoaderSchema(config);
85
+ }
86
+
87
+ /** Get all registered loader schemas. */
88
+ export function getRegisteredLoaders(): LoaderConfig[] {
89
+ return [...loaderRegistry];
90
+ }
91
+
92
+ function getProductListLoaderKeys(): string[] {
93
+ return loaderRegistry.filter((l) => l.tags?.includes("product-list")).map((l) => l.key);
94
+ }
95
+
96
+ // ---------------------------------------------------------------------------
97
+ // Matcher definitions — dynamic registry
98
+ // ---------------------------------------------------------------------------
99
+
100
+ export interface MatcherConfig {
101
+ key: string;
102
+ title: string;
103
+ namespace: string;
104
+ propsSchema?: Record<string, any>;
105
+ }
106
+
107
+ const matcherRegistry: MatcherConfig[] = [];
108
+
109
+ /** Register a single matcher schema for the admin. */
110
+ export function registerMatcherSchema(config: MatcherConfig) {
111
+ const idx = matcherRegistry.findIndex((m) => m.key === config.key);
112
+ if (idx >= 0) {
113
+ matcherRegistry[idx] = config;
114
+ } else {
115
+ matcherRegistry.push(config);
116
+ }
117
+ }
118
+
119
+ /** Register multiple matcher schemas at once. */
120
+ export function registerMatcherSchemas(configs: MatcherConfig[]) {
121
+ for (const config of configs) registerMatcherSchema(config);
122
+ }
123
+
124
+ /** Get all registered matcher schemas. */
125
+ export function getRegisteredMatchers(): MatcherConfig[] {
126
+ return matcherRegistry;
127
+ }
128
+
129
+ // Register built-in matchers that are always available
130
+ registerMatcherSchemas([
131
+ { key: "website/matchers/always.ts", title: "Always", namespace: "website" },
132
+ { key: "website/matchers/never.ts", title: "Never", namespace: "website" },
133
+ {
134
+ key: "website/matchers/device.ts",
135
+ title: "Device",
136
+ namespace: "website",
137
+ propsSchema: {
138
+ type: "object",
139
+ properties: {
140
+ mobile: { type: "boolean", title: "Mobile" },
141
+ desktop: { type: "boolean", title: "Desktop" },
142
+ },
143
+ },
144
+ },
145
+ {
146
+ key: "website/matchers/date.ts",
147
+ title: "Date Range",
148
+ namespace: "website",
149
+ propsSchema: {
150
+ type: "object",
151
+ properties: {
152
+ start: { type: "string", title: "Start Date", format: "date-time" },
153
+ end: { type: "string", title: "End Date", format: "date-time" },
154
+ },
155
+ },
156
+ },
157
+ {
158
+ key: "website/matchers/cron.ts",
159
+ title: "Time Window (Cron)",
160
+ namespace: "website",
161
+ propsSchema: {
162
+ type: "object",
163
+ properties: {
164
+ start: { type: "string", title: "Start", format: "date-time" },
165
+ end: { type: "string", title: "End", format: "date-time" },
166
+ },
167
+ },
168
+ },
169
+ {
170
+ key: "website/matchers/cookie.ts",
171
+ title: "Cookie",
172
+ namespace: "website",
173
+ propsSchema: {
174
+ type: "object",
175
+ properties: {
176
+ name: { type: "string", title: "Cookie Name" },
177
+ value: { type: "string", title: "Cookie Value" },
178
+ },
179
+ },
180
+ },
181
+ {
182
+ key: "website/matchers/host.ts",
183
+ title: "Hostname",
184
+ namespace: "website",
185
+ propsSchema: {
186
+ type: "object",
187
+ properties: {
188
+ host: { type: "string", title: "Hostname" },
189
+ },
190
+ },
191
+ },
192
+ {
193
+ key: "website/matchers/pathname.ts",
194
+ title: "Pathname",
195
+ namespace: "website",
196
+ propsSchema: {
197
+ type: "object",
198
+ properties: {
199
+ pattern: { type: "string", title: "Regex Pattern" },
200
+ includes: {
201
+ type: "array",
202
+ title: "Includes",
203
+ items: { type: "string" },
204
+ },
205
+ excludes: {
206
+ type: "array",
207
+ title: "Excludes",
208
+ items: { type: "string" },
209
+ },
210
+ },
211
+ },
212
+ },
213
+ {
214
+ key: "website/matchers/queryString.ts",
215
+ title: "Query String",
216
+ namespace: "website",
217
+ propsSchema: {
218
+ type: "object",
219
+ properties: {
220
+ key: { type: "string", title: "Parameter Name" },
221
+ value: { type: "string", title: "Parameter Value" },
222
+ },
223
+ },
224
+ },
225
+ {
226
+ key: "website/matchers/random.ts",
227
+ title: "Random (A/B Test)",
228
+ namespace: "website",
229
+ propsSchema: {
230
+ type: "object",
231
+ properties: {
232
+ traffic: {
233
+ type: "number",
234
+ title: "Traffic Percentage (0\u20131)",
235
+ minimum: 0,
236
+ maximum: 1,
237
+ },
238
+ },
239
+ },
240
+ },
241
+ ]);
242
+
243
+ function buildLoaderDefinitions() {
244
+ const definitions: Record<string, any> = {};
245
+ const manifestBlocks: Record<string, any> = {};
246
+ const loaderAnyOf: any[] = [{ $ref: `#/definitions/${RESOLVABLE_LITERAL_KEY}` }];
247
+
248
+ for (const loader of loaderRegistry) {
249
+ const defKey = toBase64(loader.key);
250
+
251
+ definitions[defKey] = {
252
+ title: loader.key,
253
+ type: "object",
254
+ required: ["__resolveType"],
255
+ properties: {
256
+ __resolveType: {
257
+ type: "string",
258
+ enum: [loader.key],
259
+ default: loader.key,
260
+ },
261
+ props: loader.propsSchema,
262
+ },
263
+ };
264
+
265
+ manifestBlocks[loader.key] = {
266
+ $ref: `#/definitions/${defKey}`,
267
+ namespace: loader.namespace,
268
+ };
269
+
270
+ loaderAnyOf.push({ $ref: `#/definitions/${defKey}` });
271
+ }
272
+
273
+ return { definitions, manifestBlocks, loaderAnyOf };
274
+ }
275
+
276
+ // ---------------------------------------------------------------------------
277
+ // Matcher definitions
278
+ // ---------------------------------------------------------------------------
279
+
280
+ function buildMatcherDefinitions() {
281
+ const definitions: Record<string, any> = {};
282
+ const manifestBlocks: Record<string, any> = {};
283
+ const matcherAnyOf: any[] = [{ $ref: `#/definitions/${RESOLVABLE_LITERAL_KEY}` }];
284
+
285
+ for (const matcher of matcherRegistry) {
286
+ const defKey = toBase64(matcher.key);
287
+ definitions[defKey] = {
288
+ title: matcher.key,
289
+ type: "object",
290
+ required: ["__resolveType"],
291
+ properties: {
292
+ __resolveType: {
293
+ type: "string",
294
+ enum: [matcher.key],
295
+ default: matcher.key,
296
+ },
297
+ ...(matcher.propsSchema?.properties || {}),
298
+ },
299
+ };
300
+ manifestBlocks[matcher.key] = {
301
+ $ref: `#/definitions/${defKey}`,
302
+ namespace: matcher.namespace,
303
+ };
304
+ matcherAnyOf.push({ $ref: `#/definitions/${defKey}` });
305
+ }
306
+
307
+ return { definitions, manifestBlocks, matcherAnyOf };
308
+ }
309
+
310
+ // ---------------------------------------------------------------------------
311
+ // Multivariate flag schema
312
+ // ---------------------------------------------------------------------------
313
+
314
+ function buildMultivariateFlagSchema(innerSchema: any) {
315
+ return {
316
+ type: "object",
317
+ required: ["__resolveType"],
318
+ properties: {
319
+ __resolveType: {
320
+ type: "string",
321
+ enum: ["website/flags/multivariate.ts", "website/flags/multivariate/section.ts"],
322
+ },
323
+ variants: {
324
+ type: "array",
325
+ title: "Variants",
326
+ items: {
327
+ type: "object",
328
+ properties: {
329
+ rule: {
330
+ title: "Rule",
331
+ type: "object",
332
+ required: ["__resolveType"],
333
+ properties: {
334
+ __resolveType: { type: "string" },
335
+ },
336
+ additionalProperties: true,
337
+ },
338
+ value: innerSchema,
339
+ },
340
+ },
341
+ },
342
+ },
343
+ };
344
+ }
345
+
346
+ // ---------------------------------------------------------------------------
347
+ // Page schema
348
+ // ---------------------------------------------------------------------------
349
+
350
+ function buildPageSchema(sectionAnyOf: any[]) {
351
+ const PAGE_TYPE = "website/pages/Page.tsx";
352
+ const defKey = toBase64(PAGE_TYPE);
353
+
354
+ const sectionsArraySchema = {
355
+ type: "array",
356
+ title: "Sections",
357
+ items: { anyOf: sectionAnyOf },
358
+ };
359
+
360
+ const sectionsMultivariateSchema = buildMultivariateFlagSchema(sectionsArraySchema);
361
+
362
+ const definition = {
363
+ title: PAGE_TYPE,
364
+ type: "object",
365
+ required: ["__resolveType"],
366
+ properties: {
367
+ __resolveType: {
368
+ type: "string",
369
+ enum: [PAGE_TYPE],
370
+ default: PAGE_TYPE,
371
+ },
372
+ name: { type: "string", title: "Name" },
373
+ path: { type: "string", title: "Path" },
374
+ seo: { title: "SEO", anyOf: sectionAnyOf },
375
+ sections: {
376
+ title: "Sections",
377
+ anyOf: [sectionsArraySchema, sectionsMultivariateSchema],
378
+ },
379
+ },
380
+ };
381
+
382
+ return {
383
+ definitions: { [defKey]: definition },
384
+ manifestBlocks: {
385
+ [PAGE_TYPE]: {
386
+ $ref: `#/definitions/${defKey}`,
387
+ namespace: "website",
388
+ },
389
+ },
390
+ rootAnyOf: [
391
+ { $ref: `#/definitions/${RESOLVABLE_LITERAL_KEY}` },
392
+ { $ref: `#/definitions/${defKey}` },
393
+ ],
394
+ };
395
+ }
396
+
397
+ // ---------------------------------------------------------------------------
398
+ // Framework sections
399
+ // ---------------------------------------------------------------------------
400
+
401
+ function buildFrameworkSections(sectionAnyOf: any[]) {
402
+ const definitions: Record<string, any> = {};
403
+ const manifestBlocks: Record<string, any> = {};
404
+ const extraAnyOf: any[] = [];
405
+
406
+ // --- website/sections/Rendering/Lazy.tsx ---
407
+ const LAZY_TYPE = "website/sections/Rendering/Lazy.tsx";
408
+ const lazyKey = toBase64(LAZY_TYPE);
409
+ definitions[lazyKey] = {
410
+ title: LAZY_TYPE,
411
+ type: "object",
412
+ required: ["__resolveType"],
413
+ properties: {
414
+ __resolveType: {
415
+ type: "string",
416
+ enum: [LAZY_TYPE],
417
+ default: LAZY_TYPE,
418
+ },
419
+ section: {
420
+ title: "Section",
421
+ anyOf: sectionAnyOf,
422
+ },
423
+ },
424
+ };
425
+ manifestBlocks[LAZY_TYPE] = {
426
+ $ref: `#/definitions/${lazyKey}`,
427
+ namespace: "website",
428
+ };
429
+ extraAnyOf.push({ $ref: `#/definitions/${lazyKey}` });
430
+
431
+ // --- website/sections/Seo/Seo.tsx ---
432
+ const SEO_TYPE = "website/sections/Seo/Seo.tsx";
433
+ const seoKey = toBase64(SEO_TYPE);
434
+ definitions[seoKey] = {
435
+ title: SEO_TYPE,
436
+ type: "object",
437
+ required: ["__resolveType"],
438
+ properties: {
439
+ __resolveType: {
440
+ type: "string",
441
+ enum: [SEO_TYPE],
442
+ default: SEO_TYPE,
443
+ },
444
+ title: { type: "string", title: "Title" },
445
+ description: { type: "string", title: "Description" },
446
+ canonical: { type: "string", title: "Canonical URL" },
447
+ favicon: { type: "string", title: "Favicon", format: "image-uri" },
448
+ noIndexing: { type: "boolean", title: "No Indexing" },
449
+ titleTemplate: { type: "string", title: "Title Template" },
450
+ descriptionTemplate: { type: "string", title: "Description Template" },
451
+ type: { type: "string", title: "Page Type" },
452
+ image: { type: "string", title: "OG Image", format: "image-uri" },
453
+ themeColor: { type: "string", title: "Theme Color", format: "color" },
454
+ },
455
+ };
456
+ manifestBlocks[SEO_TYPE] = {
457
+ $ref: `#/definitions/${seoKey}`,
458
+ namespace: "website",
459
+ };
460
+ extraAnyOf.push({ $ref: `#/definitions/${seoKey}` });
461
+
462
+ // --- website/flags/multivariate/section.ts ---
463
+ const MV_SECTION_TYPE = "website/flags/multivariate/section.ts";
464
+ const mvSectionKey = toBase64(MV_SECTION_TYPE);
465
+ definitions[mvSectionKey] = {
466
+ title: MV_SECTION_TYPE,
467
+ type: "object",
468
+ required: ["__resolveType"],
469
+ properties: {
470
+ __resolveType: {
471
+ type: "string",
472
+ enum: [MV_SECTION_TYPE],
473
+ default: MV_SECTION_TYPE,
474
+ },
475
+ variants: {
476
+ type: "array",
477
+ title: "Variants",
478
+ items: {
479
+ type: "object",
480
+ properties: {
481
+ rule: {
482
+ title: "Rule",
483
+ type: "object",
484
+ required: ["__resolveType"],
485
+ properties: { __resolveType: { type: "string" } },
486
+ additionalProperties: true,
487
+ },
488
+ value: {
489
+ title: "Section",
490
+ anyOf: sectionAnyOf,
491
+ },
492
+ },
493
+ },
494
+ },
495
+ },
496
+ };
497
+ manifestBlocks[MV_SECTION_TYPE] = {
498
+ $ref: `#/definitions/${mvSectionKey}`,
499
+ namespace: "website",
500
+ };
501
+ extraAnyOf.push({ $ref: `#/definitions/${mvSectionKey}` });
502
+
503
+ return { definitions, manifestBlocks, extraAnyOf };
504
+ }
505
+
506
+ // ---------------------------------------------------------------------------
507
+ // Post-processing: wrap complex properties with Resolvable anyOf
508
+ // ---------------------------------------------------------------------------
509
+
510
+ /**
511
+ * Walk all @Props definitions and wrap complex array/object properties
512
+ * with anyOf [Resolvable, original, ...matchingLoaders].
513
+ *
514
+ * In the deco CMS, ANY complex property can be replaced by a loader
515
+ * reference ({ __resolveType: "some/loader.ts", props: {...} }).
516
+ * This function ensures the schema accepts both inline data and
517
+ * loader references for all such properties.
518
+ */
519
+ function wrapResolvableProperties(
520
+ definitions: Record<string, any>,
521
+ _loaderDefinitions: Record<string, any>,
522
+ ) {
523
+ const resolvableRef = { $ref: `#/definitions/${RESOLVABLE_LITERAL_KEY}` };
524
+
525
+ const productLoaderRefs = getProductListLoaderKeys().map((key) => ({
526
+ $ref: `#/definitions/${toBase64(key)}`,
527
+ }));
528
+
529
+ for (const [defKey, def] of Object.entries(definitions)) {
530
+ if (!defKey.endsWith("@Props")) continue;
531
+ if (!def || !def.properties) continue;
532
+
533
+ for (const [propName, propSchema] of Object.entries(def.properties as Record<string, any>)) {
534
+ if (!propSchema || typeof propSchema !== "object") continue;
535
+ if (propSchema.anyOf || propSchema.$ref) continue;
536
+
537
+ const shouldWrap = isLoaderCompatibleProperty(propSchema);
538
+ if (!shouldWrap) continue;
539
+
540
+ const { nullable, title, hide, ...rest } = propSchema;
541
+
542
+ // Determine which loader refs to include based on property type
543
+ const loaderRefs = isProductArrayProperty(propSchema) ? productLoaderRefs : [];
544
+
545
+ const wrapped: any = {
546
+ anyOf: [resolvableRef, { ...rest, title: title || "Inline data" }, ...loaderRefs],
547
+ };
548
+ if (nullable) wrapped.nullable = true;
549
+ if (title) wrapped.title = title;
550
+ if (hide) wrapped.hide = hide;
551
+
552
+ def.properties[propName] = wrapped;
553
+ }
554
+
555
+ // Also walk nested object properties (e.g., Tab.products inside tabs array items)
556
+ wrapNestedProperties(def, resolvableRef, productLoaderRefs);
557
+ }
558
+ }
559
+
560
+ function isLoaderCompatibleProperty(schema: any): boolean {
561
+ if (schema.type === "array" && schema.items?.type === "object") {
562
+ const propCount = Object.keys(schema.items.properties || {}).length;
563
+ return propCount > 3;
564
+ }
565
+ return false;
566
+ }
567
+
568
+ function isProductArrayProperty(schema: any): boolean {
569
+ if (schema.type !== "array" || !schema.items?.properties) return false;
570
+ const itemProps = schema.items.properties;
571
+ return !!(itemProps.productID || itemProps.name || itemProps.offers || itemProps.brand);
572
+ }
573
+
574
+ /**
575
+ * Recursively walk nested object/array schemas to wrap deeply nested
576
+ * loader-compatible properties. Handles cases like Tab.products where
577
+ * the products field is inside an array item's object schema.
578
+ */
579
+ function wrapNestedProperties(
580
+ schema: any,
581
+ resolvableRef: any,
582
+ productLoaderRefs: any[],
583
+ depth = 0,
584
+ ) {
585
+ if (depth > 5 || !schema || typeof schema !== "object") return;
586
+
587
+ if (schema.type === "array" && schema.items?.type === "object" && schema.items.properties) {
588
+ for (const [propName, propSchema] of Object.entries(
589
+ schema.items.properties as Record<string, any>,
590
+ )) {
591
+ if (!propSchema || typeof propSchema !== "object") continue;
592
+ if (propSchema.anyOf || propSchema.$ref) continue;
593
+
594
+ if (isLoaderCompatibleProperty(propSchema)) {
595
+ const { nullable, title, hide, ...rest } = propSchema;
596
+ const loaderRefs = isProductArrayProperty(propSchema) ? productLoaderRefs : [];
597
+ const wrapped: any = {
598
+ anyOf: [resolvableRef, { ...rest, title: title || "Inline data" }, ...loaderRefs],
599
+ };
600
+ if (nullable) wrapped.nullable = true;
601
+ if (title) wrapped.title = title;
602
+ if (hide) wrapped.hide = hide;
603
+ schema.items.properties[propName] = wrapped;
604
+ }
605
+
606
+ wrapNestedProperties(propSchema, resolvableRef, productLoaderRefs, depth + 1);
607
+ }
608
+ }
609
+
610
+ if (schema.properties) {
611
+ for (const propSchema of Object.values(schema.properties)) {
612
+ wrapNestedProperties(propSchema as any, resolvableRef, productLoaderRefs, depth + 1);
613
+ }
614
+ }
615
+ }
616
+
617
+ // ---------------------------------------------------------------------------
618
+ // composeMeta
619
+ // ---------------------------------------------------------------------------
620
+
621
+ const SECTION_REF_DEF_KEY = "__SECTION_REF__";
622
+
623
+ export function composeMeta(siteMeta: MetaResponse): MetaResponse {
624
+ const siteAnyOf = siteMeta.schema?.root?.sections?.anyOf || [];
625
+
626
+ // Build all framework components
627
+ const fwSections = buildFrameworkSections(siteAnyOf);
628
+ const fullSectionAnyOf = [...siteAnyOf, ...fwSections.extraAnyOf];
629
+ const page = buildPageSchema(fullSectionAnyOf);
630
+ const loaders = buildLoaderDefinitions();
631
+ const matchers = buildMatcherDefinitions();
632
+
633
+ const sectionRefDef = { title: "Section", anyOf: fullSectionAnyOf };
634
+
635
+ const resolvableDef = buildResolvableDefinition();
636
+
637
+ // Merge all definitions
638
+ const allDefinitions: Record<string, any> = {
639
+ ...(siteMeta.schema?.definitions || {}),
640
+ ...fwSections.definitions,
641
+ ...page.definitions,
642
+ ...loaders.definitions,
643
+ ...matchers.definitions,
644
+ [SECTION_REF_DEF_KEY]: sectionRefDef,
645
+ [RESOLVABLE_LITERAL_KEY]: resolvableDef,
646
+ [RESOLVABLE_B64_KEY]: resolvableDef,
647
+ };
648
+
649
+ // Post-process: wrap complex section properties with Resolvable anyOf
650
+ wrapResolvableProperties(allDefinitions, loaders.definitions);
651
+
652
+ return {
653
+ ...siteMeta,
654
+ manifest: {
655
+ blocks: {
656
+ ...(siteMeta.manifest?.blocks || {}),
657
+ sections: {
658
+ ...(siteMeta.manifest?.blocks?.sections || {}),
659
+ ...fwSections.manifestBlocks,
660
+ },
661
+ pages: {
662
+ ...(siteMeta.manifest?.blocks?.pages || {}),
663
+ ...page.manifestBlocks,
664
+ },
665
+ loaders: {
666
+ ...(siteMeta.manifest?.blocks?.loaders || {}),
667
+ ...loaders.manifestBlocks,
668
+ },
669
+ matchers: {
670
+ ...(siteMeta.manifest?.blocks?.matchers || {}),
671
+ ...matchers.manifestBlocks,
672
+ },
673
+ },
674
+ },
675
+ schema: {
676
+ definitions: allDefinitions,
677
+ root: {
678
+ ...(siteMeta.schema?.root || {}),
679
+ sections: { anyOf: fullSectionAnyOf },
680
+ pages: { anyOf: page.rootAnyOf },
681
+ loaders: { anyOf: loaders.loaderAnyOf },
682
+ matchers: { anyOf: matchers.matcherAnyOf },
683
+ },
684
+ },
685
+ };
686
+ }