@wix/zero-config-implementation 1.89.0 → 1.90.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/dist/index.d.ts +5 -5
- package/dist/index.js +5507 -5472
- package/package.json +2 -2
- package/src/index.ts +11 -3
- package/src/information-extractors/css/parse.ts +2 -2
- package/src/information-extractors/css/types.ts +4 -4
- package/src/validators/editor-element-validator.test.ts +142 -0
- package/src/validators/editor-element-validator.ts +64 -0
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"registry": "https://registry.npmjs.org/",
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
7
|
-
"version": "1.
|
|
7
|
+
"version": "1.90.0",
|
|
8
8
|
"description": "Core library for extracting component manifests from JS and CSS files",
|
|
9
9
|
"type": "module",
|
|
10
10
|
"main": "dist/index.js",
|
|
@@ -106,5 +106,5 @@
|
|
|
106
106
|
]
|
|
107
107
|
}
|
|
108
108
|
},
|
|
109
|
-
"falconPackageHash": "
|
|
109
|
+
"falconPackageHash": "3d380033b2cb76ee95040a71fda50b5bbee8d8cb70460bfb8c8a5313"
|
|
110
110
|
}
|
package/src/index.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { Result, ResultAsync } from 'neverthrow'
|
|
|
3
3
|
import React, { type ComponentType } from 'react'
|
|
4
4
|
|
|
5
5
|
import { toEditorReactComponent } from './converters'
|
|
6
|
-
import { BaseError, IoError, type NotFoundError, ParseError } from './errors'
|
|
6
|
+
import { BaseError, IoError, type NotFoundError, ParseError, type ValidationError } from './errors'
|
|
7
7
|
import { buildContextProviderModules } from './extensions/context-providers/context'
|
|
8
8
|
import { buildContextAwareWrapper, loadMockProviders } from './extensions/context-providers/mock-provider'
|
|
9
9
|
import { buildRefElementContext } from './extensions/ref-elements/context'
|
|
@@ -16,6 +16,7 @@ import type { ComponentInfo } from './information-extractors/ts/types'
|
|
|
16
16
|
import { processComponent } from './manifest-pipeline'
|
|
17
17
|
import { findComponent, findDefaultComponent, loadModuleForExtraction } from './module-loader'
|
|
18
18
|
import { compileTsFile } from './ts-compiler'
|
|
19
|
+
import { validateEditorElement } from './validators/editor-element-validator'
|
|
19
20
|
|
|
20
21
|
const defaultWrapper = wrapWithWixServices
|
|
21
22
|
|
|
@@ -52,7 +53,10 @@ export function extractComponentManifestResult(
|
|
|
52
53
|
options?: ExtractComponentManifestOptions,
|
|
53
54
|
): ResultAsync<
|
|
54
55
|
ManifestResult,
|
|
55
|
-
|
|
56
|
+
| InstanceType<typeof NotFoundError>
|
|
57
|
+
| InstanceType<typeof ParseError>
|
|
58
|
+
| InstanceType<typeof IoError>
|
|
59
|
+
| InstanceType<typeof ValidationError>
|
|
56
60
|
> {
|
|
57
61
|
// Step 1: Compile TypeScript (fatal)
|
|
58
62
|
return compileTsFile(componentPath)
|
|
@@ -206,7 +210,11 @@ async function processComponentWithCleanup(
|
|
|
206
210
|
throw processResult.error
|
|
207
211
|
}
|
|
208
212
|
|
|
209
|
-
|
|
213
|
+
const editorReactComponent = toEditorReactComponent(processResult.component)
|
|
214
|
+
if (editorReactComponent.editorElement) {
|
|
215
|
+
validateEditorElement(editorReactComponent.editorElement, processResult.component.componentName)
|
|
216
|
+
}
|
|
217
|
+
return { component: editorReactComponent, errors: [] }
|
|
210
218
|
} finally {
|
|
211
219
|
await cleanup()
|
|
212
220
|
}
|
|
@@ -84,14 +84,14 @@ export function parseCss(cssString: string): CSSParserAPI {
|
|
|
84
84
|
return getSelectorSpecificity(parsed)
|
|
85
85
|
},
|
|
86
86
|
|
|
87
|
-
getVarPropertyType(varName: string, defaultValue: string): string
|
|
87
|
+
getVarPropertyType(varName: string, defaultValue: string): string {
|
|
88
88
|
// A `@property` registration is the declared type and wins over usage inference.
|
|
89
89
|
const normalizedVarName = varName.startsWith('--') ? varName : `--${varName}`
|
|
90
90
|
const registered = registeredProperties.get(normalizedVarName)
|
|
91
91
|
if (registered) return registered.cssPropertyType
|
|
92
92
|
|
|
93
93
|
const usages = this.getVarUsages(varName)
|
|
94
|
-
if (usages.length === 0) return
|
|
94
|
+
if (usages.length === 0) return inferCssDataType(defaultValue)
|
|
95
95
|
const uniqueProperties = [...new Set(usages)]
|
|
96
96
|
if (uniqueProperties.length === 1) {
|
|
97
97
|
const camelCased = camelCase(uniqueProperties[0])
|
|
@@ -120,13 +120,13 @@ export interface CSSParserAPI {
|
|
|
120
120
|
* Determines the CSS property type for a custom property.
|
|
121
121
|
* A `@property` registration is the top-priority source (declared type). Otherwise
|
|
122
122
|
* falls back to usage: if all usages of varName are within the same CSS property,
|
|
123
|
-
* returns that property name; if usages differ,
|
|
124
|
-
*
|
|
125
|
-
*
|
|
123
|
+
* returns that property name; if usages differ, or if the variable is not used via
|
|
124
|
+
* var() at all, returns the CSS data type inferred from the initial value
|
|
125
|
+
* ('color', 'length', 'number', or 'string').
|
|
126
126
|
* @param varName - The CSS variable name (with or without --)
|
|
127
127
|
* @param defaultValue - The initial value string of the custom property
|
|
128
128
|
*/
|
|
129
|
-
getVarPropertyType: (varName: string, defaultValue: string) => string
|
|
129
|
+
getVarPropertyType: (varName: string, defaultValue: string) => string
|
|
130
130
|
|
|
131
131
|
/**
|
|
132
132
|
* Returns every custom property registered via a `@property` at-rule, keyed by the
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import type { CssCustomPropertyItem, EditorElement, ElementItem } from '@wix/react-component-schema'
|
|
2
|
+
import { CSS_PROPERTIES, ELEMENTS } from '@wix/react-component-schema'
|
|
3
|
+
import { describe, expect, it } from 'vitest'
|
|
4
|
+
|
|
5
|
+
import { ValidationError } from '../errors'
|
|
6
|
+
import { validateEditorElement } from './editor-element-validator'
|
|
7
|
+
|
|
8
|
+
function buildMinimalEditorElement(overrides: Partial<EditorElement> = {}): EditorElement {
|
|
9
|
+
return {
|
|
10
|
+
selector: '.root',
|
|
11
|
+
displayName: 'Test Component',
|
|
12
|
+
...overrides,
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function buildInlineElementItem(overrides: Partial<ElementItem> = {}): ElementItem {
|
|
17
|
+
return {
|
|
18
|
+
elementType: ELEMENTS.ELEMENT_TYPE.inlineElement,
|
|
19
|
+
inlineElement: {
|
|
20
|
+
selector: '.child',
|
|
21
|
+
displayName: 'Child',
|
|
22
|
+
},
|
|
23
|
+
...overrides,
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function buildCssCustomPropertyItem(cssPropertyType: CssCustomPropertyItem['cssPropertyType']): CssCustomPropertyItem {
|
|
28
|
+
return { cssPropertyType }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
describe('validateEditorElement', () => {
|
|
32
|
+
it('does not throw for a valid minimal editorElement', () => {
|
|
33
|
+
const editorElement = buildMinimalEditorElement()
|
|
34
|
+
expect(() => validateEditorElement(editorElement, 'TestComponent')).not.toThrow()
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('does not throw when cssCustomProperties have valid property types', () => {
|
|
38
|
+
const editorElement = buildMinimalEditorElement({
|
|
39
|
+
cssCustomProperties: {
|
|
40
|
+
color: buildCssCustomPropertyItem(CSS_PROPERTIES.CSS_PROPERTY_TYPE.color),
|
|
41
|
+
size: buildCssCustomPropertyItem(CSS_PROPERTIES.CSS_PROPERTY_TYPE.length),
|
|
42
|
+
},
|
|
43
|
+
})
|
|
44
|
+
expect(() => validateEditorElement(editorElement, 'TestComponent')).not.toThrow()
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('throws ValidationError when a cssCustomProperty has UNKNOWN cssPropertyType', () => {
|
|
48
|
+
const editorElement = buildMinimalEditorElement({
|
|
49
|
+
cssCustomProperties: {
|
|
50
|
+
brokenProp: buildCssCustomPropertyItem(CSS_PROPERTIES.CSS_PROPERTY_TYPE.UNKNOWN_CssPropertyType),
|
|
51
|
+
},
|
|
52
|
+
})
|
|
53
|
+
expect(() => validateEditorElement(editorElement, 'TestComponent')).toThrow(ValidationError)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('includes the field path in the error message for UNKNOWN cssPropertyType', () => {
|
|
57
|
+
const editorElement = buildMinimalEditorElement({
|
|
58
|
+
cssCustomProperties: {
|
|
59
|
+
brokenProp: buildCssCustomPropertyItem(CSS_PROPERTIES.CSS_PROPERTY_TYPE.UNKNOWN_CssPropertyType),
|
|
60
|
+
},
|
|
61
|
+
})
|
|
62
|
+
expect(() => validateEditorElement(editorElement, 'TestComponent')).toThrow(
|
|
63
|
+
'editorElement.cssCustomProperties.brokenProp',
|
|
64
|
+
)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('throws ValidationError when a cssCustomProperty is missing cssPropertyType', () => {
|
|
68
|
+
const editorElement = buildMinimalEditorElement({
|
|
69
|
+
cssCustomProperties: {
|
|
70
|
+
missingTypeProp: {} as CssCustomPropertyItem,
|
|
71
|
+
},
|
|
72
|
+
})
|
|
73
|
+
expect(() => validateEditorElement(editorElement, 'TestComponent')).toThrow(ValidationError)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('throws ValidationError when an elements entry has UNKNOWN elementType', () => {
|
|
77
|
+
const editorElement = buildMinimalEditorElement({
|
|
78
|
+
elements: {
|
|
79
|
+
child: {
|
|
80
|
+
elementType: ELEMENTS.ELEMENT_TYPE.UNKNOWN_ElementType,
|
|
81
|
+
inlineElement: { selector: '.child', displayName: 'Child' },
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
})
|
|
85
|
+
expect(() => validateEditorElement(editorElement, 'TestComponent')).toThrow(ValidationError)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('throws ValidationError when an elements entry is missing elementType', () => {
|
|
89
|
+
const editorElement = buildMinimalEditorElement({
|
|
90
|
+
elements: {
|
|
91
|
+
child: buildInlineElementItem({ elementType: undefined }),
|
|
92
|
+
},
|
|
93
|
+
})
|
|
94
|
+
expect(() => validateEditorElement(editorElement, 'TestComponent')).toThrow(ValidationError)
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('collects all violations and throws once with all of them listed', () => {
|
|
98
|
+
const editorElement = buildMinimalEditorElement({
|
|
99
|
+
cssCustomProperties: {
|
|
100
|
+
badProp: buildCssCustomPropertyItem(CSS_PROPERTIES.CSS_PROPERTY_TYPE.UNKNOWN_CssPropertyType),
|
|
101
|
+
},
|
|
102
|
+
elements: {
|
|
103
|
+
child: {
|
|
104
|
+
elementType: ELEMENTS.ELEMENT_TYPE.UNKNOWN_ElementType,
|
|
105
|
+
inlineElement: { selector: '.child', displayName: 'Child' },
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
})
|
|
109
|
+
expect(() => validateEditorElement(editorElement, 'TestComponent')).toThrow(
|
|
110
|
+
/editorElement\.cssCustomProperties\.badProp.*\n.*editorElement\.elements\.child/s,
|
|
111
|
+
)
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('validates nested inline element cssCustomProperties recursively', () => {
|
|
115
|
+
const editorElement = buildMinimalEditorElement({
|
|
116
|
+
elements: {
|
|
117
|
+
child: {
|
|
118
|
+
elementType: ELEMENTS.ELEMENT_TYPE.inlineElement,
|
|
119
|
+
inlineElement: {
|
|
120
|
+
selector: '.child',
|
|
121
|
+
displayName: 'Child',
|
|
122
|
+
cssCustomProperties: {
|
|
123
|
+
nestedBad: buildCssCustomPropertyItem(CSS_PROPERTIES.CSS_PROPERTY_TYPE.UNKNOWN_CssPropertyType),
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
})
|
|
129
|
+
expect(() => validateEditorElement(editorElement, 'TestComponent')).toThrow(
|
|
130
|
+
'editorElement.elements.child.inlineElement.cssCustomProperties.nestedBad',
|
|
131
|
+
)
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
it('includes the component name in the error message', () => {
|
|
135
|
+
const editorElement = buildMinimalEditorElement({
|
|
136
|
+
cssCustomProperties: {
|
|
137
|
+
badProp: buildCssCustomPropertyItem(CSS_PROPERTIES.CSS_PROPERTY_TYPE.UNKNOWN_CssPropertyType),
|
|
138
|
+
},
|
|
139
|
+
})
|
|
140
|
+
expect(() => validateEditorElement(editorElement, 'MyButton')).toThrow('MyButton')
|
|
141
|
+
})
|
|
142
|
+
})
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CssCustomPropertyItem,
|
|
3
|
+
EditorReactComponent,
|
|
4
|
+
ElementItem,
|
|
5
|
+
InlineElement,
|
|
6
|
+
} from '@wix/react-component-schema'
|
|
7
|
+
import { CSS_PROPERTIES, ELEMENTS } from '@wix/react-component-schema'
|
|
8
|
+
|
|
9
|
+
import { ValidationError } from '../errors'
|
|
10
|
+
|
|
11
|
+
type RawEditorElement = NonNullable<EditorReactComponent['editorElement']>
|
|
12
|
+
type AnyElement = RawEditorElement | InlineElement
|
|
13
|
+
|
|
14
|
+
export function validateEditorElement(editorElement: RawEditorElement, componentName: string): void {
|
|
15
|
+
const violations = collectElementViolations(editorElement, 'editorElement')
|
|
16
|
+
if (violations.length === 0) return
|
|
17
|
+
|
|
18
|
+
const violationList = violations.map((violation) => ` - ${violation}`).join('\n')
|
|
19
|
+
throw new ValidationError(`Component "${componentName}" produced an invalid editorElement:\n${violationList}`, {
|
|
20
|
+
props: { phase: 'conversion' },
|
|
21
|
+
})
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function collectElementViolations(element: AnyElement, elementPath: string): string[] {
|
|
25
|
+
return [
|
|
26
|
+
...validateCssCustomProperties(element.cssCustomProperties, elementPath),
|
|
27
|
+
...validateElementsMap(element.elements, elementPath),
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function validateCssCustomProperties(
|
|
32
|
+
cssCustomProperties: Record<string, CssCustomPropertyItem> | undefined,
|
|
33
|
+
elementPath: string,
|
|
34
|
+
): string[] {
|
|
35
|
+
if (!cssCustomProperties) return []
|
|
36
|
+
|
|
37
|
+
const violations: string[] = []
|
|
38
|
+
for (const [propKey, propValue] of Object.entries(cssCustomProperties)) {
|
|
39
|
+
const unknownType = CSS_PROPERTIES.CSS_PROPERTY_TYPE.UNKNOWN_CssPropertyType
|
|
40
|
+
if (!propValue.cssPropertyType || propValue.cssPropertyType === unknownType) {
|
|
41
|
+
violations.push(`${elementPath}.cssCustomProperties.${propKey}: cssPropertyType is not set or UNKNOWN`)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return violations
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function validateElementsMap(elements: Record<string, ElementItem> | undefined, elementPath: string): string[] {
|
|
48
|
+
if (!elements) return []
|
|
49
|
+
|
|
50
|
+
const violations: string[] = []
|
|
51
|
+
for (const [elementKey, elementItem] of Object.entries(elements)) {
|
|
52
|
+
const itemPath = `${elementPath}.elements.${elementKey}`
|
|
53
|
+
const unknownType = ELEMENTS.ELEMENT_TYPE.UNKNOWN_ElementType
|
|
54
|
+
|
|
55
|
+
if (!elementItem.elementType || elementItem.elementType === unknownType) {
|
|
56
|
+
violations.push(`${itemPath}: elementType is not set or UNKNOWN`)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (elementItem.inlineElement) {
|
|
60
|
+
violations.push(...collectElementViolations(elementItem.inlineElement, `${itemPath}.inlineElement`))
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return violations
|
|
64
|
+
}
|