@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.
- package/.cursor/skills/deco-api-call-dedup/SKILL.md +443 -0
- package/.cursor/skills/deco-apps-architecture/SKILL.md +255 -0
- package/.cursor/skills/deco-apps-architecture/app-pattern.md +288 -0
- package/.cursor/skills/deco-apps-architecture/commerce-types.md +239 -0
- package/.cursor/skills/deco-apps-architecture/new-app-guide.md +268 -0
- package/.cursor/skills/deco-apps-architecture/scripts-codegen.md +148 -0
- package/.cursor/skills/deco-apps-architecture/shared-utils.md +181 -0
- package/.cursor/skills/deco-apps-architecture/vtex-deep-structure.md +253 -0
- package/.cursor/skills/deco-apps-architecture/website-app.md +169 -0
- package/.cursor/skills/deco-apps-vtex-porting/SKILL.md +189 -0
- package/.cursor/skills/deco-apps-vtex-porting/adaptation-patterns.md +335 -0
- package/.cursor/skills/deco-apps-vtex-porting/commerce-porting.md +155 -0
- package/.cursor/skills/deco-apps-vtex-porting/cookie-auth-patterns.md +148 -0
- package/.cursor/skills/deco-apps-vtex-porting/structure-map.md +234 -0
- package/.cursor/skills/deco-apps-vtex-porting/transform-mapping.md +99 -0
- package/.cursor/skills/deco-apps-vtex-porting/website-porting.md +194 -0
- package/.cursor/skills/deco-apps-vtex-review/SKILL.md +234 -0
- package/.cursor/skills/deco-async-rendering-architecture/SKILL.md +270 -0
- package/.cursor/skills/deco-async-rendering-site-guide/SKILL.md +417 -0
- package/.cursor/skills/deco-cms-layout-caching/SKILL.md +293 -0
- package/.cursor/skills/deco-cms-route-config/SKILL.md +388 -0
- package/.cursor/skills/deco-core-architecture/SKILL.md +185 -0
- package/.cursor/skills/deco-core-architecture/blocks.md +196 -0
- package/.cursor/skills/deco-core-architecture/deco-vs-deco-start.md +191 -0
- package/.cursor/skills/deco-core-architecture/engine.md +220 -0
- package/.cursor/skills/deco-core-architecture/hooks-components.md +157 -0
- package/.cursor/skills/deco-core-architecture/plugins-clients.md +136 -0
- package/.cursor/skills/deco-core-architecture/runtime.md +116 -0
- package/.cursor/skills/deco-core-architecture/site-usage.md +165 -0
- package/.cursor/skills/deco-e2e-testing/SKILL.md +372 -0
- package/.cursor/skills/deco-e2e-testing/discovery.md +337 -0
- package/.cursor/skills/deco-e2e-testing/scripts/scaffold.sh +81 -0
- package/.cursor/skills/deco-e2e-testing/selectors.md +175 -0
- package/.cursor/skills/deco-e2e-testing/templates/package.json +18 -0
- package/.cursor/skills/deco-e2e-testing/templates/playwright.config.ts +65 -0
- package/.cursor/skills/deco-e2e-testing/templates/scripts/baseline.ts +279 -0
- package/.cursor/skills/deco-e2e-testing/templates/scripts/run-e2e.ts +194 -0
- package/.cursor/skills/deco-e2e-testing/templates/specs/ecommerce-flow.spec.ts +612 -0
- package/.cursor/skills/deco-e2e-testing/templates/tsconfig.json +12 -0
- package/.cursor/skills/deco-e2e-testing/templates/utils/metrics-collector.ts +918 -0
- package/.cursor/skills/deco-e2e-testing/troubleshooting.md +602 -0
- package/.cursor/skills/deco-edge-caching/SKILL.md +316 -0
- package/.cursor/skills/deco-full-analysis/SKILL.md +898 -0
- package/.cursor/skills/deco-full-analysis/checklists/asset-optimization.md +251 -0
- package/.cursor/skills/deco-full-analysis/checklists/bug-fix.md +189 -0
- package/.cursor/skills/deco-full-analysis/checklists/cache-strategy.md +144 -0
- package/.cursor/skills/deco-full-analysis/checklists/dependency-update.md +150 -0
- package/.cursor/skills/deco-full-analysis/checklists/hydration-fix.md +191 -0
- package/.cursor/skills/deco-full-analysis/checklists/image-optimization.md +180 -0
- package/.cursor/skills/deco-full-analysis/checklists/loader-optimization.md +165 -0
- package/.cursor/skills/deco-full-analysis/checklists/seo-fix.md +183 -0
- package/.cursor/skills/deco-full-analysis/checklists/site-cleanup.md +281 -0
- package/.cursor/skills/deco-full-analysis/discovery.md +548 -0
- package/.cursor/skills/deco-incident-debugging/SKILL.md +378 -0
- package/.cursor/skills/deco-incident-debugging/headless-mode.md +510 -0
- package/.cursor/skills/deco-incident-debugging/learnings-index.md +227 -0
- package/.cursor/skills/deco-incident-debugging/triage-workflow.md +312 -0
- package/.cursor/skills/deco-islands-migration/SKILL.md +251 -0
- package/.cursor/skills/deco-loader-n-plus-1-detector/SKILL.md +275 -0
- package/.cursor/skills/deco-performance-audit/SKILL.md +530 -0
- package/.cursor/skills/deco-performance-audit/tools-reference.md +428 -0
- package/.cursor/skills/deco-performance-audit/workflow.md +457 -0
- package/.cursor/skills/deco-server-functions-invoke/SKILL.md +92 -0
- package/.cursor/skills/deco-server-functions-invoke/architecture.md +166 -0
- package/.cursor/skills/deco-server-functions-invoke/generator.md +122 -0
- package/.cursor/skills/deco-server-functions-invoke/problem.md +98 -0
- package/.cursor/skills/deco-server-functions-invoke/troubleshooting.md +110 -0
- package/.cursor/skills/deco-site-deployment/SKILL.md +396 -0
- package/.cursor/skills/deco-site-memory-debugging/SKILL.md +121 -0
- package/.cursor/skills/deco-site-memory-debugging/cdp-connection.md +222 -0
- package/.cursor/skills/deco-site-memory-debugging/memory-analysis.md +362 -0
- package/.cursor/skills/deco-site-patterns/SKILL.md +124 -0
- package/.cursor/skills/deco-site-patterns/app-composition.md +337 -0
- package/.cursor/skills/deco-site-patterns/client-patterns.md +341 -0
- package/.cursor/skills/deco-site-patterns/cms-wiring.md +230 -0
- package/.cursor/skills/deco-site-patterns/section-patterns.md +340 -0
- package/.cursor/skills/deco-site-scaling-tuning/SKILL.md +240 -0
- package/.cursor/skills/deco-site-scaling-tuning/analysis-scripts.md +267 -0
- package/.cursor/skills/deco-start-architecture/SKILL.md +218 -0
- package/.cursor/skills/deco-start-architecture/admin-protocol.md +156 -0
- package/.cursor/skills/deco-start-architecture/cms-resolution.md +201 -0
- package/.cursor/skills/deco-start-architecture/code-quality.md +158 -0
- package/.cursor/skills/deco-start-architecture/gap-analysis.md +129 -0
- package/.cursor/skills/deco-start-architecture/sdk-utilities.md +197 -0
- package/.cursor/skills/deco-start-architecture/worker-entry-caching.md +154 -0
- package/.cursor/skills/deco-startup-analysis/SKILL.md +248 -0
- package/.cursor/skills/deco-storefront-test-checklist/SKILL.md +369 -0
- package/.cursor/skills/deco-tanstack-hydration-fixes/SKILL.md +468 -0
- package/.cursor/skills/deco-tanstack-navigation/SKILL.md +681 -0
- package/.cursor/skills/deco-tanstack-search/SKILL.md +411 -0
- package/.cursor/skills/deco-tanstack-storefront-patterns/SKILL.md +1013 -0
- package/.cursor/skills/deco-to-tanstack-migration/SKILL.md +518 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/codemod-commands.md +174 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/commerce/README.md +78 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/deco-framework/README.md +128 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/gotchas.md +719 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/imports/README.md +70 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/platform-hooks/README.md +154 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/signals/README.md +220 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/vite-config/README.md +78 -0
- package/.cursor/skills/deco-to-tanstack-migration/templates/package-json.md +55 -0
- package/.cursor/skills/deco-to-tanstack-migration/templates/root-route.md +110 -0
- package/.cursor/skills/deco-to-tanstack-migration/templates/router.md +96 -0
- package/.cursor/skills/deco-to-tanstack-migration/templates/setup-ts.md +167 -0
- package/.cursor/skills/deco-to-tanstack-migration/templates/vite-config.md +122 -0
- package/.cursor/skills/deco-to-tanstack-migration/templates/worker-entry.md +67 -0
- package/.cursor/skills/deco-typescript-fixes/SKILL.md +178 -0
- package/.cursor/skills/deco-typescript-fixes/common-fixes.md +330 -0
- package/.cursor/skills/deco-typescript-fixes/strategy.md +148 -0
- package/.cursor/skills/deco-variant-selection-perf/SKILL.md +272 -0
- package/.cursor/skills/deco-vtex-fetch-cache/SKILL.md +225 -0
- package/.cursor/skills/find-skills/SKILL.md +133 -0
- package/.cursor/skills/incident-report/SKILL.md +179 -0
- package/.cursor/skills/incident-report/references/5-whys.md +75 -0
- package/.cursor/skills/incident-report/templates/client-report.md +187 -0
- package/.cursor/skills/incident-report/templates/internal-report.md +206 -0
- package/.cursor/skills/template-skill/SKILL.md +38 -0
- package/.github/workflows/release.yml +32 -0
- package/.releaserc.json +25 -0
- package/CLAUDE.md +135 -0
- package/GAP_ANALYSIS.md +224 -0
- package/GAP_ANALYSIS_V2.md +1013 -0
- package/biome.json +39 -0
- package/knip.json +5 -0
- package/package.json +87 -0
- package/scripts/generate-blocks.ts +69 -0
- package/scripts/generate-invoke.ts +378 -0
- package/scripts/generate-schema.ts +657 -0
- package/src/admin/cors.ts +29 -0
- package/src/admin/decofile.ts +72 -0
- package/src/admin/index.ts +24 -0
- package/src/admin/invoke.ts +163 -0
- package/src/admin/liveControls.ts +29 -0
- package/src/admin/meta.ts +70 -0
- package/src/admin/render.ts +205 -0
- package/src/admin/schema.ts +686 -0
- package/src/admin/setup.ts +44 -0
- package/src/cms/index.ts +59 -0
- package/src/cms/loader.ts +180 -0
- package/src/cms/registry.ts +162 -0
- package/src/cms/resolve.ts +1005 -0
- package/src/cms/sectionLoaders.ts +294 -0
- package/src/hooks/DecoPageRenderer.tsx +444 -0
- package/src/hooks/LazySection.tsx +109 -0
- package/src/hooks/LiveControls.tsx +108 -0
- package/src/hooks/SectionErrorFallback.tsx +85 -0
- package/src/hooks/index.ts +8 -0
- package/src/index.ts +5 -0
- package/src/matchers/builtins.ts +184 -0
- package/src/matchers/posthog.ts +154 -0
- package/src/middleware/decoState.ts +55 -0
- package/src/middleware/healthMetrics.ts +131 -0
- package/src/middleware/index.ts +80 -0
- package/src/middleware/liveness.ts +21 -0
- package/src/middleware/observability.ts +205 -0
- package/src/routes/adminRoutes.ts +83 -0
- package/src/routes/cmsRoute.ts +302 -0
- package/src/routes/components.tsx +34 -0
- package/src/routes/index.ts +15 -0
- package/src/sdk/analytics.ts +72 -0
- package/src/sdk/cacheHeaders.ts +268 -0
- package/src/sdk/cachedLoader.ts +206 -0
- package/src/sdk/clx.ts +3 -0
- package/src/sdk/cookie.ts +39 -0
- package/src/sdk/createInvoke.ts +57 -0
- package/src/sdk/csp.ts +59 -0
- package/src/sdk/env.ts +27 -0
- package/src/sdk/index.ts +63 -0
- package/src/sdk/instrumentedFetch.ts +137 -0
- package/src/sdk/invoke.ts +133 -0
- package/src/sdk/mergeCacheControl.ts +150 -0
- package/src/sdk/redirects.ts +217 -0
- package/src/sdk/requestContext.ts +184 -0
- package/src/sdk/serverTimings.ts +68 -0
- package/src/sdk/signal.ts +41 -0
- package/src/sdk/sitemap.ts +143 -0
- package/src/sdk/urlUtils.ts +117 -0
- package/src/sdk/useDevice.ts +82 -0
- package/src/sdk/useId.ts +7 -0
- package/src/sdk/useScript.ts +101 -0
- package/src/sdk/workerEntry.ts +703 -0
- package/src/sdk/wrapCaughtErrors.ts +107 -0
- package/src/types/index.ts +39 -0
- package/src/types/widgets.ts +13 -0
- 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
|
+
}
|