@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,657 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
/**
|
|
5
|
+
* Schema Generator for deco admin compatibility.
|
|
6
|
+
*
|
|
7
|
+
* Scans src/sections/ for .tsx files, parses their Props interfaces,
|
|
8
|
+
* and generates JSON Schema 7 definitions in the format expected by
|
|
9
|
+
* the deco admin (/deco/meta endpoint).
|
|
10
|
+
*
|
|
11
|
+
* Usage (from site root):
|
|
12
|
+
* npx tsx node_modules/@decocms/start/scripts/generate-schema.ts [options]
|
|
13
|
+
*
|
|
14
|
+
* Options:
|
|
15
|
+
* --namespace Section namespace (default: "site")
|
|
16
|
+
* --site Site name (default: "storefront")
|
|
17
|
+
* --version Framework version (default: "1.0.0")
|
|
18
|
+
* --sections Sections directory (default: "src/sections")
|
|
19
|
+
* --out Output file (default: "src/server/admin/meta.gen.json")
|
|
20
|
+
* --platform Platform name (default: "cloudflare")
|
|
21
|
+
*/
|
|
22
|
+
import { type Symbol as MorphSymbol, Node, Project, SyntaxKind, type Type } from "ts-morph";
|
|
23
|
+
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// CLI arg parsing
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
const argv = process.argv.slice(2);
|
|
28
|
+
function arg(name: string, fallback: string): string {
|
|
29
|
+
const idx = argv.indexOf(`--${name}`);
|
|
30
|
+
return idx !== -1 && argv[idx + 1] ? argv[idx + 1] : fallback;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const SITE_NAMESPACE = arg("namespace", "site");
|
|
34
|
+
const SITE_NAME = arg("site", "storefront");
|
|
35
|
+
const FRAMEWORK_VERSION = arg("version", "1.0.0");
|
|
36
|
+
const SECTIONS_REL = arg("sections", "src/sections");
|
|
37
|
+
const OUT_REL = arg("out", "src/server/admin/meta.gen.json");
|
|
38
|
+
const PLATFORM = arg("platform", "cloudflare");
|
|
39
|
+
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// Interfaces
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
interface MetaResponse {
|
|
44
|
+
major: number;
|
|
45
|
+
version: string;
|
|
46
|
+
namespace: string;
|
|
47
|
+
site: string;
|
|
48
|
+
manifest: { blocks: Record<string, Record<string, any>> };
|
|
49
|
+
schema: { definitions: Record<string, any>; root: Record<string, any> };
|
|
50
|
+
platform: string;
|
|
51
|
+
cloudProvider: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
// Helpers
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
function toBase64(str: string): string {
|
|
58
|
+
return Buffer.from(str).toString("base64");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Map JSDoc tags to JSON Schema 7 keywords.
|
|
63
|
+
* Supports all 20+ tags from deco-cx/deco.
|
|
64
|
+
*/
|
|
65
|
+
/**
|
|
66
|
+
* Tags that receive special type coercion (not just string passthrough).
|
|
67
|
+
* Matches the original deco-cx/deco parseJSDocAttribute behaviour.
|
|
68
|
+
*/
|
|
69
|
+
const NUMERIC_TAGS = new Set([
|
|
70
|
+
"maximum",
|
|
71
|
+
"minimum",
|
|
72
|
+
"exclusiveMaximum",
|
|
73
|
+
"exclusiveMinimum",
|
|
74
|
+
"multipleOf",
|
|
75
|
+
"maxLength",
|
|
76
|
+
"minLength",
|
|
77
|
+
"maxItems",
|
|
78
|
+
"minItems",
|
|
79
|
+
"maxProperties",
|
|
80
|
+
"minProperties",
|
|
81
|
+
]);
|
|
82
|
+
const BOOLEAN_TAGS = new Set(["readOnly", "writeOnly", "deprecated", "uniqueItems", "ignore"]);
|
|
83
|
+
|
|
84
|
+
function applyJsDocToSchema(schema: any, tags: Record<string, string>): void {
|
|
85
|
+
for (const [tag, value] of Object.entries(tags)) {
|
|
86
|
+
if (tag === "ignore") continue;
|
|
87
|
+
|
|
88
|
+
// Tags with special coercion
|
|
89
|
+
if (tag === "hide") {
|
|
90
|
+
schema.hide = "true";
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (tag === "default") {
|
|
95
|
+
if (value === "true") schema.default = true;
|
|
96
|
+
else if (value === "false") schema.default = false;
|
|
97
|
+
else if (value === "null") schema.default = null;
|
|
98
|
+
else if (!isNaN(Number(value)) && value.trim() !== "") schema.default = Number(value);
|
|
99
|
+
else {
|
|
100
|
+
try {
|
|
101
|
+
schema.default = JSON.parse(value);
|
|
102
|
+
} catch {
|
|
103
|
+
schema.default = value;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (tag === "examples") {
|
|
110
|
+
const lines = value
|
|
111
|
+
.split("\n")
|
|
112
|
+
.map((l) => l.trim())
|
|
113
|
+
.filter(Boolean);
|
|
114
|
+
schema.examples =
|
|
115
|
+
lines.length > 1
|
|
116
|
+
? lines
|
|
117
|
+
: (() => {
|
|
118
|
+
try {
|
|
119
|
+
return JSON.parse(value);
|
|
120
|
+
} catch {
|
|
121
|
+
return [value];
|
|
122
|
+
}
|
|
123
|
+
})();
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (NUMERIC_TAGS.has(tag)) {
|
|
128
|
+
schema[tag] = Number(value);
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (BOOLEAN_TAGS.has(tag)) {
|
|
132
|
+
schema[tag] = value === "true";
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Everything else: pass through as-is (matching original deco behaviour)
|
|
137
|
+
// Covers: title, description, format, widget, icon, titleBy, mode,
|
|
138
|
+
// hideOption, label, options, pattern, section, group, placeholder, etc.
|
|
139
|
+
schema[tag] = value;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const WIDGET_TYPE_FORMATS: Record<string, string> = {
|
|
144
|
+
ImageWidget: "image-uri",
|
|
145
|
+
VideoWidget: "video-uri",
|
|
146
|
+
HTMLWidget: "html",
|
|
147
|
+
RichText: "rich-text",
|
|
148
|
+
Color: "color",
|
|
149
|
+
Secret: "password",
|
|
150
|
+
TextArea: "textarea",
|
|
151
|
+
Code: "code",
|
|
152
|
+
DateTimeWidget: "date-time",
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Detect known widget types and set the appropriate format.
|
|
157
|
+
*/
|
|
158
|
+
function applyWidgetDetection(schema: any, typeText: string): void {
|
|
159
|
+
if (schema.format) return;
|
|
160
|
+
|
|
161
|
+
for (const [widgetType, format] of Object.entries(WIDGET_TYPE_FORMATS)) {
|
|
162
|
+
if (typeText === widgetType || typeText.includes(widgetType)) {
|
|
163
|
+
schema.format = format;
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Smart widget format application that handles arrays, nullable types,
|
|
171
|
+
* and union types by applying the format to the correct inner schema.
|
|
172
|
+
*/
|
|
173
|
+
function applyWidgetFormat(schema: any, typeHint: string): void {
|
|
174
|
+
const matchedFormat = Object.entries(WIDGET_TYPE_FORMATS).find(
|
|
175
|
+
([wt]) => typeHint === wt || typeHint.includes(wt),
|
|
176
|
+
)?.[1];
|
|
177
|
+
|
|
178
|
+
if (!matchedFormat) {
|
|
179
|
+
applyWidgetDetection(schema, typeHint);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (schema.type === "string" && !schema.format) {
|
|
184
|
+
schema.format = matchedFormat;
|
|
185
|
+
} else if (schema.type === "array" && schema.items) {
|
|
186
|
+
if (schema.items.type === "string" && !schema.items.format) {
|
|
187
|
+
schema.items.format = matchedFormat;
|
|
188
|
+
}
|
|
189
|
+
} else if (schema.anyOf) {
|
|
190
|
+
for (const variant of schema.anyOf) {
|
|
191
|
+
if (variant.type === "string" && !variant.format) {
|
|
192
|
+
variant.format = matchedFormat;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Well-known definition key for Section type references resolved by composeMeta
|
|
199
|
+
const SECTION_REF_DEF_KEY = "__SECTION_REF__";
|
|
200
|
+
|
|
201
|
+
// Only truly React-internal props that are never user-defined.
|
|
202
|
+
// Do NOT include "children", "type", or "props" — those are commonly used
|
|
203
|
+
// as legitimate section property names.
|
|
204
|
+
const REACT_INTERNAL_PROPS = new Set([
|
|
205
|
+
"key",
|
|
206
|
+
"ref",
|
|
207
|
+
"then",
|
|
208
|
+
"catch",
|
|
209
|
+
"finally",
|
|
210
|
+
"$$typeof",
|
|
211
|
+
"_owner",
|
|
212
|
+
"_store",
|
|
213
|
+
]);
|
|
214
|
+
|
|
215
|
+
function typeToJsonSchema(type: Type, visited = new Set<string>()): any {
|
|
216
|
+
const typeText = type.getText();
|
|
217
|
+
if (visited.has(typeText)) return { type: "object" };
|
|
218
|
+
visited.add(typeText);
|
|
219
|
+
|
|
220
|
+
try {
|
|
221
|
+
// any / unknown → accept anything
|
|
222
|
+
if (type.isAny() || type.isUnknown()) return {};
|
|
223
|
+
|
|
224
|
+
// ReactNode, JSX.Element, VNode → hide from form
|
|
225
|
+
if (
|
|
226
|
+
/\bReactNode\b|\bJSX\.Element\b|\bReactElement\b|\bVNode\b|\bComponentChildren\b/.test(
|
|
227
|
+
typeText,
|
|
228
|
+
)
|
|
229
|
+
) {
|
|
230
|
+
return { type: "object", hide: "true" };
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (type.isString() || type.isStringLiteral()) {
|
|
234
|
+
return type.isStringLiteral()
|
|
235
|
+
? { type: "string", const: type.getLiteralValue() }
|
|
236
|
+
: { type: "string" };
|
|
237
|
+
}
|
|
238
|
+
if (type.isNumber() || type.isNumberLiteral()) return { type: "number" };
|
|
239
|
+
if (type.isBoolean() || type.isBooleanLiteral()) return { type: "boolean" };
|
|
240
|
+
if (type.isNull() || type.isUndefined()) return { type: "null" };
|
|
241
|
+
|
|
242
|
+
if (type.isArray()) {
|
|
243
|
+
const el = type.getArrayElementType();
|
|
244
|
+
return el
|
|
245
|
+
? { type: "array", items: typeToJsonSchema(el, new Set(visited)) }
|
|
246
|
+
: { type: "array" };
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (type.isUnion()) {
|
|
250
|
+
const parts = type.getUnionTypes();
|
|
251
|
+
const nonNull = parts.filter((t) => !t.isNull() && !t.isUndefined());
|
|
252
|
+
const isNullable = nonNull.length < parts.length;
|
|
253
|
+
|
|
254
|
+
if (nonNull.length === 1) {
|
|
255
|
+
const inner = typeToJsonSchema(nonNull[0], new Set(visited));
|
|
256
|
+
return isNullable ? { ...inner, nullable: true } : inner;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// boolean? → true | false | undefined → collapse to { type: "boolean" }
|
|
260
|
+
if (nonNull.every((t) => t.isBooleanLiteral())) {
|
|
261
|
+
const result: any = { type: "boolean" };
|
|
262
|
+
if (isNullable) result.nullable = true;
|
|
263
|
+
return result;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (nonNull.every((t) => t.isStringLiteral())) {
|
|
267
|
+
const result: any = { type: "string", enum: nonNull.map((t) => t.getLiteralValue()) };
|
|
268
|
+
if (isNullable) result.nullable = true;
|
|
269
|
+
return result;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// 1 | 2 | 3 → { type: "number", enum: [1, 2, 3] }
|
|
273
|
+
if (nonNull.every((t) => t.isNumberLiteral())) {
|
|
274
|
+
const result: any = { type: "number", enum: nonNull.map((t) => t.getLiteralValue()) };
|
|
275
|
+
if (isNullable) result.nullable = true;
|
|
276
|
+
return result;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// General anyOf — try to add title to each variant for discriminated unions
|
|
280
|
+
const anyOf = nonNull.map((t) => {
|
|
281
|
+
const schema = typeToJsonSchema(t, new Set(visited));
|
|
282
|
+
if (!schema.title && schema.type === "object") {
|
|
283
|
+
const sym = t.getAliasSymbol() ?? t.getSymbol();
|
|
284
|
+
const symName = sym?.getName();
|
|
285
|
+
if (symName && symName !== "__type" && symName !== "default") {
|
|
286
|
+
schema.title = symName;
|
|
287
|
+
}
|
|
288
|
+
// Fallback: use a const discriminator field value as title
|
|
289
|
+
if (!schema.title && schema.properties) {
|
|
290
|
+
for (const v of Object.values(schema.properties) as any[]) {
|
|
291
|
+
if (v?.const !== undefined) {
|
|
292
|
+
schema.title = String(v.const);
|
|
293
|
+
break;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return schema;
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
const result: any = { anyOf };
|
|
302
|
+
if (isNullable) result.nullable = true;
|
|
303
|
+
return result;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (type.isObject() || type.isInterface()) {
|
|
307
|
+
// Record<K,V> → { type: "object", additionalProperties: V-schema }
|
|
308
|
+
const stringIdx = type.getStringIndexType();
|
|
309
|
+
const numberIdx = type.getNumberIndexType();
|
|
310
|
+
if ((stringIdx || numberIdx) && type.getProperties().length === 0) {
|
|
311
|
+
const valType = (stringIdx || numberIdx)!;
|
|
312
|
+
return {
|
|
313
|
+
type: "object",
|
|
314
|
+
additionalProperties: typeToJsonSchema(valType, new Set(visited)),
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const properties: Record<string, any> = {};
|
|
319
|
+
const required: string[] = [];
|
|
320
|
+
|
|
321
|
+
for (const prop of type.getProperties()) {
|
|
322
|
+
const name = prop.getName();
|
|
323
|
+
if (name.startsWith("_") || name.startsWith("$") || name === "@type") continue;
|
|
324
|
+
if (REACT_INTERNAL_PROPS.has(name)) continue;
|
|
325
|
+
|
|
326
|
+
const decl = prop.getValueDeclaration();
|
|
327
|
+
if (!decl) continue;
|
|
328
|
+
const propType = prop.getTypeAtLocation(decl);
|
|
329
|
+
|
|
330
|
+
const tags = getJsDocTags(prop);
|
|
331
|
+
if (tags.ignore) continue;
|
|
332
|
+
|
|
333
|
+
// Get AST type-annotation text before resolving
|
|
334
|
+
let typeHint = propType.getText();
|
|
335
|
+
const typeNode =
|
|
336
|
+
decl.getChildrenOfKind(SyntaxKind.TypeReference)[0] ??
|
|
337
|
+
decl.getChildAtIndex(decl.getChildCount() - 1);
|
|
338
|
+
if (typeNode && Node.isTypeReference(typeNode)) {
|
|
339
|
+
typeHint = typeNode.getText();
|
|
340
|
+
} else if (Node.isPropertySignature(decl) || Node.isPropertyDeclaration(decl)) {
|
|
341
|
+
const tn = (decl as any).getTypeNode?.();
|
|
342
|
+
if (tn) typeHint = tn.getText();
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Section type → section picker reference (resolved by composeMeta)
|
|
346
|
+
const baseHint = typeHint.replace(/\s*\|\s*(null|undefined)/g, "").trim();
|
|
347
|
+
if (baseHint === "Section" || baseHint === "Section[]" || baseHint === "Section[] | null") {
|
|
348
|
+
const isArray = baseHint.includes("[]");
|
|
349
|
+
const sectionSchema: any = isArray
|
|
350
|
+
? {
|
|
351
|
+
type: "array",
|
|
352
|
+
items: { $ref: `#/definitions/${SECTION_REF_DEF_KEY}` },
|
|
353
|
+
title: name.charAt(0).toUpperCase() + name.slice(1),
|
|
354
|
+
}
|
|
355
|
+
: {
|
|
356
|
+
$ref: `#/definitions/${SECTION_REF_DEF_KEY}`,
|
|
357
|
+
title: name.charAt(0).toUpperCase() + name.slice(1),
|
|
358
|
+
};
|
|
359
|
+
if (prop.isOptional() || typeHint.includes("null") || typeHint.includes("undefined")) {
|
|
360
|
+
sectionSchema.nullable = true;
|
|
361
|
+
}
|
|
362
|
+
applyJsDocToSchema(sectionSchema, tags);
|
|
363
|
+
properties[name] = sectionSchema;
|
|
364
|
+
if (!prop.isOptional()) required.push(name);
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const schema = typeToJsonSchema(propType, new Set(visited));
|
|
369
|
+
|
|
370
|
+
applyJsDocToSchema(schema, tags);
|
|
371
|
+
applyWidgetFormat(schema, typeHint);
|
|
372
|
+
|
|
373
|
+
if (!schema.title) schema.title = name.charAt(0).toUpperCase() + name.slice(1);
|
|
374
|
+
|
|
375
|
+
properties[name] = schema;
|
|
376
|
+
if (!prop.isOptional()) required.push(name);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const result: any = { type: "object", properties };
|
|
380
|
+
if (required.length > 0) result.required = required;
|
|
381
|
+
return result;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return { type: "string" };
|
|
385
|
+
} finally {
|
|
386
|
+
visited.delete(typeText);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function getJsDocTags(symbol: MorphSymbol): Record<string, string> {
|
|
391
|
+
const tags: Record<string, string> = {};
|
|
392
|
+
for (const decl of symbol.getDeclarations()) {
|
|
393
|
+
const jsDocs = Node.isJSDocable(decl) ? decl.getJsDocs() : [];
|
|
394
|
+
for (const doc of jsDocs) {
|
|
395
|
+
const desc = doc.getDescription().trim();
|
|
396
|
+
if (desc) tags.description = desc;
|
|
397
|
+
for (const tag of doc.getTags()) {
|
|
398
|
+
tags[tag.getTagName()] = tag.getCommentText()?.trim() || "true";
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
return tags;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Extract the first parameter's type from a component's default export
|
|
407
|
+
* using the type checker. Works regardless of whether the export is a
|
|
408
|
+
* function declaration, arrow function, const assignment, or re-export.
|
|
409
|
+
*/
|
|
410
|
+
function extractDefaultExportPropsType(sourceFile: import("ts-morph").SourceFile): Type | null {
|
|
411
|
+
const symbol = sourceFile.getDefaultExportSymbol();
|
|
412
|
+
if (!symbol) return null;
|
|
413
|
+
|
|
414
|
+
const exportType = symbol.getTypeAtLocation(sourceFile);
|
|
415
|
+
const callSigs = exportType.getCallSignatures();
|
|
416
|
+
if (callSigs.length === 0) return null;
|
|
417
|
+
|
|
418
|
+
const params = callSigs[0].getParameters();
|
|
419
|
+
if (params.length === 0) return null;
|
|
420
|
+
|
|
421
|
+
const paramType = params[0].getTypeAtLocation(sourceFile);
|
|
422
|
+
if (paramType.isAny() || paramType.getText() === "{}") return null;
|
|
423
|
+
|
|
424
|
+
return paramType;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Resolve a module specifier to an absolute file path.
|
|
429
|
+
*/
|
|
430
|
+
function resolveModulePath(
|
|
431
|
+
moduleSpec: string,
|
|
432
|
+
fromFile: string,
|
|
433
|
+
projectRoot: string,
|
|
434
|
+
): string | null {
|
|
435
|
+
let target = moduleSpec;
|
|
436
|
+
if (target.startsWith("~/")) {
|
|
437
|
+
target = path.resolve(projectRoot, "src", target.slice(2));
|
|
438
|
+
} else if (target.startsWith("./") || target.startsWith("../")) {
|
|
439
|
+
target = path.resolve(path.dirname(fromFile), target);
|
|
440
|
+
}
|
|
441
|
+
if (!target.match(/\.(tsx?|jsx?)$/)) {
|
|
442
|
+
for (const ext of [".tsx", ".ts", ".jsx", ".js"]) {
|
|
443
|
+
if (fs.existsSync(target + ext)) return target + ext;
|
|
444
|
+
}
|
|
445
|
+
if (fs.existsSync(path.join(target, "index.tsx"))) return path.join(target, "index.tsx");
|
|
446
|
+
if (fs.existsSync(path.join(target, "index.ts"))) return path.join(target, "index.ts");
|
|
447
|
+
}
|
|
448
|
+
return fs.existsSync(target) ? target : null;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Recursively follow `export { default } from "..."` chains (up to maxDepth hops)
|
|
453
|
+
* and try to extract Props from each target file.
|
|
454
|
+
*/
|
|
455
|
+
function resolvePropsViaReExport(
|
|
456
|
+
project: import("ts-morph").Project,
|
|
457
|
+
sourceFile: import("ts-morph").SourceFile,
|
|
458
|
+
filePath: string,
|
|
459
|
+
projectRoot: string,
|
|
460
|
+
maxDepth: number,
|
|
461
|
+
): any | null {
|
|
462
|
+
if (maxDepth <= 0) return null;
|
|
463
|
+
|
|
464
|
+
for (const exportDecl of sourceFile.getExportDeclarations()) {
|
|
465
|
+
const moduleSpec = exportDecl.getModuleSpecifierValue();
|
|
466
|
+
if (!moduleSpec) continue;
|
|
467
|
+
const hasDefault = exportDecl.getNamedExports().some((n) => {
|
|
468
|
+
const name = n.getName();
|
|
469
|
+
const alias = n.getAliasNode()?.getText();
|
|
470
|
+
return name === "default" || alias === "default";
|
|
471
|
+
});
|
|
472
|
+
if (!hasDefault) continue;
|
|
473
|
+
|
|
474
|
+
const targetPath = resolveModulePath(moduleSpec, filePath, projectRoot);
|
|
475
|
+
if (!targetPath) continue;
|
|
476
|
+
|
|
477
|
+
try {
|
|
478
|
+
const targetFile = project.addSourceFileAtPath(targetPath);
|
|
479
|
+
|
|
480
|
+
const targetProps = targetFile.getInterface("Props");
|
|
481
|
+
if (targetProps) return typeToJsonSchema(targetProps.getType());
|
|
482
|
+
|
|
483
|
+
const targetAlias = targetFile.getTypeAlias("Props");
|
|
484
|
+
if (targetAlias) return typeToJsonSchema(targetAlias.getType());
|
|
485
|
+
|
|
486
|
+
// Type-checker approach: extract from default export call signature
|
|
487
|
+
const propsType = extractDefaultExportPropsType(targetFile);
|
|
488
|
+
if (propsType) return typeToJsonSchema(propsType);
|
|
489
|
+
|
|
490
|
+
// Recurse: target might also re-export from another file
|
|
491
|
+
const deeper = resolvePropsViaReExport(
|
|
492
|
+
project,
|
|
493
|
+
targetFile,
|
|
494
|
+
targetPath,
|
|
495
|
+
projectRoot,
|
|
496
|
+
maxDepth - 1,
|
|
497
|
+
);
|
|
498
|
+
if (deeper) return deeper;
|
|
499
|
+
} catch {
|
|
500
|
+
// Target file couldn't be parsed
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
return null;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function findTsxFiles(dir: string): string[] {
|
|
507
|
+
const results: string[] = [];
|
|
508
|
+
if (!fs.existsSync(dir)) return results;
|
|
509
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
510
|
+
const full = path.join(dir, entry.name);
|
|
511
|
+
if (entry.isDirectory()) results.push(...findTsxFiles(full));
|
|
512
|
+
else if (entry.name.endsWith(".tsx") || entry.name.endsWith(".ts")) results.push(full);
|
|
513
|
+
}
|
|
514
|
+
return results;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// ---------------------------------------------------------------------------
|
|
518
|
+
// Main
|
|
519
|
+
// ---------------------------------------------------------------------------
|
|
520
|
+
function generateMeta(): MetaResponse {
|
|
521
|
+
const root = process.cwd();
|
|
522
|
+
const sectionsDir = path.resolve(root, SECTIONS_REL);
|
|
523
|
+
const srcDir = path.join(root, "src");
|
|
524
|
+
|
|
525
|
+
const project = new Project({
|
|
526
|
+
tsConfigFilePath: path.join(root, "tsconfig.json"),
|
|
527
|
+
skipAddingFilesFromTsConfig: true,
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
const definitions: Record<string, any> = {};
|
|
531
|
+
const sectionBlocks: Record<string, any> = {};
|
|
532
|
+
const sectionRootAnyOf: any[] = [];
|
|
533
|
+
|
|
534
|
+
// Resolvable: the admin's deRefUntil expects the LITERAL key "Resolvable",
|
|
535
|
+
// not a base64-encoded version. We store both for compatibility.
|
|
536
|
+
const RESOLVABLE_KEY = "Resolvable";
|
|
537
|
+
const resolvableB64Key = toBase64("Resolvable");
|
|
538
|
+
const resolvableDef = {
|
|
539
|
+
title: "Select from saved",
|
|
540
|
+
type: "object",
|
|
541
|
+
required: ["__resolveType"],
|
|
542
|
+
additionalProperties: true,
|
|
543
|
+
properties: { __resolveType: { type: "string" } },
|
|
544
|
+
};
|
|
545
|
+
definitions[RESOLVABLE_KEY] = resolvableDef;
|
|
546
|
+
definitions[resolvableB64Key] = resolvableDef;
|
|
547
|
+
sectionRootAnyOf.push({ $ref: `#/definitions/${RESOLVABLE_KEY}` });
|
|
548
|
+
|
|
549
|
+
if (!fs.existsSync(sectionsDir)) {
|
|
550
|
+
console.error(`Sections directory not found: ${sectionsDir}`);
|
|
551
|
+
process.exit(1);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
const sectionFiles = findTsxFiles(sectionsDir);
|
|
555
|
+
console.log(`Found ${sectionFiles.length} section files`);
|
|
556
|
+
|
|
557
|
+
for (const filePath of sectionFiles) {
|
|
558
|
+
const relativePath = path.relative(srcDir, filePath);
|
|
559
|
+
const blockKey = `${SITE_NAMESPACE}/${relativePath}`;
|
|
560
|
+
|
|
561
|
+
try {
|
|
562
|
+
const sourceFile = project.addSourceFileAtPath(filePath);
|
|
563
|
+
|
|
564
|
+
let propsSchema: any = null;
|
|
565
|
+
|
|
566
|
+
// Strategy 1: Local Props interface/type alias in the section file
|
|
567
|
+
const propsInterface = sourceFile.getInterface("Props");
|
|
568
|
+
if (propsInterface) propsSchema = typeToJsonSchema(propsInterface.getType());
|
|
569
|
+
|
|
570
|
+
const propsTypeAlias = sourceFile.getTypeAlias("Props");
|
|
571
|
+
if (!propsSchema && propsTypeAlias) propsSchema = typeToJsonSchema(propsTypeAlias.getType());
|
|
572
|
+
|
|
573
|
+
// Strategy 2: Follow re-exports recursively (up to 3 hops)
|
|
574
|
+
// Handles: section → island → component chains
|
|
575
|
+
if (!propsSchema) {
|
|
576
|
+
propsSchema = resolvePropsViaReExport(project, sourceFile, filePath, root, 3);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// Strategy 4: Default export call signature in the section file via type checker
|
|
580
|
+
if (!propsSchema) {
|
|
581
|
+
const localPropsType = extractDefaultExportPropsType(sourceFile);
|
|
582
|
+
if (localPropsType) {
|
|
583
|
+
propsSchema = typeToJsonSchema(localPropsType);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
if (!propsSchema) propsSchema = { type: "object", properties: {} };
|
|
588
|
+
|
|
589
|
+
const propCount = Object.keys(propsSchema.properties || {}).length;
|
|
590
|
+
|
|
591
|
+
const propsDefKey = toBase64(`file:///${filePath}`) + "@Props";
|
|
592
|
+
definitions[propsDefKey] = propsSchema;
|
|
593
|
+
|
|
594
|
+
const sectionDefKey = toBase64(blockKey);
|
|
595
|
+
definitions[sectionDefKey] = {
|
|
596
|
+
title: blockKey,
|
|
597
|
+
type: "object",
|
|
598
|
+
allOf: [{ $ref: `#/definitions/${propsDefKey}` }],
|
|
599
|
+
required: ["__resolveType"],
|
|
600
|
+
properties: {
|
|
601
|
+
__resolveType: { type: "string", enum: [blockKey], default: blockKey },
|
|
602
|
+
},
|
|
603
|
+
};
|
|
604
|
+
|
|
605
|
+
sectionBlocks[blockKey] = {
|
|
606
|
+
$ref: `#/definitions/${sectionDefKey}`,
|
|
607
|
+
namespace: SITE_NAMESPACE,
|
|
608
|
+
};
|
|
609
|
+
sectionRootAnyOf.push({
|
|
610
|
+
$ref: `#/definitions/${sectionDefKey}`,
|
|
611
|
+
inputSchema: `#/definitions/${propsDefKey}`,
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
console.log(` ${propCount > 0 ? "✓" : "○"} ${blockKey} (${propCount} props)`);
|
|
615
|
+
} catch (e) {
|
|
616
|
+
console.warn(` ✗ ${blockKey}: ${(e as Error).message}`);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// Pages, loaders, matchers, etc. are injected at runtime by composeMeta()
|
|
621
|
+
// in src/admin/schema.ts -- the generator only handles site sections.
|
|
622
|
+
const emptyAnyOf = { anyOf: [] as any[] };
|
|
623
|
+
return {
|
|
624
|
+
major: 1,
|
|
625
|
+
version: FRAMEWORK_VERSION,
|
|
626
|
+
namespace: SITE_NAMESPACE,
|
|
627
|
+
site: SITE_NAME,
|
|
628
|
+
manifest: { blocks: { sections: sectionBlocks } },
|
|
629
|
+
schema: {
|
|
630
|
+
definitions,
|
|
631
|
+
root: {
|
|
632
|
+
sections: { anyOf: sectionRootAnyOf },
|
|
633
|
+
loaders: emptyAnyOf,
|
|
634
|
+
actions: emptyAnyOf,
|
|
635
|
+
pages: emptyAnyOf,
|
|
636
|
+
handlers: emptyAnyOf,
|
|
637
|
+
matchers: emptyAnyOf,
|
|
638
|
+
flags: emptyAnyOf,
|
|
639
|
+
functions: emptyAnyOf,
|
|
640
|
+
apps: emptyAnyOf,
|
|
641
|
+
},
|
|
642
|
+
},
|
|
643
|
+
platform: PLATFORM,
|
|
644
|
+
cloudProvider: PLATFORM,
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
const meta = generateMeta();
|
|
649
|
+
const outPath = path.resolve(process.cwd(), OUT_REL);
|
|
650
|
+
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
651
|
+
fs.writeFileSync(outPath, JSON.stringify(meta, null, 2));
|
|
652
|
+
|
|
653
|
+
const defCount = Object.keys(meta.schema.definitions).length;
|
|
654
|
+
const secCount = Object.keys(meta.manifest.blocks.sections || {}).length;
|
|
655
|
+
console.log(
|
|
656
|
+
`\nGenerated schema: ${defCount} definitions, ${secCount} sections → ${path.relative(process.cwd(), outPath)}`,
|
|
657
|
+
);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const ADMIN_ORIGINS = [
|
|
2
|
+
"https://admin.deco.cx",
|
|
3
|
+
"https://v0-admin.deco.cx",
|
|
4
|
+
"https://play.deco.cx",
|
|
5
|
+
"https://admin-cx.deco.page",
|
|
6
|
+
"https://deco.chat",
|
|
7
|
+
"https://admin.decocms.com",
|
|
8
|
+
"https://decocms.com",
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
export function isAdminOrLocalhost(request: Request): boolean {
|
|
12
|
+
const origin = request.headers.get("origin") || request.headers.get("referer") || "";
|
|
13
|
+
|
|
14
|
+
if (origin.includes("localhost") || origin.includes("127.0.0.1")) {
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return ADMIN_ORIGINS.some((domain) => origin.startsWith(domain));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function corsHeaders(request: Request): Record<string, string> {
|
|
22
|
+
const origin = request.headers.get("origin") || "*";
|
|
23
|
+
return {
|
|
24
|
+
"Access-Control-Allow-Origin": origin,
|
|
25
|
+
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
26
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization, If-None-Match",
|
|
27
|
+
"Access-Control-Allow-Credentials": "true",
|
|
28
|
+
};
|
|
29
|
+
}
|