meno-core 1.0.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/bin/cli.ts +281 -0
- package/build-static.ts +298 -0
- package/bunfig.toml +39 -0
- package/entries/client-router.tsx +111 -0
- package/entries/server-router.tsx +71 -0
- package/lib/client/ClientInitializer.test.ts +9 -0
- package/lib/client/ClientInitializer.test.ts.skip +92 -0
- package/lib/client/ClientInitializer.ts +60 -0
- package/lib/client/ErrorBoundary.test.tsx +595 -0
- package/lib/client/ErrorBoundary.tsx +230 -0
- package/lib/client/componentRegistry.test.ts +165 -0
- package/lib/client/componentRegistry.ts +18 -0
- package/lib/client/contexts/ThemeContext.tsx +73 -0
- package/lib/client/core/ComponentBuilder.test.ts +677 -0
- package/lib/client/core/ComponentBuilder.ts +660 -0
- package/lib/client/core/ComponentRenderer.test.tsx +176 -0
- package/lib/client/core/ComponentRenderer.tsx +83 -0
- package/lib/client/core/cmsTemplateProcessor.ts +129 -0
- package/lib/client/elementRegistry.ts +81 -0
- package/lib/client/hmr/HMRManager.tsx +179 -0
- package/lib/client/hmr/index.ts +5 -0
- package/lib/client/hmrWebSocket.test.ts +9 -0
- package/lib/client/hmrWebSocket.ts +250 -0
- package/lib/client/hooks/useColorVariables.test.ts +166 -0
- package/lib/client/hooks/useColorVariables.ts +249 -0
- package/lib/client/hooks/usePropertyAutocomplete.test.ts +9 -0
- package/lib/client/hooks/usePropertyAutocomplete.ts +40 -0
- package/lib/client/hydration/HydrationUtils.test.ts +154 -0
- package/lib/client/hydration/HydrationUtils.ts +35 -0
- package/lib/client/i18nConfigService.test.ts +74 -0
- package/lib/client/i18nConfigService.ts +78 -0
- package/lib/client/index.ts +56 -0
- package/lib/client/navigation.test.ts +441 -0
- package/lib/client/navigation.ts +23 -0
- package/lib/client/responsiveStyleResolver.test.ts +491 -0
- package/lib/client/responsiveStyleResolver.ts +184 -0
- package/lib/client/routing/RouteLoader.test.ts +635 -0
- package/lib/client/routing/RouteLoader.ts +347 -0
- package/lib/client/routing/Router.tsx +382 -0
- package/lib/client/scripts/ScriptExecutor.test.ts +489 -0
- package/lib/client/scripts/ScriptExecutor.ts +171 -0
- package/lib/client/scripts/formHandler.ts +103 -0
- package/lib/client/styleProcessor.test.ts +126 -0
- package/lib/client/styleProcessor.ts +92 -0
- package/lib/client/styles/StyleInjector.test.ts +354 -0
- package/lib/client/styles/StyleInjector.ts +154 -0
- package/lib/client/templateEngine.test.ts +660 -0
- package/lib/client/templateEngine.ts +667 -0
- package/lib/client/theme.test.ts +173 -0
- package/lib/client/theme.ts +159 -0
- package/lib/client/utils/toast.ts +46 -0
- package/lib/server/createServer.ts +170 -0
- package/lib/server/cssGenerator.test.ts +172 -0
- package/lib/server/cssGenerator.ts +58 -0
- package/lib/server/fileWatcher.ts +134 -0
- package/lib/server/index.ts +55 -0
- package/lib/server/jsonLoader.test.ts +103 -0
- package/lib/server/jsonLoader.ts +350 -0
- package/lib/server/middleware/cors.test.ts +177 -0
- package/lib/server/middleware/cors.ts +69 -0
- package/lib/server/middleware/errorHandler.test.ts +208 -0
- package/lib/server/middleware/errorHandler.ts +63 -0
- package/lib/server/middleware/index.ts +9 -0
- package/lib/server/middleware/logger.test.ts +233 -0
- package/lib/server/middleware/logger.ts +99 -0
- package/lib/server/pageCache.test.ts +167 -0
- package/lib/server/pageCache.ts +97 -0
- package/lib/server/projectContext.ts +51 -0
- package/lib/server/providers/fileSystemCMSProvider.test.ts +292 -0
- package/lib/server/providers/fileSystemCMSProvider.ts +227 -0
- package/lib/server/providers/fileSystemPageProvider.ts +83 -0
- package/lib/server/routes/api/cms.test.ts +177 -0
- package/lib/server/routes/api/cms.ts +82 -0
- package/lib/server/routes/api/colors.ts +59 -0
- package/lib/server/routes/api/components.ts +70 -0
- package/lib/server/routes/api/config.test.ts +9 -0
- package/lib/server/routes/api/config.ts +28 -0
- package/lib/server/routes/api/core-routes.ts +182 -0
- package/lib/server/routes/api/functions.ts +170 -0
- package/lib/server/routes/api/index.ts +69 -0
- package/lib/server/routes/api/pages.ts +95 -0
- package/lib/server/routes/api/shared.test.ts +81 -0
- package/lib/server/routes/api/shared.ts +31 -0
- package/lib/server/routes/editor.test.ts +9 -0
- package/lib/server/routes/index.ts +104 -0
- package/lib/server/routes/pages.ts +161 -0
- package/lib/server/routes/static.ts +107 -0
- package/lib/server/services/ColorService.ts +193 -0
- package/lib/server/services/cmsService.test.ts +388 -0
- package/lib/server/services/cmsService.ts +296 -0
- package/lib/server/services/componentService.test.ts +276 -0
- package/lib/server/services/componentService.ts +346 -0
- package/lib/server/services/configService.ts +156 -0
- package/lib/server/services/fileWatcherService.ts +67 -0
- package/lib/server/services/index.ts +10 -0
- package/lib/server/services/pageService.test.ts +258 -0
- package/lib/server/services/pageService.ts +240 -0
- package/lib/server/ssrRenderer.test.ts +1005 -0
- package/lib/server/ssrRenderer.ts +878 -0
- package/lib/server/utilityClassGenerator.ts +11 -0
- package/lib/server/utils/index.ts +5 -0
- package/lib/server/utils/jsonLineMapper.test.ts +100 -0
- package/lib/server/utils/jsonLineMapper.ts +166 -0
- package/lib/server/validateStyleCoverage.test.ts +9 -0
- package/lib/server/validateStyleCoverage.ts +167 -0
- package/lib/server/websocketManager.test.ts +9 -0
- package/lib/server/websocketManager.ts +95 -0
- package/lib/shared/attributeNodeUtils.test.ts +152 -0
- package/lib/shared/attributeNodeUtils.ts +50 -0
- package/lib/shared/breakpoints.test.ts +166 -0
- package/lib/shared/breakpoints.ts +65 -0
- package/lib/shared/colorProperties.test.ts +111 -0
- package/lib/shared/colorProperties.ts +40 -0
- package/lib/shared/colorVariableUtils.test.ts +319 -0
- package/lib/shared/colorVariableUtils.ts +97 -0
- package/lib/shared/constants.test.ts +175 -0
- package/lib/shared/constants.ts +116 -0
- package/lib/shared/cssGeneration.ts +481 -0
- package/lib/shared/cssProperties.test.ts +252 -0
- package/lib/shared/cssProperties.ts +338 -0
- package/lib/shared/elementUtils.test.ts +245 -0
- package/lib/shared/elementUtils.ts +90 -0
- package/lib/shared/fontLoader.ts +97 -0
- package/lib/shared/i18n.test.ts +313 -0
- package/lib/shared/i18n.ts +286 -0
- package/lib/shared/index.ts +50 -0
- package/lib/shared/interfaces/contentProvider.test.ts +9 -0
- package/lib/shared/interfaces/contentProvider.ts +121 -0
- package/lib/shared/nodeUtils.test.ts +320 -0
- package/lib/shared/nodeUtils.ts +220 -0
- package/lib/shared/pathArrayUtils.test.ts +315 -0
- package/lib/shared/pathArrayUtils.ts +17 -0
- package/lib/shared/pathUtils.test.ts +260 -0
- package/lib/shared/pathUtils.ts +244 -0
- package/lib/shared/paths/Path.test.ts +74 -0
- package/lib/shared/paths/Path.ts +23 -0
- package/lib/shared/paths/PathConverter.test.ts +232 -0
- package/lib/shared/paths/PathConverter.ts +141 -0
- package/lib/shared/paths/PathUtils.ts +290 -0
- package/lib/shared/paths/PathValidator.test.ts +193 -0
- package/lib/shared/paths/PathValidator.ts +53 -0
- package/lib/shared/paths/index.ts +48 -0
- package/lib/shared/propResolver.test.ts +639 -0
- package/lib/shared/propResolver.ts +124 -0
- package/lib/shared/registry/BaseNodeTypeRegistry.test.ts +190 -0
- package/lib/shared/registry/BaseNodeTypeRegistry.ts +200 -0
- package/lib/shared/registry/ClientNodeTypeRegistry.ts +34 -0
- package/lib/shared/registry/ClientRegistry.test.ts +26 -0
- package/lib/shared/registry/ClientRegistry.ts +15 -0
- package/lib/shared/registry/ComponentRegistry.test.ts +293 -0
- package/lib/shared/registry/ComponentRegistry.ts +100 -0
- package/lib/shared/registry/NodeTypeDefinition.ts +198 -0
- package/lib/shared/registry/NodeTypeManager.ts +94 -0
- package/lib/shared/registry/RegistryManager.test.ts +58 -0
- package/lib/shared/registry/RegistryManager.ts +60 -0
- package/lib/shared/registry/SSRNodeTypeRegistry.ts +33 -0
- package/lib/shared/registry/SSRRegistry.test.ts +26 -0
- package/lib/shared/registry/SSRRegistry.ts +15 -0
- package/lib/shared/registry/createNodeType.ts +175 -0
- package/lib/shared/registry/defineNodeType.ts +73 -0
- package/lib/shared/registry/fieldPresets.ts +109 -0
- package/lib/shared/registry/index.ts +50 -0
- package/lib/shared/registry/nodeTypes/ComponentInstanceNodeType.ts +71 -0
- package/lib/shared/registry/nodeTypes/EmbedNodeType.ts +61 -0
- package/lib/shared/registry/nodeTypes/HtmlNodeType.ts +88 -0
- package/lib/shared/registry/nodeTypes/LocaleListNodeType.ts +66 -0
- package/lib/shared/registry/nodeTypes/ObjectLinkNodeType.ts +75 -0
- package/lib/shared/registry/nodeTypes/SlotMarkerType.ts +49 -0
- package/lib/shared/registry/nodeTypes/TextNodeType.ts +52 -0
- package/lib/shared/registry/nodeTypes/index.ts +75 -0
- package/lib/shared/responsiveScaling.test.ts +268 -0
- package/lib/shared/responsiveScaling.ts +194 -0
- package/lib/shared/responsiveStyleUtils.test.ts +300 -0
- package/lib/shared/responsiveStyleUtils.ts +139 -0
- package/lib/shared/slugTranslator.test.ts +325 -0
- package/lib/shared/slugTranslator.ts +177 -0
- package/lib/shared/styleNodeUtils.test.ts +132 -0
- package/lib/shared/styleNodeUtils.ts +102 -0
- package/lib/shared/styleUtils.test.ts +238 -0
- package/lib/shared/styleUtils.ts +63 -0
- package/lib/shared/themeDefaults.test.ts +113 -0
- package/lib/shared/themeDefaults.ts +103 -0
- package/lib/shared/tree/PathBuilder.ts +383 -0
- package/lib/shared/treePathUtils.test.ts +539 -0
- package/lib/shared/treePathUtils.ts +339 -0
- package/lib/shared/types/api.ts +58 -0
- package/lib/shared/types/cms.ts +95 -0
- package/lib/shared/types/colors.ts +45 -0
- package/lib/shared/types/components.ts +121 -0
- package/lib/shared/types/errors.test.ts +103 -0
- package/lib/shared/types/errors.ts +69 -0
- package/lib/shared/types/index.ts +96 -0
- package/lib/shared/types/nodes.ts +20 -0
- package/lib/shared/types/rendering.ts +61 -0
- package/lib/shared/types/styles.ts +38 -0
- package/lib/shared/types.ts +11 -0
- package/lib/shared/utilityClassConfig.ts +287 -0
- package/lib/shared/utilityClassMapper.test.ts +140 -0
- package/lib/shared/utilityClassMapper.ts +229 -0
- package/lib/shared/utils/fileUtils.test.ts +99 -0
- package/lib/shared/utils/fileUtils.ts +56 -0
- package/lib/shared/utils.test.ts +261 -0
- package/lib/shared/utils.ts +84 -0
- package/lib/shared/validation/index.ts +7 -0
- package/lib/shared/validation/propValidator.test.ts +178 -0
- package/lib/shared/validation/propValidator.ts +238 -0
- package/lib/shared/validation/schemas.test.ts +177 -0
- package/lib/shared/validation/schemas.ts +401 -0
- package/lib/shared/validation/validators.test.ts +109 -0
- package/lib/shared/validation/validators.ts +304 -0
- package/lib/test-utils/dom-setup.ts +55 -0
- package/lib/test-utils/factories/ConsoleMockFactory.ts +200 -0
- package/lib/test-utils/factories/DomMockFactory.ts +487 -0
- package/lib/test-utils/factories/EventMockFactory.ts +244 -0
- package/lib/test-utils/factories/FetchMockFactory.ts +210 -0
- package/lib/test-utils/factories/ServerMockFactory.ts +223 -0
- package/lib/test-utils/factories/StoreMockFactory.ts +370 -0
- package/lib/test-utils/factories/index.ts +11 -0
- package/lib/test-utils/fixtures.ts +134 -0
- package/lib/test-utils/helpers/asyncHelpers.test.ts +112 -0
- package/lib/test-utils/helpers/asyncHelpers.ts +196 -0
- package/lib/test-utils/helpers/index.ts +6 -0
- package/lib/test-utils/helpers.test.ts +73 -0
- package/lib/test-utils/helpers.ts +90 -0
- package/lib/test-utils/index.ts +17 -0
- package/lib/test-utils/mockFactories.ts +92 -0
- package/lib/test-utils/mocks.ts +341 -0
- package/package.json +38 -0
- package/templates/index-router.html +34 -0
- package/tsconfig.json +14 -0
- package/vite.config.ts +43 -0
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation Schemas
|
|
3
|
+
* Zod schemas for runtime validation of JSON data structures
|
|
4
|
+
* All schemas use .passthrough() to maintain backward compatibility with unknown fields
|
|
5
|
+
*
|
|
6
|
+
* Note: Node type definitions also define their own schemas for TypeScript type inference.
|
|
7
|
+
* The schemas here are the authoritative source for VALIDATION (with proper recursive
|
|
8
|
+
* ComponentNode references). Node type files use simplified schemas for type inference.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { z } from 'zod';
|
|
12
|
+
import type { StyleObject, StyleMapping, LinkMapping } from '../types/styles';
|
|
13
|
+
import { NODE_TYPE } from '../constants';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Prop type schema - validates PropType values
|
|
17
|
+
*/
|
|
18
|
+
export const PropTypeSchema = z.enum(['string', 'select', 'boolean', 'number', 'link', 'file']);
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Prop definition schema
|
|
22
|
+
* Validates prop definitions from component interfaces
|
|
23
|
+
*/
|
|
24
|
+
export const PropDefinitionSchema = z.object({
|
|
25
|
+
type: PropTypeSchema,
|
|
26
|
+
default: z.union([z.string(), z.number(), z.boolean(), z.object({ href: z.string(), target: z.string().optional() })]).optional(),
|
|
27
|
+
options: z.array(z.string()).readonly().optional(),
|
|
28
|
+
accept: z.string().optional(), // For 'file' type: MIME pattern like "image/*", "video/*"
|
|
29
|
+
}).passthrough();
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Style mapping schema
|
|
33
|
+
*/
|
|
34
|
+
export const StyleMappingSchema: z.ZodType<StyleMapping> = z.object({
|
|
35
|
+
_mapping: z.literal(true),
|
|
36
|
+
prop: z.string(),
|
|
37
|
+
values: z.record(z.string(), z.union([z.string(), z.number()])),
|
|
38
|
+
}).passthrough();
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Link mapping schema - for object-link href mappings
|
|
42
|
+
* values is optional - when missing, acts as passthrough for link-type props
|
|
43
|
+
*/
|
|
44
|
+
export const LinkMappingSchema: z.ZodType<LinkMapping> = z.object({
|
|
45
|
+
_mapping: z.literal(true),
|
|
46
|
+
prop: z.string(),
|
|
47
|
+
values: z.record(z.string(), z.object({
|
|
48
|
+
href: z.string(),
|
|
49
|
+
target: z.string().optional(),
|
|
50
|
+
})).optional(),
|
|
51
|
+
}).passthrough();
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Style object schema - flat style object
|
|
55
|
+
*/
|
|
56
|
+
export const StyleObjectSchema: z.ZodType<StyleObject> = z.record(
|
|
57
|
+
z.string(),
|
|
58
|
+
z.union([
|
|
59
|
+
z.string(),
|
|
60
|
+
z.number(),
|
|
61
|
+
StyleMappingSchema,
|
|
62
|
+
])
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Responsive style object schema
|
|
67
|
+
*/
|
|
68
|
+
export const ResponsiveStyleObjectSchema = z.object({
|
|
69
|
+
base: StyleObjectSchema.optional(),
|
|
70
|
+
tablet: StyleObjectSchema.optional(),
|
|
71
|
+
mobile: StyleObjectSchema.optional(),
|
|
72
|
+
}).passthrough();
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Style value schema - can be either flat or responsive
|
|
76
|
+
*/
|
|
77
|
+
export const StyleValueSchema = z.union([
|
|
78
|
+
StyleObjectSchema,
|
|
79
|
+
ResponsiveStyleObjectSchema,
|
|
80
|
+
]);
|
|
81
|
+
|
|
82
|
+
// ============================================================================
|
|
83
|
+
// Node Type Schemas (Validation)
|
|
84
|
+
// These schemas handle proper recursive validation with ComponentNode references.
|
|
85
|
+
// Node type files also have schemas for TypeScript type inference.
|
|
86
|
+
// ============================================================================
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Slot marker schema
|
|
90
|
+
*/
|
|
91
|
+
export const SlotMarkerSchema = z.object({
|
|
92
|
+
type: z.literal(NODE_TYPE.SLOT),
|
|
93
|
+
}).passthrough();
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Component node schema - discriminated union (forward declaration)
|
|
97
|
+
* Uses z.ZodType<any> because schema is more permissive than TypeScript types
|
|
98
|
+
*/
|
|
99
|
+
let ComponentNodeSchema: z.ZodType<any>;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* HTML node schema - recursive definition
|
|
103
|
+
*/
|
|
104
|
+
export const HtmlNodeSchema: z.ZodType<any> = z.lazy(() => z.object({
|
|
105
|
+
type: z.literal(NODE_TYPE.NODE),
|
|
106
|
+
tag: z.string(),
|
|
107
|
+
style: StyleValueSchema.optional(),
|
|
108
|
+
attributes: z.record(z.string(), z.union([z.string(), z.number(), z.boolean()])).optional(),
|
|
109
|
+
props: z.record(z.string(), z.any()).optional(), // Allow props for backward compatibility
|
|
110
|
+
children: z.union([
|
|
111
|
+
z.array(z.union([ComponentNodeSchema, z.string()])),
|
|
112
|
+
z.string(),
|
|
113
|
+
]).optional(),
|
|
114
|
+
}).passthrough());
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Component instance node schema - recursive definition
|
|
118
|
+
*/
|
|
119
|
+
export const ComponentInstanceNodeSchema: z.ZodType<any> = z.lazy(() => z.object({
|
|
120
|
+
type: z.literal(NODE_TYPE.COMPONENT),
|
|
121
|
+
component: z.string(),
|
|
122
|
+
props: z.record(z.string(), z.any()).optional(),
|
|
123
|
+
style: StyleValueSchema.optional(),
|
|
124
|
+
attributes: z.record(z.string(), z.union([z.string(), z.number(), z.boolean()])).optional(),
|
|
125
|
+
children: z.union([
|
|
126
|
+
z.array(z.union([ComponentNodeSchema, z.string()])),
|
|
127
|
+
z.string(),
|
|
128
|
+
]).optional(),
|
|
129
|
+
}).passthrough());
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Embed node schema - for rendering custom HTML/SVG content
|
|
133
|
+
*/
|
|
134
|
+
export const EmbedNodeSchema: z.ZodType<any> = z.lazy(() => z.object({
|
|
135
|
+
type: z.literal(NODE_TYPE.EMBED),
|
|
136
|
+
html: z.string(),
|
|
137
|
+
style: StyleValueSchema.optional(),
|
|
138
|
+
attributes: z.record(z.string(), z.union([z.string(), z.number(), z.boolean()])).optional(),
|
|
139
|
+
}).passthrough());
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Object link node schema - renders as div in editor, <a> in SSR
|
|
143
|
+
*/
|
|
144
|
+
export const ObjectLinkNodeSchema: z.ZodType<any> = z.lazy(() => z.object({
|
|
145
|
+
type: z.literal(NODE_TYPE.OBJECT_LINK),
|
|
146
|
+
href: z.union([z.string(), LinkMappingSchema]),
|
|
147
|
+
style: StyleValueSchema.optional(),
|
|
148
|
+
attributes: z.record(z.string(), z.union([z.string(), z.number(), z.boolean()])).optional(),
|
|
149
|
+
children: z.union([
|
|
150
|
+
z.array(z.union([ComponentNodeSchema, z.string()])),
|
|
151
|
+
z.string(),
|
|
152
|
+
]).optional(),
|
|
153
|
+
}).passthrough());
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Locale list node schema - renders locale switcher links
|
|
157
|
+
*/
|
|
158
|
+
export const LocaleListNodeSchema: z.ZodType<any> = z.lazy(() => z.object({
|
|
159
|
+
type: z.literal(NODE_TYPE.LOCALE_LIST),
|
|
160
|
+
style: StyleValueSchema.optional(),
|
|
161
|
+
itemStyle: StyleValueSchema.optional(),
|
|
162
|
+
activeItemStyle: StyleValueSchema.optional(),
|
|
163
|
+
separatorStyle: StyleValueSchema.optional(),
|
|
164
|
+
showCurrent: z.boolean().optional(),
|
|
165
|
+
showSeparator: z.boolean().optional(),
|
|
166
|
+
showFlag: z.boolean().optional(),
|
|
167
|
+
flagStyle: StyleValueSchema.optional(),
|
|
168
|
+
attributes: z.record(z.string(), z.union([z.string(), z.number(), z.boolean()])).optional(),
|
|
169
|
+
}).passthrough());
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Text node schema - static text in component structures
|
|
173
|
+
*/
|
|
174
|
+
export const TextNodeSchema = z.object({
|
|
175
|
+
type: z.literal(NODE_TYPE.TEXT),
|
|
176
|
+
value: z.string(),
|
|
177
|
+
}).passthrough();
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Component node schema - discriminated union (now defined after dependencies)
|
|
181
|
+
*/
|
|
182
|
+
ComponentNodeSchema = z.union([
|
|
183
|
+
HtmlNodeSchema,
|
|
184
|
+
ComponentInstanceNodeSchema,
|
|
185
|
+
SlotMarkerSchema,
|
|
186
|
+
EmbedNodeSchema,
|
|
187
|
+
ObjectLinkNodeSchema,
|
|
188
|
+
LocaleListNodeSchema,
|
|
189
|
+
TextNodeSchema,
|
|
190
|
+
]);
|
|
191
|
+
|
|
192
|
+
// Export ComponentNodeSchema
|
|
193
|
+
export { ComponentNodeSchema };
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Structured component definition schema
|
|
197
|
+
*/
|
|
198
|
+
export const StructuredComponentDefinitionSchema = z.object({
|
|
199
|
+
interface: z.record(z.string(), PropDefinitionSchema).optional(),
|
|
200
|
+
structure: ComponentNodeSchema.optional(),
|
|
201
|
+
javascript: z.string().optional(),
|
|
202
|
+
css: z.string().optional(),
|
|
203
|
+
category: z.string().optional(),
|
|
204
|
+
}).passthrough();
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Component definition schema
|
|
208
|
+
* Accepts both legacy format (with type/props/children) and new format (just component)
|
|
209
|
+
* New format checked first since it's more common
|
|
210
|
+
*/
|
|
211
|
+
export const ComponentDefinitionSchema = z.union([
|
|
212
|
+
// New format: { component: {...} }
|
|
213
|
+
z.object({
|
|
214
|
+
component: StructuredComponentDefinitionSchema,
|
|
215
|
+
}).passthrough(),
|
|
216
|
+
// Legacy format: { type, props?, children?, component }
|
|
217
|
+
z.object({
|
|
218
|
+
type: z.string(),
|
|
219
|
+
props: z.record(z.string(), z.any()).optional(),
|
|
220
|
+
children: z.array(z.any()).optional(),
|
|
221
|
+
component: StructuredComponentDefinitionSchema,
|
|
222
|
+
}).passthrough(),
|
|
223
|
+
]);
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* I18n value schema - accepts either a string or an i18n object
|
|
227
|
+
*/
|
|
228
|
+
const I18nOrStringSchema = z.union([
|
|
229
|
+
z.string(),
|
|
230
|
+
z.object({
|
|
231
|
+
_i18n: z.literal(true),
|
|
232
|
+
}).passthrough(),
|
|
233
|
+
]);
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Page metadata schema (base version without CMS - used internally)
|
|
237
|
+
* Note: CMS fields added via PageMetaDataWithCMSSchema after CMS schemas are defined
|
|
238
|
+
*/
|
|
239
|
+
const PageMetaDataBaseSchema = z.object({
|
|
240
|
+
title: I18nOrStringSchema.optional(),
|
|
241
|
+
description: I18nOrStringSchema.optional(),
|
|
242
|
+
keywords: I18nOrStringSchema.optional(),
|
|
243
|
+
ogTitle: I18nOrStringSchema.optional(),
|
|
244
|
+
ogDescription: I18nOrStringSchema.optional(),
|
|
245
|
+
ogImage: z.string().optional(),
|
|
246
|
+
ogType: z.string().optional(),
|
|
247
|
+
slugs: z.record(z.string(), z.string()).optional(),
|
|
248
|
+
}).passthrough();
|
|
249
|
+
|
|
250
|
+
// Temporary export for backward compatibility - will be replaced with full schema below
|
|
251
|
+
export const PageMetaDataSchema = PageMetaDataBaseSchema;
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Line range schema for element line number tracking
|
|
255
|
+
*/
|
|
256
|
+
export const LineRangeSchema = z.object({
|
|
257
|
+
startLine: z.number(),
|
|
258
|
+
endLine: z.number(),
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* JSON page schema
|
|
263
|
+
* Requires at least one field to prevent empty objects from matching
|
|
264
|
+
*/
|
|
265
|
+
export const JSONPageSchema = z.object({
|
|
266
|
+
components: z.record(z.string(), ComponentDefinitionSchema).optional(),
|
|
267
|
+
root: ComponentNodeSchema.optional(),
|
|
268
|
+
meta: PageMetaDataSchema.optional(),
|
|
269
|
+
_lineMap: z.record(z.string(), LineRangeSchema).optional(),
|
|
270
|
+
}).passthrough().refine((data) => {
|
|
271
|
+
// Require at least one field to be present
|
|
272
|
+
return data.meta !== undefined || data.components !== undefined || data.root !== undefined;
|
|
273
|
+
}, {
|
|
274
|
+
message: "JSONPage must have at least one of: meta, components, or root"
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Page data with component schema
|
|
279
|
+
*/
|
|
280
|
+
export const PageDataWithComponentSchema = z.object({
|
|
281
|
+
component: z.object({
|
|
282
|
+
structure: ComponentNodeSchema.optional(),
|
|
283
|
+
interface: z.record(z.string(), PropDefinitionSchema).optional(),
|
|
284
|
+
javascript: z.string().optional(),
|
|
285
|
+
css: z.string().optional(),
|
|
286
|
+
}),
|
|
287
|
+
}).passthrough();
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Page data schema - union of JSONPage and PageDataWithComponent
|
|
291
|
+
* PageDataWithComponent checked first because it's more specific
|
|
292
|
+
*/
|
|
293
|
+
export const PageDataSchema = z.union([
|
|
294
|
+
PageDataWithComponentSchema,
|
|
295
|
+
JSONPageSchema,
|
|
296
|
+
]);
|
|
297
|
+
|
|
298
|
+
// ============================================================================
|
|
299
|
+
// CMS Schemas
|
|
300
|
+
// ============================================================================
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* CMS field type schema
|
|
304
|
+
*/
|
|
305
|
+
export const CMSFieldTypeSchema = z.enum([
|
|
306
|
+
'string', 'text', 'number', 'boolean', 'image', 'date', 'select', 'reference', 'i18n', 'i18n-text'
|
|
307
|
+
]);
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* CMS field definition schema
|
|
311
|
+
*/
|
|
312
|
+
export const CMSFieldDefinitionSchema = z.object({
|
|
313
|
+
type: CMSFieldTypeSchema,
|
|
314
|
+
required: z.boolean().optional(),
|
|
315
|
+
default: z.unknown().optional(),
|
|
316
|
+
label: z.string().optional(),
|
|
317
|
+
description: z.string().optional(),
|
|
318
|
+
options: z.array(z.string()).optional(),
|
|
319
|
+
collection: z.string().optional(),
|
|
320
|
+
}).passthrough();
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* CMS schema schema (defines collection structure)
|
|
324
|
+
*/
|
|
325
|
+
export const CMSSchemaSchema = z.object({
|
|
326
|
+
id: z.string(),
|
|
327
|
+
name: z.string(),
|
|
328
|
+
slugField: z.string(),
|
|
329
|
+
urlPattern: z.string(),
|
|
330
|
+
fields: z.record(CMSFieldDefinitionSchema),
|
|
331
|
+
}).passthrough();
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* CMS item schema (content entry)
|
|
335
|
+
*/
|
|
336
|
+
export const CMSItemSchema = z.object({
|
|
337
|
+
_id: z.string(),
|
|
338
|
+
_slug: z.string().optional(),
|
|
339
|
+
_createdAt: z.string().optional(),
|
|
340
|
+
_updatedAt: z.string().optional(),
|
|
341
|
+
}).passthrough();
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* CMS filter condition schema
|
|
345
|
+
*/
|
|
346
|
+
export const CMSFilterConditionSchema = z.object({
|
|
347
|
+
field: z.string(),
|
|
348
|
+
operator: z.enum(['eq', 'neq', 'gt', 'gte', 'lt', 'lte', 'contains', 'in']).optional(),
|
|
349
|
+
value: z.unknown(),
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* CMS sort config schema
|
|
354
|
+
*/
|
|
355
|
+
export const CMSSortConfigSchema = z.object({
|
|
356
|
+
field: z.string(),
|
|
357
|
+
order: z.enum(['asc', 'desc']).optional(),
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* CMSList query schema
|
|
362
|
+
*/
|
|
363
|
+
export const CMSListQuerySchema = z.object({
|
|
364
|
+
collection: z.string(),
|
|
365
|
+
filter: z.union([
|
|
366
|
+
CMSFilterConditionSchema,
|
|
367
|
+
z.array(CMSFilterConditionSchema),
|
|
368
|
+
z.record(z.unknown()),
|
|
369
|
+
]).optional(),
|
|
370
|
+
sort: z.union([CMSSortConfigSchema, z.array(CMSSortConfigSchema)]).optional(),
|
|
371
|
+
limit: z.number().positive().optional(),
|
|
372
|
+
offset: z.number().nonnegative().optional(),
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Page CMS config schema (embedded in page meta)
|
|
377
|
+
*/
|
|
378
|
+
export const PageCmsConfigSchema = z.object({
|
|
379
|
+
id: z.string(),
|
|
380
|
+
name: z.string(),
|
|
381
|
+
slugField: z.string(),
|
|
382
|
+
urlPattern: z.string(),
|
|
383
|
+
fields: z.record(CMSFieldDefinitionSchema),
|
|
384
|
+
}).passthrough();
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Page metadata schema with CMS support
|
|
388
|
+
* Extends base schema with source and cms fields
|
|
389
|
+
*/
|
|
390
|
+
export const PageMetaDataWithCMSSchema = z.object({
|
|
391
|
+
title: z.union([z.string(), z.object({ _i18n: z.literal(true) }).passthrough()]).optional(),
|
|
392
|
+
description: z.union([z.string(), z.object({ _i18n: z.literal(true) }).passthrough()]).optional(),
|
|
393
|
+
keywords: z.union([z.string(), z.object({ _i18n: z.literal(true) }).passthrough()]).optional(),
|
|
394
|
+
ogTitle: z.union([z.string(), z.object({ _i18n: z.literal(true) }).passthrough()]).optional(),
|
|
395
|
+
ogDescription: z.union([z.string(), z.object({ _i18n: z.literal(true) }).passthrough()]).optional(),
|
|
396
|
+
ogImage: z.string().optional(),
|
|
397
|
+
ogType: z.string().optional(),
|
|
398
|
+
slugs: z.record(z.string(), z.string()).optional(),
|
|
399
|
+
source: z.enum(['static', 'cms']).optional(),
|
|
400
|
+
cms: PageCmsConfigSchema.optional(),
|
|
401
|
+
}).passthrough();
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { describe, test, expect } from 'bun:test';
|
|
2
|
+
import {
|
|
3
|
+
validatePropDefinition,
|
|
4
|
+
validateComponentNode,
|
|
5
|
+
validateOrThrow,
|
|
6
|
+
type ValidationResult,
|
|
7
|
+
} from './validators';
|
|
8
|
+
import { NODE_TYPE } from '../constants';
|
|
9
|
+
|
|
10
|
+
describe('validators', () => {
|
|
11
|
+
describe('validatePropDefinition', () => {
|
|
12
|
+
test('validates valid prop definition', () => {
|
|
13
|
+
const validProp = {
|
|
14
|
+
name: 'title',
|
|
15
|
+
type: 'string',
|
|
16
|
+
defaultValue: 'Hello',
|
|
17
|
+
};
|
|
18
|
+
const result = validatePropDefinition(validProp);
|
|
19
|
+
expect(result.valid).toBe(true);
|
|
20
|
+
if (result.valid) {
|
|
21
|
+
expect(result.data.name).toBe('title');
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('returns errors for invalid prop definition', () => {
|
|
26
|
+
const invalidProp = {
|
|
27
|
+
name: 123, // Should be string
|
|
28
|
+
};
|
|
29
|
+
const result = validatePropDefinition(invalidProp);
|
|
30
|
+
expect(result.valid).toBe(false);
|
|
31
|
+
if (!result.valid) {
|
|
32
|
+
expect(result.errors.length).toBeGreaterThan(0);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('handles missing required fields', () => {
|
|
37
|
+
const result = validatePropDefinition({});
|
|
38
|
+
expect(result.valid).toBe(false);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe('validateComponentNode', () => {
|
|
43
|
+
test('validates valid HTML node', () => {
|
|
44
|
+
const validNode = {
|
|
45
|
+
type: NODE_TYPE.NODE,
|
|
46
|
+
tag: 'div',
|
|
47
|
+
children: [],
|
|
48
|
+
};
|
|
49
|
+
const result = validateComponentNode(validNode);
|
|
50
|
+
expect(result.valid).toBe(true);
|
|
51
|
+
if (result.valid) {
|
|
52
|
+
expect(result.data.type).toBe(NODE_TYPE.NODE);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('validates valid component node', () => {
|
|
57
|
+
const validNode = {
|
|
58
|
+
type: NODE_TYPE.COMPONENT,
|
|
59
|
+
component: 'Card',
|
|
60
|
+
props: {},
|
|
61
|
+
};
|
|
62
|
+
const result = validateComponentNode(validNode);
|
|
63
|
+
expect(result.valid).toBe(true);
|
|
64
|
+
if (result.valid) {
|
|
65
|
+
expect(result.data.type).toBe(NODE_TYPE.COMPONENT);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('returns errors for invalid node', () => {
|
|
70
|
+
const invalidNode = {
|
|
71
|
+
type: 'invalid_type',
|
|
72
|
+
};
|
|
73
|
+
const result = validateComponentNode(invalidNode);
|
|
74
|
+
expect(result.valid).toBe(false);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('handles null/undefined', () => {
|
|
78
|
+
expect(validateComponentNode(null).valid).toBe(false);
|
|
79
|
+
expect(validateComponentNode(undefined).valid).toBe(false);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe('validateOrThrow', () => {
|
|
84
|
+
test('returns data on valid input', () => {
|
|
85
|
+
const validator = (data: unknown): ValidationResult<string> => ({
|
|
86
|
+
valid: true,
|
|
87
|
+
data: data as string,
|
|
88
|
+
});
|
|
89
|
+
const result = validateOrThrow('test', validator);
|
|
90
|
+
expect(result).toBe('test');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('throws on invalid input', () => {
|
|
94
|
+
const validator = (data: unknown): ValidationResult<string> => ({
|
|
95
|
+
valid: false,
|
|
96
|
+
errors: [{ path: 'root', message: 'Invalid' }],
|
|
97
|
+
});
|
|
98
|
+
expect(() => validateOrThrow('test', validator)).toThrow();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test('includes custom error message', () => {
|
|
102
|
+
const validator = (data: unknown): ValidationResult<string> => ({
|
|
103
|
+
valid: false,
|
|
104
|
+
errors: [{ path: 'field', message: 'Required' }],
|
|
105
|
+
});
|
|
106
|
+
expect(() => validateOrThrow('test', validator, 'Custom error')).toThrow('Custom error');
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
});
|