@wix/zero-config-implementation 1.5.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/README.md +72 -0
- package/dist/component-loader.d.ts +42 -0
- package/dist/component-renderer.d.ts +31 -0
- package/dist/converters/data-item-builder.d.ts +15 -0
- package/dist/converters/index.d.ts +1 -0
- package/dist/converters/to-editor-component.d.ts +3 -0
- package/dist/converters/utils.d.ts +16 -0
- package/dist/errors.d.ts +230 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.js +51978 -0
- package/dist/information-extractors/css/index.d.ts +3 -0
- package/dist/information-extractors/css/parse.d.ts +7 -0
- package/dist/information-extractors/css/selector-matcher.d.ts +3 -0
- package/dist/information-extractors/css/types.d.ts +49 -0
- package/dist/information-extractors/react/extractors/core/index.d.ts +6 -0
- package/dist/information-extractors/react/extractors/core/runner.d.ts +19 -0
- package/dist/information-extractors/react/extractors/core/store.d.ts +17 -0
- package/dist/information-extractors/react/extractors/core/tree-builder.d.ts +15 -0
- package/dist/information-extractors/react/extractors/core/types.d.ts +40 -0
- package/dist/information-extractors/react/extractors/css-properties.d.ts +20 -0
- package/dist/information-extractors/react/extractors/index.d.ts +11 -0
- package/dist/information-extractors/react/extractors/prop-tracker.d.ts +24 -0
- package/dist/information-extractors/react/index.d.ts +9 -0
- package/dist/information-extractors/react/types.d.ts +51 -0
- package/dist/information-extractors/react/utils/mock-generator.d.ts +9 -0
- package/dist/information-extractors/react/utils/prop-spy.d.ts +10 -0
- package/dist/information-extractors/ts/components.d.ts +9 -0
- package/dist/information-extractors/ts/css-imports.d.ts +2 -0
- package/dist/information-extractors/ts/index.d.ts +3 -0
- package/dist/information-extractors/ts/types.d.ts +47 -0
- package/dist/information-extractors/ts/utils/semantic-type-resolver.d.ts +3 -0
- package/dist/jsx-runtime-interceptor.d.ts +42 -0
- package/dist/jsx-runtime-interceptor.js +63 -0
- package/dist/jsx-runtime-loader.d.ts +23 -0
- package/dist/jsx-runtime-loader.js +7 -0
- package/dist/manifest-pipeline.d.ts +33 -0
- package/dist/schema.d.ts +167 -0
- package/dist/ts-compiler.d.ts +13 -0
- package/package.json +81 -0
- package/src/component-loader.test.ts +277 -0
- package/src/component-loader.ts +256 -0
- package/src/component-renderer.ts +192 -0
- package/src/converters/data-item-builder.ts +354 -0
- package/src/converters/index.ts +1 -0
- package/src/converters/to-editor-component.ts +167 -0
- package/src/converters/utils.ts +21 -0
- package/src/errors.ts +103 -0
- package/src/index.ts +223 -0
- package/src/information-extractors/css/README.md +3 -0
- package/src/information-extractors/css/index.ts +3 -0
- package/src/information-extractors/css/parse.ts +450 -0
- package/src/information-extractors/css/selector-matcher.ts +88 -0
- package/src/information-extractors/css/types.ts +56 -0
- package/src/information-extractors/react/extractors/core/index.ts +6 -0
- package/src/information-extractors/react/extractors/core/runner.ts +89 -0
- package/src/information-extractors/react/extractors/core/store.ts +36 -0
- package/src/information-extractors/react/extractors/core/tree-builder.ts +273 -0
- package/src/information-extractors/react/extractors/core/types.ts +48 -0
- package/src/information-extractors/react/extractors/css-properties.ts +214 -0
- package/src/information-extractors/react/extractors/index.ts +27 -0
- package/src/information-extractors/react/extractors/prop-tracker.ts +132 -0
- package/src/information-extractors/react/index.ts +53 -0
- package/src/information-extractors/react/types.ts +70 -0
- package/src/information-extractors/react/utils/mock-generator.ts +331 -0
- package/src/information-extractors/react/utils/prop-spy.ts +168 -0
- package/src/information-extractors/ts/components.ts +300 -0
- package/src/information-extractors/ts/css-imports.ts +26 -0
- package/src/information-extractors/ts/index.ts +3 -0
- package/src/information-extractors/ts/types.ts +56 -0
- package/src/information-extractors/ts/utils/semantic-type-resolver.ts +377 -0
- package/src/jsx-runtime-interceptor.ts +146 -0
- package/src/jsx-runtime-loader.ts +38 -0
- package/src/manifest-pipeline.ts +362 -0
- package/src/schema.ts +174 -0
- package/src/ts-compiler.ts +41 -0
- package/tsconfig.json +17 -0
- package/typedoc.json +18 -0
- package/vite.config.ts +45 -0
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data Item Builder
|
|
3
|
+
*
|
|
4
|
+
* Converts PropInfo (TypeScript prop types) to DataItem (Wix Editor schema).
|
|
5
|
+
* Handles all TypeScript type kinds: primitives, arrays, objects, unions, functions, etc.
|
|
6
|
+
*
|
|
7
|
+
* Unknown or unrecognized types silently fall back to `DATA_TYPE.text` — this is
|
|
8
|
+
* intentional because new type kinds are expected as the TypeScript type system
|
|
9
|
+
* evolves, and a text fallback is always a safe default.
|
|
10
|
+
*
|
|
11
|
+
* The only real error is an invariant violation in `handleArrayType` when the
|
|
12
|
+
* resolved type metadata is inconsistent (kind === 'array' but no elementType).
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { camelCase } from 'case-anything'
|
|
16
|
+
import type { Result } from 'neverthrow'
|
|
17
|
+
import { err, ok } from 'neverthrow'
|
|
18
|
+
import { ParseError } from '../errors'
|
|
19
|
+
import type { PropInfo, ResolvedType } from '../information-extractors/ts/types'
|
|
20
|
+
import type { DataItem } from '../schema'
|
|
21
|
+
import { DATA, MEDIA } from '../schema'
|
|
22
|
+
import { formatDisplayName } from './utils'
|
|
23
|
+
|
|
24
|
+
const { DATA_TYPE } = DATA
|
|
25
|
+
|
|
26
|
+
type ParseErrorInstance = InstanceType<typeof ParseError>
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Converts a single PropInfo to a DataItem for the Wix Editor component schema.
|
|
30
|
+
*
|
|
31
|
+
* @param propInfo - The resolved TypeScript prop information to convert.
|
|
32
|
+
* @param defaultValue - Optional default value for the data item.
|
|
33
|
+
* @returns `Ok<DataItem>` on success, `Err<ParseError>` if an invariant is violated
|
|
34
|
+
* (e.g. an array type missing its element type).
|
|
35
|
+
*/
|
|
36
|
+
export function buildDataItem(propInfo: PropInfo, defaultValue?: unknown): Result<DataItem, ParseErrorInstance> {
|
|
37
|
+
const dataItem: DataItem = {
|
|
38
|
+
displayName: formatDisplayName(propInfo.name),
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (defaultValue !== undefined) {
|
|
42
|
+
dataItem.defaultValue = defaultValue
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const result = applyResolvedTypeToDataItem(dataItem, propInfo.resolvedType, propInfo)
|
|
46
|
+
if (result.isErr()) {
|
|
47
|
+
return err(result.error)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return ok(dataItem)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Maps resolved TypeScript type to DataItem configuration by dispatching
|
|
55
|
+
* to the appropriate handler based on `resolvedType.kind`.
|
|
56
|
+
*
|
|
57
|
+
* Unknown type kinds silently fall back to `DATA_TYPE.text`.
|
|
58
|
+
*/
|
|
59
|
+
function applyResolvedTypeToDataItem(
|
|
60
|
+
dataItem: DataItem,
|
|
61
|
+
resolvedType: ResolvedType,
|
|
62
|
+
propInfo: PropInfo,
|
|
63
|
+
): Result<void, ParseErrorInstance> {
|
|
64
|
+
switch (resolvedType.kind) {
|
|
65
|
+
case 'primitive':
|
|
66
|
+
handlePrimitiveType(dataItem, resolvedType, propInfo)
|
|
67
|
+
return ok(undefined)
|
|
68
|
+
|
|
69
|
+
case 'literal':
|
|
70
|
+
handleLiteralType(dataItem)
|
|
71
|
+
return ok(undefined)
|
|
72
|
+
|
|
73
|
+
case 'enum':
|
|
74
|
+
handleEnumType(dataItem)
|
|
75
|
+
return ok(undefined)
|
|
76
|
+
|
|
77
|
+
case 'array':
|
|
78
|
+
return handleArrayType(dataItem, resolvedType)
|
|
79
|
+
|
|
80
|
+
case 'object':
|
|
81
|
+
return handleObjectType(dataItem, resolvedType)
|
|
82
|
+
|
|
83
|
+
case 'union':
|
|
84
|
+
return handleUnionType(dataItem, resolvedType, propInfo)
|
|
85
|
+
|
|
86
|
+
case 'function':
|
|
87
|
+
handleFunctionType(dataItem, propInfo)
|
|
88
|
+
return ok(undefined)
|
|
89
|
+
|
|
90
|
+
case 'semantic':
|
|
91
|
+
handleSemanticType(dataItem, resolvedType)
|
|
92
|
+
return ok(undefined)
|
|
93
|
+
|
|
94
|
+
default:
|
|
95
|
+
// Unknown type kind — silently fall back to text
|
|
96
|
+
dataItem.dataType = DATA_TYPE.text
|
|
97
|
+
dataItem.text = {}
|
|
98
|
+
return ok(undefined)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Handles primitive types (string, number, boolean).
|
|
104
|
+
* Unrecognized primitives silently fall back to text.
|
|
105
|
+
*/
|
|
106
|
+
function handlePrimitiveType(dataItem: DataItem, resolvedType: ResolvedType, propInfo: PropInfo): void {
|
|
107
|
+
const typeValue = (resolvedType.value as string | undefined)?.toLowerCase() || propInfo.type.toLowerCase()
|
|
108
|
+
|
|
109
|
+
if (typeValue.includes('string')) {
|
|
110
|
+
dataItem.dataType = DATA_TYPE.text
|
|
111
|
+
dataItem.text = {}
|
|
112
|
+
} else if (typeValue.includes('number')) {
|
|
113
|
+
dataItem.dataType = DATA_TYPE.number
|
|
114
|
+
dataItem.number = {}
|
|
115
|
+
} else if (typeValue.includes('boolean')) {
|
|
116
|
+
dataItem.dataType = DATA_TYPE.booleanValue
|
|
117
|
+
} else {
|
|
118
|
+
// Unknown primitive — silently fall back to text
|
|
119
|
+
dataItem.dataType = DATA_TYPE.text
|
|
120
|
+
dataItem.text = {}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Handles literal types (e.g., "red" | "blue").
|
|
126
|
+
*/
|
|
127
|
+
function handleLiteralType(dataItem: DataItem): void {
|
|
128
|
+
dataItem.dataType = DATA_TYPE.text
|
|
129
|
+
dataItem.text = {}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Handles enum types.
|
|
134
|
+
*/
|
|
135
|
+
function handleEnumType(dataItem: DataItem): void {
|
|
136
|
+
dataItem.dataType = DATA_TYPE.textEnum
|
|
137
|
+
dataItem.textEnum = {
|
|
138
|
+
options: [],
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Handles array types. Fails with a `ParseError` if the resolved type
|
|
144
|
+
* has kind 'array' but is missing its `elementType` (invariant violation).
|
|
145
|
+
*/
|
|
146
|
+
function handleArrayType(dataItem: DataItem, resolvedType: ResolvedType): Result<void, ParseErrorInstance> {
|
|
147
|
+
if (resolvedType.kind !== 'array' || resolvedType.elementType === undefined) {
|
|
148
|
+
return err(
|
|
149
|
+
new ParseError('Invalid array type: resolved type has kind "array" but is missing elementType', {
|
|
150
|
+
props: { phase: 'conversion', isDefect: true },
|
|
151
|
+
}),
|
|
152
|
+
)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
dataItem.dataType = DATA_TYPE.arrayItems
|
|
156
|
+
const elementDataItem: DataItem = {}
|
|
157
|
+
const result = applyResolvedTypeToDataItem(elementDataItem, resolvedType.elementType, {
|
|
158
|
+
name: 'element',
|
|
159
|
+
required: false,
|
|
160
|
+
type: resolvedType.elementType.kind,
|
|
161
|
+
resolvedType: resolvedType.elementType,
|
|
162
|
+
} satisfies PropInfo)
|
|
163
|
+
|
|
164
|
+
if (result.isErr()) {
|
|
165
|
+
return result
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
dataItem.arrayItems = {
|
|
169
|
+
dataItem: elementDataItem,
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return ok(undefined)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Handles object types with nested properties.
|
|
177
|
+
* Recursively calls `buildDataItem` for each property.
|
|
178
|
+
*/
|
|
179
|
+
function handleObjectType(dataItem: DataItem, resolvedType: ResolvedType): Result<void, ParseErrorInstance> {
|
|
180
|
+
dataItem.dataType = DATA_TYPE.data
|
|
181
|
+
|
|
182
|
+
if (resolvedType.properties) {
|
|
183
|
+
const nestedItems: Record<string, DataItem> = {}
|
|
184
|
+
|
|
185
|
+
for (const [propName, propInfo] of Object.entries(resolvedType.properties)) {
|
|
186
|
+
const result = buildDataItem(propInfo, undefined)
|
|
187
|
+
if (result.isErr()) {
|
|
188
|
+
return err(result.error)
|
|
189
|
+
}
|
|
190
|
+
nestedItems[propName] = result.value
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
dataItem.data = {
|
|
194
|
+
items: nestedItems,
|
|
195
|
+
}
|
|
196
|
+
} else {
|
|
197
|
+
dataItem.data = {
|
|
198
|
+
items: {},
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return ok(undefined)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Handles union types by detecting special patterns (boolean literals,
|
|
207
|
+
* string literal enums) or falling back to the first valid member type.
|
|
208
|
+
*/
|
|
209
|
+
function handleUnionType(
|
|
210
|
+
dataItem: DataItem,
|
|
211
|
+
resolvedType: ResolvedType,
|
|
212
|
+
propInfo: PropInfo,
|
|
213
|
+
): Result<void, ParseErrorInstance> {
|
|
214
|
+
if (!resolvedType.types || resolvedType.types.length === 0) {
|
|
215
|
+
dataItem.dataType = DATA_TYPE.text
|
|
216
|
+
dataItem.text = {}
|
|
217
|
+
return ok(undefined)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Filter out undefined and null types
|
|
221
|
+
const validTypes = resolvedType.types.filter((t) => t.value !== 'undefined' && t.value !== 'null')
|
|
222
|
+
|
|
223
|
+
if (validTypes.length === 0) {
|
|
224
|
+
dataItem.dataType = DATA_TYPE.text
|
|
225
|
+
dataItem.text = {}
|
|
226
|
+
return ok(undefined)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Check if it's a boolean type (union of true | false literals)
|
|
230
|
+
const isBooleanLiteralUnion =
|
|
231
|
+
validTypes.length <= 2 && validTypes.every((t) => t.kind === 'literal' && (t.value === true || t.value === false))
|
|
232
|
+
if (isBooleanLiteralUnion) {
|
|
233
|
+
dataItem.dataType = DATA_TYPE.booleanValue
|
|
234
|
+
return ok(undefined)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Check if it's a union of string literals (enum-like)
|
|
238
|
+
const allStringLiterals = validTypes.every((t) => t.kind === 'literal' && typeof t.value === 'string')
|
|
239
|
+
if (allStringLiterals) {
|
|
240
|
+
dataItem.dataType = DATA_TYPE.textEnum
|
|
241
|
+
dataItem.textEnum = {
|
|
242
|
+
options: validTypes
|
|
243
|
+
.map((t) => ({
|
|
244
|
+
value: String(t.value || ''),
|
|
245
|
+
displayName: formatDisplayName(String(t.value || '')),
|
|
246
|
+
}))
|
|
247
|
+
.filter((opt) => opt.value),
|
|
248
|
+
}
|
|
249
|
+
return ok(undefined)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// For other unions, use the first valid type
|
|
253
|
+
const firstValidType = validTypes[0]
|
|
254
|
+
if (firstValidType) {
|
|
255
|
+
return applyResolvedTypeToDataItem(dataItem, firstValidType, propInfo)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
dataItem.dataType = DATA_TYPE.text
|
|
259
|
+
dataItem.text = {}
|
|
260
|
+
return ok(undefined)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Handles semantic types from React and @wix/public-schemas packages.
|
|
265
|
+
* Unknown semantic sources or types silently fall back to text.
|
|
266
|
+
*/
|
|
267
|
+
function handleSemanticType(dataItem: DataItem, resolvedType: ResolvedType): void {
|
|
268
|
+
const semanticValue = resolvedType.value as string
|
|
269
|
+
const source = resolvedType.source
|
|
270
|
+
|
|
271
|
+
// React types - handle containers
|
|
272
|
+
if (source === '@types/react' || source === 'react') {
|
|
273
|
+
if (semanticValue === 'ReactNode' || semanticValue === 'ReactElement') {
|
|
274
|
+
dataItem.dataType = DATA_TYPE.container
|
|
275
|
+
} else {
|
|
276
|
+
dataItem.dataType = DATA_TYPE.text
|
|
277
|
+
dataItem.text = {}
|
|
278
|
+
}
|
|
279
|
+
return
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Wix public-schemas (Builder) types - map directly to DATA_TYPE via camelCase conversion
|
|
283
|
+
if (source === '@wix/public-schemas') {
|
|
284
|
+
const dataTypeKey = camelCase(semanticValue) as keyof typeof DATA_TYPE
|
|
285
|
+
if (dataTypeKey in DATA_TYPE) {
|
|
286
|
+
dataItem.dataType = DATA_TYPE[dataTypeKey]
|
|
287
|
+
applyDataToBuilderType(dataItem, dataTypeKey)
|
|
288
|
+
} else {
|
|
289
|
+
// Unknown Wix semantic type — silently fall back to text
|
|
290
|
+
dataItem.dataType = DATA_TYPE.text
|
|
291
|
+
dataItem.text = {}
|
|
292
|
+
}
|
|
293
|
+
return
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Unknown source — silently fall back to text
|
|
297
|
+
dataItem.dataType = DATA_TYPE.text
|
|
298
|
+
dataItem.text = {}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Handles function types — maps to event handlers.
|
|
303
|
+
*/
|
|
304
|
+
function handleFunctionType(dataItem: DataItem, propInfo: PropInfo): void {
|
|
305
|
+
const propName = propInfo.name.toLowerCase()
|
|
306
|
+
|
|
307
|
+
if (propName === 'onclick') {
|
|
308
|
+
dataItem.dataType = DATA_TYPE.onClick
|
|
309
|
+
} else if (propName === 'onchange') {
|
|
310
|
+
dataItem.dataType = DATA_TYPE.onChange
|
|
311
|
+
} else if (propName === 'onkeypress') {
|
|
312
|
+
dataItem.dataType = DATA_TYPE.onKeyPress
|
|
313
|
+
} else if (propName === 'onkeyup') {
|
|
314
|
+
dataItem.dataType = DATA_TYPE.onKeyUp
|
|
315
|
+
} else if (propName === 'onsubmit') {
|
|
316
|
+
dataItem.dataType = DATA_TYPE.onSubmit
|
|
317
|
+
} else {
|
|
318
|
+
dataItem.dataType = DATA_TYPE.function
|
|
319
|
+
dataItem.function = {}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Applies special data to builder types if required (e.g., link types, image category).
|
|
325
|
+
*/
|
|
326
|
+
function applyDataToBuilderType(dataItem: DataItem, builderType: keyof typeof DATA_TYPE): void {
|
|
327
|
+
switch (builderType) {
|
|
328
|
+
case 'link':
|
|
329
|
+
dataItem.dataType = DATA_TYPE.link
|
|
330
|
+
dataItem.link = {
|
|
331
|
+
linkTypes: [
|
|
332
|
+
DATA.LINK_TYPE.externalLink,
|
|
333
|
+
DATA.LINK_TYPE.anchorLink,
|
|
334
|
+
DATA.LINK_TYPE.emailLink,
|
|
335
|
+
DATA.LINK_TYPE.phoneLink,
|
|
336
|
+
DATA.LINK_TYPE.dynamicPageLink,
|
|
337
|
+
DATA.LINK_TYPE.pageLink,
|
|
338
|
+
DATA.LINK_TYPE.whatsAppLink,
|
|
339
|
+
DATA.LINK_TYPE.documentLink,
|
|
340
|
+
DATA.LINK_TYPE.popupLink,
|
|
341
|
+
DATA.LINK_TYPE.addressLink,
|
|
342
|
+
DATA.LINK_TYPE.edgeAnchorLinks,
|
|
343
|
+
DATA.LINK_TYPE.loginToWixLink,
|
|
344
|
+
],
|
|
345
|
+
}
|
|
346
|
+
break
|
|
347
|
+
case 'image':
|
|
348
|
+
dataItem.dataType = DATA_TYPE.image
|
|
349
|
+
dataItem.image = {
|
|
350
|
+
category: MEDIA.IMAGE_CATEGORY.IMAGE,
|
|
351
|
+
}
|
|
352
|
+
break
|
|
353
|
+
}
|
|
354
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { toEditorReactComponent } from './to-editor-component'
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import type { ComponentInfoWithCss } from '../index'
|
|
2
|
+
import type { MatchedCssData } from '../information-extractors/css/types'
|
|
3
|
+
import type {
|
|
4
|
+
CoupledComponentInfo,
|
|
5
|
+
CoupledProp,
|
|
6
|
+
CssPropertiesData,
|
|
7
|
+
ExtractedElement,
|
|
8
|
+
} from '../information-extractors/react'
|
|
9
|
+
import type {
|
|
10
|
+
CssCustomPropertyItem,
|
|
11
|
+
CssPropertyItem,
|
|
12
|
+
DataItem,
|
|
13
|
+
EditorElement,
|
|
14
|
+
EditorReactComponent,
|
|
15
|
+
ElementItem,
|
|
16
|
+
} from '../schema'
|
|
17
|
+
import { ELEMENTS } from '../schema'
|
|
18
|
+
import { buildDataItem } from './data-item-builder'
|
|
19
|
+
import { formatDisplayName } from './utils'
|
|
20
|
+
|
|
21
|
+
// Props to exclude from data items (same as in element-data.ts)
|
|
22
|
+
const EXCLUDED_PROPS = new Set(['id', 'className', 'elementProps', 'wix'])
|
|
23
|
+
|
|
24
|
+
export function toEditorReactComponent(component: ComponentInfoWithCss): EditorReactComponent {
|
|
25
|
+
return {
|
|
26
|
+
editorElement: buildEditorElement(component),
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function buildEditorElement(component: ComponentInfoWithCss): EditorElement {
|
|
31
|
+
const rootElement = component.elements[0]
|
|
32
|
+
const childElements = rootElement?.children ?? []
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
selector: buildSelector(rootElement),
|
|
36
|
+
displayName: formatDisplayName(component.componentName),
|
|
37
|
+
data: buildData(component.props),
|
|
38
|
+
elements: buildElements(childElements, component.innerElementProps),
|
|
39
|
+
cssProperties: buildCssProperties(rootElement),
|
|
40
|
+
cssCustomProperties: buildCssCustomPropertiesForElement(rootElement),
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function buildSelector(rootElement?: ExtractedElement): string {
|
|
45
|
+
if (rootElement?.attributes.class) {
|
|
46
|
+
return `.${rootElement.attributes.class.split(' ')[0]}`
|
|
47
|
+
}
|
|
48
|
+
return rootElement?.tag ?? ''
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function buildData(props: Record<string, CoupledProp>): Record<string, DataItem> {
|
|
52
|
+
const data: Record<string, DataItem> = {}
|
|
53
|
+
|
|
54
|
+
for (const [name, prop] of Object.entries(props)) {
|
|
55
|
+
// Skip excluded props and literals
|
|
56
|
+
if (EXCLUDED_PROPS.has(name) || prop.resolvedType.kind === 'literal') continue
|
|
57
|
+
|
|
58
|
+
// Extract default value for buildDataItem
|
|
59
|
+
const defaultValue = prop.defaultValue?.kind !== 'unresolved' ? prop.defaultValue?.value : undefined
|
|
60
|
+
|
|
61
|
+
const result = buildDataItem(prop, defaultValue)
|
|
62
|
+
if (result.isOk()) {
|
|
63
|
+
data[name] = result.value
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return data
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function buildElements(
|
|
71
|
+
elements: ExtractedElement[],
|
|
72
|
+
innerElementProps?: CoupledComponentInfo['innerElementProps'],
|
|
73
|
+
): Record<string, ElementItem> {
|
|
74
|
+
const result: Record<string, ElementItem> = {}
|
|
75
|
+
|
|
76
|
+
for (const el of elements) {
|
|
77
|
+
const elementData = innerElementProps?.get(el.traceId)
|
|
78
|
+
const data = elementData ? buildData(elementData) : undefined
|
|
79
|
+
const cssProps = buildCssProperties(el)
|
|
80
|
+
const cssCustomProps = buildCssCustomPropertiesForElement(el)
|
|
81
|
+
|
|
82
|
+
result[el.name] = {
|
|
83
|
+
elementType: ELEMENTS.ELEMENT_TYPE.inlineElement,
|
|
84
|
+
inlineElement: {
|
|
85
|
+
selector: buildSelector(el),
|
|
86
|
+
displayName: formatDisplayName(el.name),
|
|
87
|
+
// Add data from inner element props if available
|
|
88
|
+
...(data && Object.keys(data).length > 0 && { data }),
|
|
89
|
+
// CSS properties from heuristic + matched CSS files
|
|
90
|
+
...(Object.keys(cssProps).length > 0 && { cssProperties: cssProps }),
|
|
91
|
+
// CSS custom properties from matched rules for this element
|
|
92
|
+
...(Object.keys(cssCustomProps).length > 0 && { cssCustomProperties: cssCustomProps }),
|
|
93
|
+
// Recursively build nested elements
|
|
94
|
+
elements: el.children.length > 0 ? buildElements(el.children, innerElementProps) : undefined,
|
|
95
|
+
},
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return result
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Returns the CSS property values matched for this specific element's selector(s).
|
|
104
|
+
* Reads from css-matcher.matches which is populated by matchCssSelectors.
|
|
105
|
+
* When multiple rules match (e.g. .foo and .foo.dark), later match wins for same property.
|
|
106
|
+
*/
|
|
107
|
+
function getMatchedPropertyValues(element: ExtractedElement): Map<string, string> {
|
|
108
|
+
const matcherData = element.extractorData.get('css-matcher') as MatchedCssData | undefined
|
|
109
|
+
const matches = matcherData?.matches
|
|
110
|
+
if (!matches || matches.length === 0) return new Map()
|
|
111
|
+
|
|
112
|
+
const values = new Map<string, string>()
|
|
113
|
+
for (const match of matches) {
|
|
114
|
+
for (const prop of match.properties) {
|
|
115
|
+
values.set(prop.name, prop.value)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return values
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function buildCssProperties(element: ExtractedElement | undefined): Record<string, CssPropertyItem> {
|
|
122
|
+
const result: Record<string, CssPropertyItem> = {}
|
|
123
|
+
|
|
124
|
+
// Get the CSS properties decided by the heuristic
|
|
125
|
+
const cssData = element?.extractorData.get('css-properties') as CssPropertiesData | undefined
|
|
126
|
+
const decidedProperties = cssData?.relevant
|
|
127
|
+
if (!decidedProperties || decidedProperties.length === 0) {
|
|
128
|
+
return result
|
|
129
|
+
}
|
|
130
|
+
const cssPropertyValues = element ? getMatchedPropertyValues(element) : new Map<string, string>()
|
|
131
|
+
for (const propName of decidedProperties) {
|
|
132
|
+
const defaultValue = cssPropertyValues.get(propName)
|
|
133
|
+
result[propName] = {
|
|
134
|
+
// Only include defaultValue if found in CSS files
|
|
135
|
+
...(defaultValue !== undefined && { defaultValue }),
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return result
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Build cssCustomProperties for a single element from this element's matched rules only.
|
|
144
|
+
* Only includes --vars that were declared in a rule that matched this element's selector(s).
|
|
145
|
+
*/
|
|
146
|
+
function buildCssCustomPropertiesForElement(
|
|
147
|
+
element: ExtractedElement | undefined,
|
|
148
|
+
): Record<string, CssCustomPropertyItem> {
|
|
149
|
+
const matcherData = element?.extractorData.get('css-matcher') as MatchedCssData | undefined
|
|
150
|
+
const customProperties = matcherData?.customProperties
|
|
151
|
+
if (!customProperties || Object.keys(customProperties).length === 0) {
|
|
152
|
+
return {}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const result: Record<string, CssCustomPropertyItem> = {}
|
|
156
|
+
for (const [name, value] of Object.entries(customProperties)) {
|
|
157
|
+
// Skip --display (handled in regular properties)
|
|
158
|
+
if (name === '--display') continue
|
|
159
|
+
|
|
160
|
+
// Strip the -- prefix from the name
|
|
161
|
+
const cleanName = name.startsWith('--') ? name.slice(2) : name
|
|
162
|
+
result[cleanName] = {
|
|
163
|
+
defaultValue: value,
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return result
|
|
167
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for the converter module
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { capitalCase } from 'case-anything'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Formats any string format to Title Case display name
|
|
9
|
+
* Handles: kebab-case, camelCase, PascalCase, snake_case, SCREAMING_SNAKE_CASE, and mixed formats
|
|
10
|
+
*
|
|
11
|
+
* Examples:
|
|
12
|
+
* "input-field-weight" -> "Input Field Weight"
|
|
13
|
+
* "camelCaseExample" -> "Camel Case Example"
|
|
14
|
+
* "PascalCaseExample" -> "Pascal Case Example"
|
|
15
|
+
* "snake_case_example" -> "Snake Case Example"
|
|
16
|
+
* "SCREAMING_SNAKE_CASE" -> "Screaming Snake Case"
|
|
17
|
+
* "mixed-format_example" -> "Mixed Format Example"
|
|
18
|
+
*/
|
|
19
|
+
export function formatDisplayName(input: string): string {
|
|
20
|
+
return capitalCase(input, { keepSpecialCharacters: false })
|
|
21
|
+
}
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error class hierarchy using modern-errors and modern-errors-cli.
|
|
3
|
+
*
|
|
4
|
+
* Classes are organized by **failure type** (what went wrong), not by pipeline
|
|
5
|
+
* phase (where it happened). Phase is an orthogonal runtime property composed
|
|
6
|
+
* onto any error instance via `mapErr` or instance `props`.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import ModernError from 'modern-errors'
|
|
10
|
+
import modernErrorsCli from 'modern-errors-cli'
|
|
11
|
+
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Base Error
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Root error class for the entire project. All domain errors extend this.
|
|
18
|
+
*
|
|
19
|
+
* Plugins attached here cascade to every subclass automatically.
|
|
20
|
+
* The `phase` prop defaults to `"unknown"` and is overridden at the call
|
|
21
|
+
* site or via `mapErr` as the error propagates through pipeline stages.
|
|
22
|
+
*/
|
|
23
|
+
export const BaseError = ModernError.subclass('BaseError', {
|
|
24
|
+
plugins: [modernErrorsCli],
|
|
25
|
+
props: {
|
|
26
|
+
/** Pipeline stage where the error originated (composed at runtime). */
|
|
27
|
+
phase: 'unknown' as string,
|
|
28
|
+
/** True if this error represents a violated invariant, not a user error. */
|
|
29
|
+
isDefect: false,
|
|
30
|
+
/** Process exit code used by BaseError.exit(). */
|
|
31
|
+
exitCode: 1,
|
|
32
|
+
},
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// Domain Subclasses — organized by failure type
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
/** A required resource (file, module, component) could not be found. */
|
|
40
|
+
export const NotFoundError = BaseError.subclass('NotFoundError')
|
|
41
|
+
|
|
42
|
+
/** Data could not be parsed or decoded (JSON, source code, config, CSS). */
|
|
43
|
+
export const ParseError = BaseError.subclass('ParseError')
|
|
44
|
+
|
|
45
|
+
/** Input or data does not satisfy validation rules. */
|
|
46
|
+
export const ValidationError = BaseError.subclass('ValidationError')
|
|
47
|
+
|
|
48
|
+
/** File system or network I/O failed (permissions, disk full, timeout). */
|
|
49
|
+
export const IoError = BaseError.subclass('IoError')
|
|
50
|
+
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
// Defect Error
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Represents an unexpected internal failure — a violated invariant or
|
|
57
|
+
* impossible state that should never occur during normal operation.
|
|
58
|
+
*
|
|
59
|
+
* Exit code 70 (EX_SOFTWARE) signals an internal software error.
|
|
60
|
+
*/
|
|
61
|
+
export const DefectError = BaseError.subclass('DefectError', {
|
|
62
|
+
props: {
|
|
63
|
+
phase: 'internal',
|
|
64
|
+
isDefect: true,
|
|
65
|
+
exitCode: 70,
|
|
66
|
+
},
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
// Defect Boundary
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
const REPO_ISSUES_URL = 'https://github.com/wix-private/ZeroConfig/issues'
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Wrap an async entry point with a defect boundary.
|
|
77
|
+
*
|
|
78
|
+
* If the wrapped function throws an exception (i.e. a defect that was not
|
|
79
|
+
* captured as a Result), it is caught, wrapped in a DefectError, and printed
|
|
80
|
+
* via BaseError.exit().
|
|
81
|
+
*
|
|
82
|
+
* @param fn - The entry point function to wrap
|
|
83
|
+
* @throws {DefectError} If an unexpected exception occurs during execution
|
|
84
|
+
*/
|
|
85
|
+
export async function withDefectBoundary(fn: () => Promise<void>): Promise<void> {
|
|
86
|
+
try {
|
|
87
|
+
await fn()
|
|
88
|
+
} catch (thrown) {
|
|
89
|
+
const normalized = BaseError.normalize(thrown)
|
|
90
|
+
|
|
91
|
+
if (normalized instanceof BaseError) {
|
|
92
|
+
BaseError.exit(normalized)
|
|
93
|
+
return
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const defect = new DefectError(
|
|
97
|
+
`[DEFECT] An unexpected internal error occurred — please report at ${REPO_ISSUES_URL}`,
|
|
98
|
+
{ cause: normalized },
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
BaseError.exit(defect)
|
|
102
|
+
}
|
|
103
|
+
}
|