@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,362 @@
1
+ import fs from 'node:fs'
2
+
3
+ import type { ComponentInfo } from './information-extractors/ts'
4
+ import type { PropInfo } from './information-extractors/ts/types'
5
+
6
+ import {
7
+ type ExtractedElement,
8
+ type PropTrackerData,
9
+ createCssPropertiesExtractor,
10
+ createPropTrackerExtractor,
11
+ runExtractors,
12
+ } from './information-extractors/react'
13
+ import type { CoupledComponentInfo, CoupledProp, DOMBinding, TrackingStores } from './information-extractors/react'
14
+
15
+ import { matchCssSelectors, parseCss } from './information-extractors/css'
16
+ import type { CSSParserAPI } from './information-extractors/css'
17
+
18
+ import type { ComponentType } from 'react'
19
+
20
+ // ─────────────────────────────────────────────────────────────────────────────
21
+ // Types
22
+ // ─────────────────────────────────────────────────────────────────────────────
23
+
24
+ /** A non-fatal issue encountered during component processing. */
25
+ export interface ExtractionWarning {
26
+ componentName: string
27
+ phase: 'render' | 'coupling' | 'css' | 'loader' | 'conversion'
28
+ error: string
29
+ }
30
+
31
+ export interface ExtractedCssInfo {
32
+ filePath: string
33
+ api: CSSParserAPI
34
+ properties: Map<string, string>
35
+ customProperties: Map<string, string>
36
+ }
37
+
38
+ export interface ComponentInfoWithCss extends CoupledComponentInfo {
39
+ css: ExtractedCssInfo[]
40
+ }
41
+
42
+ /** The result of processing a single component through the manifest pipeline. */
43
+ export interface ProcessComponentResult {
44
+ component: ComponentInfoWithCss
45
+ warnings: ExtractionWarning[]
46
+ }
47
+
48
+ // ─────────────────────────────────────────────────────────────────────────────
49
+ // Component Processing
50
+ // ─────────────────────────────────────────────────────────────────────────────
51
+
52
+ /**
53
+ * Processes a single component through the full manifest pipeline:
54
+ * loading, rendering with prop tracking, CSS extraction, and selector matching.
55
+ *
56
+ * Returns the enriched component info alongside any non-fatal warnings
57
+ * encountered during processing. Always returns a component, falling back
58
+ * to minimal info (without DOM coupling) when rendering fails.
59
+ */
60
+ export function processComponent(
61
+ componentInfo: ComponentInfo,
62
+ loadComponent: (componentName: string) => ComponentType<unknown> | null,
63
+ cssImportPaths: string[],
64
+ loaderHasError?: boolean,
65
+ ): ProcessComponentResult {
66
+ const warnings: ExtractionWarning[] = []
67
+
68
+ // Load the actual component
69
+ let Component: ComponentType<unknown> | null = null
70
+ try {
71
+ Component = loadComponent(componentInfo.componentName)
72
+ if (!Component && !loaderHasError) {
73
+ warnings.push({
74
+ componentName: componentInfo.componentName,
75
+ phase: 'loader',
76
+ error: `Component "${componentInfo.componentName}" not found in package exports`,
77
+ })
78
+ }
79
+ } catch (error) {
80
+ if (!loaderHasError) {
81
+ warnings.push({
82
+ componentName: componentInfo.componentName,
83
+ phase: 'loader',
84
+ error: `Failed to load "${componentInfo.componentName}": ${error instanceof Error ? error.message : String(error)}`,
85
+ })
86
+ }
87
+ }
88
+
89
+ let coupledInfo: CoupledComponentInfo | null = null
90
+ let enhancedInfo: CoupledComponentInfo
91
+ let html: string | undefined
92
+ let extractedElements: ExtractedElement[] = []
93
+
94
+ // Render component with mock props and track prop flow (can fail)
95
+ if (Component) {
96
+ try {
97
+ const { extractor: propTracker, state } = createPropTrackerExtractor()
98
+ const cssExtractor = createCssPropertiesExtractor()
99
+
100
+ const result = runExtractors(componentInfo, Component, [propTracker, cssExtractor])
101
+ html = result.html
102
+ extractedElements = result.elements
103
+
104
+ const { props: coupledProps, innerElementProps } = buildCoupledProps(componentInfo, state.stores)
105
+ coupledInfo = {
106
+ componentName: componentInfo.componentName,
107
+ props: coupledProps,
108
+ elements: convertElements(extractedElements),
109
+ innerElementProps: innerElementProps.size > 0 ? innerElementProps : undefined,
110
+ }
111
+ } catch (error) {
112
+ warnings.push({
113
+ componentName: componentInfo.componentName,
114
+ phase: 'render',
115
+ error: error instanceof Error ? error.message : String(error),
116
+ })
117
+ }
118
+ }
119
+
120
+ // If rendering succeeded, use the coupled info directly
121
+ // (CSS properties are now embedded via css-properties-extractor)
122
+ if (coupledInfo) {
123
+ enhancedInfo = coupledInfo
124
+ } else {
125
+ // Fallback: create minimal info without DOM coupling
126
+ enhancedInfo = {
127
+ componentName: componentInfo.componentName,
128
+ props: Object.fromEntries(
129
+ Object.entries(componentInfo.props).map(([name, info]) => [name, { ...info, bindings: [], logicOnly: false }]),
130
+ ),
131
+ elements: [],
132
+ }
133
+ }
134
+
135
+ // Read and parse CSS imports
136
+ const { cssInfos: css, warnings: cssWarnings } = extractCssInfo(cssImportPaths, componentInfo.componentName)
137
+ warnings.push(...cssWarnings)
138
+
139
+ // Match CSS selectors to elements
140
+ if (html && extractedElements.length > 0 && css.length > 0) {
141
+ try {
142
+ const enrichedElements = matchCssSelectors(html, extractedElements, css)
143
+ enhancedInfo = {
144
+ ...enhancedInfo,
145
+ elements: convertElements(enrichedElements),
146
+ }
147
+ } catch (error) {
148
+ warnings.push({
149
+ componentName: componentInfo.componentName,
150
+ phase: 'css',
151
+ error: `CSS selector matching failed: ${error instanceof Error ? error.message : String(error)}`,
152
+ })
153
+ }
154
+ }
155
+
156
+ return {
157
+ component: {
158
+ ...enhancedInfo,
159
+ css,
160
+ },
161
+ warnings,
162
+ }
163
+ }
164
+
165
+ // ─────────────────────────────────────────────────────────────────────────────
166
+ // Prop Coupling Helpers
167
+ // ─────────────────────────────────────────────────────────────────────────────
168
+
169
+ function buildCoupledProps(
170
+ componentInfo: ComponentInfo,
171
+ stores: TrackingStores,
172
+ ): { props: Record<string, CoupledProp>; innerElementProps: Map<string, Record<string, CoupledProp>> } {
173
+ const result: Record<string, CoupledProp> = {}
174
+
175
+ for (const [name, info] of Object.entries(componentInfo.props)) {
176
+ const path = `props.${name}`
177
+ const wasRead = stores.reads.has(path)
178
+ const writeInfo = stores.writes.get(path)
179
+
180
+ result[name] = {
181
+ ...info,
182
+ logicOnly: wasRead && !writeInfo,
183
+ bindings: writeInfo ? extractBindings(writeInfo) : [],
184
+ }
185
+ }
186
+
187
+ const innerElementProps = processElementPropsWrites(componentInfo, stores)
188
+
189
+ return { props: result, innerElementProps }
190
+ }
191
+
192
+ const ELEMENT_PROPS_PREFIX = 'props.elementProps.'
193
+
194
+ /**
195
+ * Processes stores.writes entries with paths starting with "props.elementProps."
196
+ * to extract inner element prop bindings grouped by elementId (traceId).
197
+ */
198
+ function processElementPropsWrites(
199
+ componentInfo: ComponentInfo,
200
+ stores: TrackingStores,
201
+ ): Map<string, Record<string, CoupledProp>> {
202
+ const result = new Map<string, Record<string, CoupledProp>>()
203
+
204
+ const elementPropsInfo = componentInfo.props.elementProps
205
+ if (!elementPropsInfo) return result
206
+
207
+ for (const [path, writeInfo] of stores.writes) {
208
+ if (!path.startsWith(ELEMENT_PROPS_PREFIX)) continue
209
+
210
+ // Resolve the PropInfo for this leaf prop by walking the type tree
211
+ const relativePath = path.slice(ELEMENT_PROPS_PREFIX.length) // e.g. "navbar.items"
212
+ const propInfo = resolveInnerPropInfo(elementPropsInfo, relativePath)
213
+ if (!propInfo) continue
214
+
215
+ const bindings = extractBindings(writeInfo)
216
+
217
+ // Group by elementId from the bindings
218
+ for (const binding of bindings) {
219
+ const { elementId } = binding
220
+ let propsForElement = result.get(elementId)
221
+ if (!propsForElement) {
222
+ propsForElement = {}
223
+ result.set(elementId, propsForElement)
224
+ }
225
+
226
+ const leafName = propInfo.name
227
+ if (!propsForElement[leafName]) {
228
+ propsForElement[leafName] = {
229
+ ...propInfo,
230
+ logicOnly: false,
231
+ bindings: [],
232
+ }
233
+ }
234
+ propsForElement[leafName].bindings.push(binding)
235
+ }
236
+ }
237
+
238
+ return result
239
+ }
240
+
241
+ /**
242
+ * Walks the elementProps resolved type tree by path segments to find the leaf PropInfo.
243
+ * e.g., path "navbar.items" -> walk elementProps type -> navbar property -> items property
244
+ * Handles recursive nesting: "hamburgerMenu.elementProps.hamburgerCloseButton.label"
245
+ */
246
+ function resolveInnerPropInfo(elementPropsPropInfo: PropInfo, relativePath: string): PropInfo | null {
247
+ const segments = relativePath.split('.')
248
+ let currentType = elementPropsPropInfo.resolvedType
249
+
250
+ for (let i = 0; i < segments.length; i++) {
251
+ const segment = segments[i]
252
+ if (!currentType.properties) return null
253
+
254
+ const propInfo = currentType.properties[segment]
255
+ if (!propInfo) return null
256
+
257
+ // If this is the last segment, return the PropInfo
258
+ if (i === segments.length - 1) {
259
+ return propInfo
260
+ }
261
+
262
+ // Otherwise, descend into this property's resolved type
263
+ currentType = propInfo.resolvedType
264
+ }
265
+
266
+ return null
267
+ }
268
+
269
+ function extractBindings(writeInfo: TrackingStores['writes'] extends Map<string, infer V> ? V : never): DOMBinding[] {
270
+ const bindings: DOMBinding[] = []
271
+
272
+ for (const [key, attrInfo] of writeInfo.attributes) {
273
+ const elementId = key.split(':')[0]
274
+ const elementInfo = writeInfo.elements.get(elementId)
275
+ if (elementInfo) {
276
+ bindings.push({
277
+ element: elementInfo.tag,
278
+ attribute: attrInfo.attr,
279
+ concatenated: attrInfo.concatenated,
280
+ elementId: elementInfo.elementId,
281
+ })
282
+ }
283
+ }
284
+
285
+ return bindings
286
+ }
287
+
288
+ function convertElements(elements: ExtractedElement[]): CoupledComponentInfo['elements'] {
289
+ return elements.map((el) => {
290
+ // Get prop-tracker data for boundProps
291
+ const propTrackerData = el.extractorData.get('prop-tracker') as PropTrackerData | undefined
292
+
293
+ return {
294
+ traceId: el.traceId,
295
+ name: el.name,
296
+ tag: el.tag,
297
+ attributes: el.attributes,
298
+ extractorData: el.extractorData,
299
+ children: convertElements(el.children),
300
+ // Legacy fields
301
+ boundProps: propTrackerData?.boundProps ?? [],
302
+ role: propTrackerData?.role,
303
+ hasTextContent: el.hasTextContent,
304
+ }
305
+ })
306
+ }
307
+
308
+ // ─────────────────────────────────────────────────────────────────────────────
309
+ // CSS Extraction
310
+ // ─────────────────────────────────────────────────────────────────────────────
311
+
312
+ /**
313
+ * Reads and parses CSS files, extracting standard and custom properties.
314
+ * Returns the parsed CSS info alongside any warnings from files that failed to parse.
315
+ */
316
+ function extractCssInfo(
317
+ cssImportPaths: string[],
318
+ componentName: string,
319
+ ): { cssInfos: ExtractedCssInfo[]; warnings: ExtractionWarning[] } {
320
+ const cssInfos: ExtractedCssInfo[] = []
321
+ const warnings: ExtractionWarning[] = []
322
+
323
+ for (const cssPath of cssImportPaths) {
324
+ try {
325
+ // Read CSS file
326
+ const cssContent = fs.readFileSync(cssPath, 'utf-8')
327
+
328
+ // Parse CSS
329
+ const api = parseCss(cssContent)
330
+ const allProps = api.getAllProperties()
331
+
332
+ // Extract regular properties and custom properties (CSS variables)
333
+ const properties = new Map<string, string>()
334
+ const customProperties = new Map<string, string>()
335
+
336
+ for (const [, props] of allProps) {
337
+ for (const prop of props) {
338
+ if (prop.name.startsWith('--')) {
339
+ customProperties.set(prop.name, prop.value)
340
+ } else {
341
+ properties.set(prop.name, prop.value)
342
+ }
343
+ }
344
+ }
345
+
346
+ cssInfos.push({
347
+ filePath: cssPath,
348
+ api,
349
+ properties,
350
+ customProperties,
351
+ })
352
+ } catch (error) {
353
+ warnings.push({
354
+ componentName,
355
+ phase: 'css',
356
+ error: `Failed to parse ${cssPath}: ${error instanceof Error ? error.message : String(error)}`,
357
+ })
358
+ }
359
+ }
360
+
361
+ return { cssInfos, warnings }
362
+ }
package/src/schema.ts ADDED
@@ -0,0 +1,174 @@
1
+ // ─────────────────────────────────────────────────────────────────────────────
2
+ // Constants
3
+ // Extracted verbatim from @wix/component-protocol (chunk-U3HQJA7D.js).
4
+ // ─────────────────────────────────────────────────────────────────────────────
5
+
6
+ const DATA_TYPE = {
7
+ UNKNOWN_DataType: 'UNKNOWN_DataType',
8
+ text: 'text',
9
+ textEnum: 'textEnum',
10
+ number: 'number',
11
+ booleanValue: 'booleanValue',
12
+ a11y: 'a11y',
13
+ link: 'link',
14
+ image: 'image',
15
+ video: 'video',
16
+ vectorArt: 'vectorArt',
17
+ audio: 'audio',
18
+ schema: 'schema',
19
+ localDate: 'localDate',
20
+ localTime: 'localTime',
21
+ localDateTime: 'localDateTime',
22
+ webUrl: 'webUrl',
23
+ email: 'email',
24
+ phone: 'phone',
25
+ hostname: 'hostname',
26
+ regex: 'regex',
27
+ guid: 'guid',
28
+ richText: 'richText',
29
+ container: 'container',
30
+ arrayItems: 'arrayItems',
31
+ direction: 'direction',
32
+ menuItems: 'menuItems',
33
+ data: 'data',
34
+ function: 'function',
35
+ onClick: 'onClick',
36
+ onChange: 'onChange',
37
+ onKeyPress: 'onKeyPress',
38
+ onKeyUp: 'onKeyUp',
39
+ onSubmit: 'onSubmit',
40
+ } as const
41
+
42
+ const A11Y_ATTRIBUTES = {
43
+ Unknown_AriaAttributes: 'Unknown_AriaAttributes',
44
+ tabIndex: 'tabIndex',
45
+ ariaLevel: 'ariaLevel',
46
+ ariaExpanded: 'ariaExpanded',
47
+ ariaDisabled: 'ariaDisabled',
48
+ ariaAtomic: 'ariaAtomic',
49
+ ariaHidden: 'ariaHidden',
50
+ ariaBusy: 'ariaBusy',
51
+ multiline: 'multiline',
52
+ ariaAutocomplete: 'ariaAutocomplete',
53
+ ariaPressed: 'ariaPressed',
54
+ ariaHaspopup: 'ariaHaspopup',
55
+ ariaRelevant: 'ariaRelevant',
56
+ role: 'role',
57
+ ariaLive: 'ariaLive',
58
+ ariaCurrent: 'ariaCurrent',
59
+ ariaLabel: 'ariaLabel',
60
+ ariaRoledescription: 'ariaRoledescription',
61
+ ariaDescribedby: 'ariaDescribedby',
62
+ ariaLabelledby: 'ariaLabelledby',
63
+ ariaErrormessage: 'ariaErrormessage',
64
+ ariaOwns: 'ariaOwns',
65
+ ariaControls: 'ariaControls',
66
+ tag: 'tag',
67
+ ariaMultiline: 'ariaMultiline',
68
+ ariaInvalid: 'ariaInvalid',
69
+ } as const
70
+
71
+ const LINK_TYPE = {
72
+ UNKNOWN_LinkType: 'UNKNOWN_LinkType',
73
+ externalLink: 'externalLink',
74
+ anchorLink: 'anchorLink',
75
+ emailLink: 'emailLink',
76
+ phoneLink: 'phoneLink',
77
+ dynamicPageLink: 'dynamicPageLink',
78
+ pageLink: 'pageLink',
79
+ whatsAppLink: 'whatsAppLink',
80
+ documentLink: 'documentLink',
81
+ popupLink: 'popupLink',
82
+ addressLink: 'addressLink',
83
+ edgeAnchorLinks: 'edgeAnchorLinks',
84
+ loginToWixLink: 'loginToWixLink',
85
+ } as const
86
+
87
+ const IMAGE_CATEGORY = {
88
+ UNKNOWN_CategoryName: 'UNKNOWN_CategoryName',
89
+ IMAGE: 'IMAGE',
90
+ IMAGE_BACKGROUND: 'IMAGE_BACKGROUND',
91
+ } as const
92
+
93
+ const VIDEO_CATEGORY = {
94
+ UNKNOWN_VideoCategoryTypes: 'UNKNOWN_VideoCategoryTypes',
95
+ VIDEO: 'VIDEO',
96
+ VIDEO_TRANSPARENT: 'VIDEO_TRANSPARENT',
97
+ VIDEO_OPAQUE: 'VIDEO_OPAQUE',
98
+ } as const
99
+
100
+ const VECTOR_ART_CATEGORY = {
101
+ UNKNOWN_VectorArtCategoryTypes: 'UNKNOWN_VectorArtCategoryTypes',
102
+ SHAPE_ALL: 'SHAPE_ALL',
103
+ SHAPE_BASIC: 'SHAPE_BASIC',
104
+ SHAPE_ART: 'SHAPE_ART',
105
+ ICON_SOCIAL: 'ICON_SOCIAL',
106
+ SHAPE_DIVIDERS: 'SHAPE_DIVIDERS',
107
+ SHAPE_LOCATION: 'SHAPE_LOCATION',
108
+ SHAPE_DOCUMENTS: 'SHAPE_DOCUMENTS',
109
+ SHAPE_SOCIAL: 'SHAPE_SOCIAL',
110
+ SHAPE_ARROWS: 'SHAPE_ARROWS',
111
+ } as const
112
+
113
+ const ELEMENT_TYPE = {
114
+ UNKNOWN_ElementType: 'UNKNOWN_ElementType',
115
+ inlineElement: 'inlineElement',
116
+ refElement: 'refElement',
117
+ } as const
118
+
119
+ export const DATA = { DATA_TYPE, A11Y_ATTRIBUTES, LINK_TYPE }
120
+ export const MEDIA = { VIDEO_CATEGORY, VECTOR_ART_CATEGORY, IMAGE_CATEGORY }
121
+ export const ELEMENTS = { ELEMENT_TYPE }
122
+
123
+ // ─────────────────────────────────────────────────────────────────────────────
124
+ // Types
125
+ // Minimal stubs matching only the properties accessed in this codebase.
126
+ // ─────────────────────────────────────────────────────────────────────────────
127
+
128
+ export interface DataItem {
129
+ displayName?: string
130
+ defaultValue?: unknown
131
+ dataType?: string
132
+ text?: Record<string, unknown>
133
+ number?: Record<string, unknown>
134
+ booleanValue?: unknown
135
+ textEnum?: { options: Array<{ value: string; displayName?: string }> }
136
+ arrayItems?: { dataItem?: DataItem }
137
+ data?: { items?: Record<string, DataItem> }
138
+ function?: Record<string, unknown>
139
+ link?: { linkTypes?: string[] }
140
+ image?: { category?: string }
141
+ }
142
+
143
+ export interface CssPropertyItem {
144
+ defaultValue?: string
145
+ }
146
+
147
+ export interface CssCustomPropertyItem {
148
+ defaultValue?: string
149
+ }
150
+
151
+ export interface EditorElement {
152
+ selector?: string
153
+ displayName?: string
154
+ data?: Record<string, DataItem>
155
+ elements?: Record<string, ElementItem>
156
+ cssProperties?: Record<string, CssPropertyItem>
157
+ cssCustomProperties?: Record<string, CssCustomPropertyItem>
158
+ }
159
+
160
+ export interface ElementItem {
161
+ elementType?: string
162
+ inlineElement?: {
163
+ selector?: string
164
+ displayName?: string
165
+ data?: Record<string, DataItem>
166
+ cssProperties?: Record<string, CssPropertyItem>
167
+ cssCustomProperties?: Record<string, CssCustomPropertyItem>
168
+ elements?: Record<string, ElementItem>
169
+ }
170
+ }
171
+
172
+ export interface EditorReactComponent {
173
+ editorElement?: EditorElement
174
+ }
@@ -0,0 +1,41 @@
1
+ import * as fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import { ResultAsync, errAsync } from 'neverthrow'
4
+ import { parseNative } from 'tsconfck'
5
+ import ts from 'typescript'
6
+ import { NotFoundError, ParseError } from './errors'
7
+
8
+ /**
9
+ * Compile a TypeScript file into a ts.Program.
10
+ *
11
+ * @param filePath - Path to the TypeScript source file
12
+ * @returns The compiled TypeScript program on success
13
+ * @errors
14
+ * - {@link NotFoundError} — Source file does not exist (phase: `compile`)
15
+ * - {@link ParseError} — TypeScript config could not be parsed (phase: `compile`)
16
+ */
17
+ export function compileTsFile(
18
+ filePath: string,
19
+ ): ResultAsync<ts.Program, InstanceType<typeof NotFoundError> | InstanceType<typeof ParseError>> {
20
+ const resolvedPath = path.resolve(filePath)
21
+
22
+ if (!fs.existsSync(resolvedPath)) {
23
+ return errAsync(new NotFoundError(`File not found: ${resolvedPath}`, { props: { phase: 'compile' } }))
24
+ }
25
+
26
+ return ResultAsync.fromPromise(
27
+ parseNative(resolvedPath),
28
+ (error) =>
29
+ new ParseError(`Failed to parse TypeScript config for ${resolvedPath}`, {
30
+ cause: error as Error,
31
+ props: { phase: 'compile' },
32
+ }),
33
+ ).map(({ tsconfig, tsconfigFile }) => {
34
+ // Parse the JSON config through TypeScript's API to handle inheritance,
35
+ // convert string values (like "ES2020") to enum values, and process extends
36
+ const configDir = path.dirname(tsconfigFile)
37
+ const parsedConfig = ts.parseJsonConfigFileContent(tsconfig, ts.sys, configDir)
38
+
39
+ return ts.createProgram([filePath], parsedConfig.options)
40
+ })
41
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "ESNext",
5
+ "moduleResolution": "Bundler",
6
+ "lib": ["ES2020"],
7
+ "outDir": "./dist",
8
+ "rootDir": "./src",
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "declaration": true
14
+ },
15
+ "include": ["src/**/*"],
16
+ "exclude": ["node_modules", "dist"]
17
+ }
package/typedoc.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "entryPoints": [
3
+ "./src"
4
+ ],
5
+ "entryPointStrategy": "expand",
6
+ "out": "docs-audit",
7
+ "plugin": [
8
+ "typedoc-plugin-markdown"
9
+ ],
10
+ "readme": "none",
11
+ "githubPages": false,
12
+ "disableSources": true,
13
+ "cleanOutputDir": true,
14
+ "excludePrivate": false,
15
+ "excludeProtected": false,
16
+ "excludeInternal": false,
17
+ "excludeExternals": false
18
+ }
package/vite.config.ts ADDED
@@ -0,0 +1,45 @@
1
+ import { builtinModules } from 'node:module'
2
+ import { resolve } from 'node:path'
3
+ import { defineConfig } from 'vite'
4
+ import dts from 'vite-plugin-dts'
5
+
6
+ export default defineConfig({
7
+ plugins: [
8
+ dts({
9
+ include: ['src/**/*.ts'],
10
+ exclude: ['**/*.test.ts'],
11
+ }),
12
+ ],
13
+ build: {
14
+ lib: {
15
+ entry: {
16
+ index: resolve(__dirname, 'src/index.ts'),
17
+ 'jsx-runtime-interceptor': resolve(__dirname, 'src/jsx-runtime-interceptor.ts'),
18
+ 'jsx-runtime-loader': resolve(__dirname, 'src/jsx-runtime-loader.ts'),
19
+ },
20
+ formats: ['es'],
21
+ },
22
+ rollupOptions: {
23
+ external: (id) => {
24
+ // Externalize Node.js built-ins (both node: prefixed and bare), typescript (peer dep), and lightningcss (native bindings)
25
+ if (id.startsWith('node:') || builtinModules.includes(id) || ['typescript', 'lightningcss'].includes(id)) {
26
+ return true
27
+ }
28
+ return false
29
+ },
30
+ },
31
+ outDir: 'dist',
32
+ },
33
+ resolve: {
34
+ // Ensure Node.js-specific export conditions are used when bundling for Node
35
+ conditions: ['node', 'import', 'module', 'default'],
36
+ alias: {
37
+ // Alias jsx-runtime to our interceptable version for tests
38
+ 'react/jsx-runtime': resolve(__dirname, 'src/jsx-runtime-interceptor.ts'),
39
+ 'react/jsx-dev-runtime': resolve(__dirname, 'src/jsx-runtime-interceptor.ts'),
40
+ },
41
+ },
42
+ test: {
43
+ globals: true,
44
+ },
45
+ })