@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.
Files changed (78) hide show
  1. package/README.md +72 -0
  2. package/dist/component-loader.d.ts +42 -0
  3. package/dist/component-renderer.d.ts +31 -0
  4. package/dist/converters/data-item-builder.d.ts +15 -0
  5. package/dist/converters/index.d.ts +1 -0
  6. package/dist/converters/to-editor-component.d.ts +3 -0
  7. package/dist/converters/utils.d.ts +16 -0
  8. package/dist/errors.d.ts +230 -0
  9. package/dist/index.d.ts +42 -0
  10. package/dist/index.js +51978 -0
  11. package/dist/information-extractors/css/index.d.ts +3 -0
  12. package/dist/information-extractors/css/parse.d.ts +7 -0
  13. package/dist/information-extractors/css/selector-matcher.d.ts +3 -0
  14. package/dist/information-extractors/css/types.d.ts +49 -0
  15. package/dist/information-extractors/react/extractors/core/index.d.ts +6 -0
  16. package/dist/information-extractors/react/extractors/core/runner.d.ts +19 -0
  17. package/dist/information-extractors/react/extractors/core/store.d.ts +17 -0
  18. package/dist/information-extractors/react/extractors/core/tree-builder.d.ts +15 -0
  19. package/dist/information-extractors/react/extractors/core/types.d.ts +40 -0
  20. package/dist/information-extractors/react/extractors/css-properties.d.ts +20 -0
  21. package/dist/information-extractors/react/extractors/index.d.ts +11 -0
  22. package/dist/information-extractors/react/extractors/prop-tracker.d.ts +24 -0
  23. package/dist/information-extractors/react/index.d.ts +9 -0
  24. package/dist/information-extractors/react/types.d.ts +51 -0
  25. package/dist/information-extractors/react/utils/mock-generator.d.ts +9 -0
  26. package/dist/information-extractors/react/utils/prop-spy.d.ts +10 -0
  27. package/dist/information-extractors/ts/components.d.ts +9 -0
  28. package/dist/information-extractors/ts/css-imports.d.ts +2 -0
  29. package/dist/information-extractors/ts/index.d.ts +3 -0
  30. package/dist/information-extractors/ts/types.d.ts +47 -0
  31. package/dist/information-extractors/ts/utils/semantic-type-resolver.d.ts +3 -0
  32. package/dist/jsx-runtime-interceptor.d.ts +42 -0
  33. package/dist/jsx-runtime-interceptor.js +63 -0
  34. package/dist/jsx-runtime-loader.d.ts +23 -0
  35. package/dist/jsx-runtime-loader.js +7 -0
  36. package/dist/manifest-pipeline.d.ts +33 -0
  37. package/dist/schema.d.ts +167 -0
  38. package/dist/ts-compiler.d.ts +13 -0
  39. package/package.json +81 -0
  40. package/src/component-loader.test.ts +277 -0
  41. package/src/component-loader.ts +256 -0
  42. package/src/component-renderer.ts +192 -0
  43. package/src/converters/data-item-builder.ts +354 -0
  44. package/src/converters/index.ts +1 -0
  45. package/src/converters/to-editor-component.ts +167 -0
  46. package/src/converters/utils.ts +21 -0
  47. package/src/errors.ts +103 -0
  48. package/src/index.ts +223 -0
  49. package/src/information-extractors/css/README.md +3 -0
  50. package/src/information-extractors/css/index.ts +3 -0
  51. package/src/information-extractors/css/parse.ts +450 -0
  52. package/src/information-extractors/css/selector-matcher.ts +88 -0
  53. package/src/information-extractors/css/types.ts +56 -0
  54. package/src/information-extractors/react/extractors/core/index.ts +6 -0
  55. package/src/information-extractors/react/extractors/core/runner.ts +89 -0
  56. package/src/information-extractors/react/extractors/core/store.ts +36 -0
  57. package/src/information-extractors/react/extractors/core/tree-builder.ts +273 -0
  58. package/src/information-extractors/react/extractors/core/types.ts +48 -0
  59. package/src/information-extractors/react/extractors/css-properties.ts +214 -0
  60. package/src/information-extractors/react/extractors/index.ts +27 -0
  61. package/src/information-extractors/react/extractors/prop-tracker.ts +132 -0
  62. package/src/information-extractors/react/index.ts +53 -0
  63. package/src/information-extractors/react/types.ts +70 -0
  64. package/src/information-extractors/react/utils/mock-generator.ts +331 -0
  65. package/src/information-extractors/react/utils/prop-spy.ts +168 -0
  66. package/src/information-extractors/ts/components.ts +300 -0
  67. package/src/information-extractors/ts/css-imports.ts +26 -0
  68. package/src/information-extractors/ts/index.ts +3 -0
  69. package/src/information-extractors/ts/types.ts +56 -0
  70. package/src/information-extractors/ts/utils/semantic-type-resolver.ts +377 -0
  71. package/src/jsx-runtime-interceptor.ts +146 -0
  72. package/src/jsx-runtime-loader.ts +38 -0
  73. package/src/manifest-pipeline.ts +362 -0
  74. package/src/schema.ts +174 -0
  75. package/src/ts-compiler.ts +41 -0
  76. package/tsconfig.json +17 -0
  77. package/typedoc.json +18 -0
  78. 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
+ }