@wix/zero-config-implementation 1.7.0 → 1.9.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 (26) hide show
  1. package/dist/converters/data-item-builder.d.ts +4 -1
  2. package/dist/index.d.ts +5 -3
  3. package/dist/index.js +22432 -19408
  4. package/dist/information-extractors/react/extractors/core/index.d.ts +1 -1
  5. package/dist/information-extractors/react/extractors/core/runner.d.ts +5 -1
  6. package/dist/information-extractors/react/extractors/index.d.ts +1 -1
  7. package/dist/information-extractors/react/extractors/prop-tracker.d.ts +3 -6
  8. package/dist/information-extractors/react/index.d.ts +2 -3
  9. package/dist/information-extractors/react/types.d.ts +4 -17
  10. package/dist/information-extractors/react/utils/mock-generator.d.ts +13 -2
  11. package/dist/manifest-pipeline.d.ts +2 -2
  12. package/package.json +2 -2
  13. package/src/converters/data-item-builder.ts +120 -35
  14. package/src/converters/to-editor-component.ts +11 -6
  15. package/src/index.ts +5 -3
  16. package/src/information-extractors/react/extractors/core/index.ts +1 -1
  17. package/src/information-extractors/react/extractors/core/runner.ts +12 -7
  18. package/src/information-extractors/react/extractors/core/tree-builder.ts +3 -1
  19. package/src/information-extractors/react/extractors/index.ts +1 -0
  20. package/src/information-extractors/react/extractors/prop-tracker.ts +49 -28
  21. package/src/information-extractors/react/index.ts +1 -7
  22. package/src/information-extractors/react/types.ts +4 -20
  23. package/src/information-extractors/react/utils/mock-generator.ts +99 -31
  24. package/src/manifest-pipeline.ts +18 -12
  25. package/dist/information-extractors/react/utils/prop-spy.d.ts +0 -10
  26. package/src/information-extractors/react/utils/prop-spy.ts +0 -168
@@ -1,6 +1,6 @@
1
1
  export { ExtractorStore } from './store';
2
2
  export { runExtractors } from './runner';
3
- export type { ExtractionResult } from './runner';
3
+ export type { ExtractionResult, RunExtractorsOptions } from './runner';
4
4
  export { buildElementTree } from './tree-builder';
5
5
  export type { ExtractedElement } from './tree-builder';
6
6
  export type { ReactExtractor, RenderContext, CreateElementEvent, RenderCompleteEvent } from './types';
@@ -8,6 +8,10 @@ export interface ExtractionResult {
8
8
  store: ExtractorStore;
9
9
  elements: ExtractedElement[];
10
10
  }
11
+ export interface RunExtractorsOptions {
12
+ /** Optional HOC to wrap the component before rendering (e.g. a context provider). */
13
+ wrapper?: (component: ComponentType<unknown>) => ComponentType<unknown>;
14
+ }
11
15
  /**
12
16
  * Runs extractors through the full lifecycle and returns extraction results.
13
17
  *
@@ -16,4 +20,4 @@ export interface ExtractionResult {
16
20
  * @param extractors - Array of extractors to run
17
21
  * @returns Extraction results including HTML, store, and element tree
18
22
  */
19
- export declare function runExtractors(componentInfo: ComponentInfo, component: ComponentType<unknown>, extractors: ReactExtractor[]): ExtractionResult;
23
+ export declare function runExtractors(componentInfo: ComponentInfo, component: ComponentType<unknown>, extractors: ReactExtractor[], options?: RunExtractorsOptions): ExtractionResult;
@@ -4,7 +4,7 @@
4
4
  * Core infrastructure and pluggable extractors for the React information extraction system.
5
5
  */
6
6
  export { ExtractorStore, runExtractors, buildElementTree, } from './core';
7
- export type { ExtractionResult, ExtractedElement, ReactExtractor, RenderContext, CreateElementEvent, RenderCompleteEvent, } from './core';
7
+ export type { ExtractionResult, RunExtractorsOptions, ExtractedElement, ReactExtractor, RenderContext, CreateElementEvent, RenderCompleteEvent, } from './core';
8
8
  export { createPropTrackerExtractor } from './prop-tracker';
9
9
  export type { PropTrackerData, PropTrackerExtractorState } from './prop-tracker';
10
10
  export { createCssPropertiesExtractor } from './css-properties';
@@ -1,7 +1,5 @@
1
- import { PropSpyMeta, TrackingStores } from '../types';
2
- import { PropSpyContext } from '../utils/prop-spy';
1
+ import { TrackingStores } from '../types';
3
2
  import { ReactExtractor } from './core/types';
4
- export type GetSpyMetadataFn = (id: string) => PropSpyMeta | null;
5
3
  export interface PropTrackerData {
6
4
  tag: string;
7
5
  role?: string;
@@ -10,13 +8,12 @@ export interface PropTrackerData {
10
8
  }
11
9
  export interface PropTrackerExtractorState {
12
10
  stores: TrackingStores;
13
- spyContext: PropSpyContext;
14
11
  }
15
12
  /**
16
13
  * Creates a prop tracker extractor that:
17
- * 1. Wraps props with spy proxies during beforeRender
14
+ * 1. Generates spy-instrumented mock props during beforeRender
18
15
  * 2. Detects spy markers in element props during onCreateElement
19
- * 3. Writes tracking data to the store namespaced by 'prop-tracker'
16
+ * 3. propUsages tracking data to the store namespaced by 'prop-tracker'
20
17
  */
21
18
  export declare function createPropTrackerExtractor(): {
22
19
  extractor: ReactExtractor;
@@ -4,6 +4,5 @@
4
4
  * API: runExtractors() with pluggable extractors
5
5
  */
6
6
  export { ExtractorStore, runExtractors, buildElementTree, createPropTrackerExtractor, createCssPropertiesExtractor, } from './extractors';
7
- export type { ExtractionResult, ExtractedElement, ReactExtractor, RenderContext, CreateElementEvent, RenderCompleteEvent, PropTrackerData, PropTrackerExtractorState, CssPropertiesData, } from './extractors';
8
- export type { CoupledComponentInfo, CoupledProp, TrackingStores, DOMBinding, PropReadInfo, PropWriteInfo, PropSpyMeta, } from './types';
9
- export type { PropSpyContext } from './utils/prop-spy';
7
+ export type { ExtractionResult, RunExtractorsOptions, ExtractedElement, ReactExtractor, RenderContext, CreateElementEvent, RenderCompleteEvent, PropTrackerData, PropTrackerExtractorState, CssPropertiesData, } from './extractors';
8
+ export type { CoupledComponentInfo, CoupledProp, TrackingStores, DOMBinding, PropWriteInfo, PropSpyMeta, } from './types';
@@ -1,24 +1,10 @@
1
1
  import { PropInfo } from '../ts/types';
2
2
  import { ExtractedElement } from './extractors/core/tree-builder';
3
- export declare const PROP_SPY_SYMBOL: unique symbol;
4
3
  export interface PropSpyMeta {
5
4
  path: string;
6
5
  propName: string;
7
- uniqueId: string;
8
6
  originalValue: unknown;
9
7
  }
10
- export interface PropSpy<T = unknown> {
11
- [PROP_SPY_SYMBOL]: true;
12
- __meta: PropSpyMeta;
13
- valueOf: () => T;
14
- toString: () => string;
15
- toJSON: () => T;
16
- [Symbol.toPrimitive]?: (hint: string) => T | string | number;
17
- }
18
- export interface PropReadInfo {
19
- components: Set<string>;
20
- value: unknown;
21
- }
22
8
  export interface PropWriteInfo {
23
9
  elements: Map<string, {
24
10
  tag: string;
@@ -30,8 +16,7 @@ export interface PropWriteInfo {
30
16
  }>;
31
17
  }
32
18
  export interface TrackingStores {
33
- reads: Map<string, PropReadInfo>;
34
- writes: Map<string, PropWriteInfo>;
19
+ propUsages: Map<string, PropWriteInfo>;
35
20
  }
36
21
  export interface DOMBinding {
37
22
  element: string;
@@ -40,7 +25,8 @@ export interface DOMBinding {
40
25
  elementId: string;
41
26
  }
42
27
  export interface CoupledProp extends PropInfo {
43
- bindings: DOMBinding[];
28
+ /** Full stores.propUsages key for this prop, e.g. "props.linkUrl". */
29
+ propPath: string;
44
30
  logicOnly: boolean;
45
31
  }
46
32
  export interface CoupledComponentInfo {
@@ -48,4 +34,5 @@ export interface CoupledComponentInfo {
48
34
  props: Record<string, CoupledProp>;
49
35
  elements: ExtractedElement[];
50
36
  innerElementProps?: Map<string, Record<string, CoupledProp>>;
37
+ propUsages: TrackingStores['propUsages'];
51
38
  }
@@ -4,6 +4,17 @@ import { ComponentInfo } from '../../ts/types';
4
4
  */
5
5
  export declare function resetMockCounter(): void;
6
6
  /**
7
- * Generate mock props object from ComponentInfo
7
+ * Narrow interface for registering spy markers during mock generation.
8
+ * Satisfied structurally by PropSpyContext.
8
9
  */
9
- export declare function generateMockProps(componentInfo: ComponentInfo): Record<string, unknown>;
10
+ export interface PropSpyRegistrar {
11
+ registerString(path: string, propName: string, value: string): void;
12
+ registerNumber(path: string, propName: string, value: number): void;
13
+ registerFunction(path: string, propName: string, value: (...args: unknown[]) => unknown): void;
14
+ }
15
+ /**
16
+ * Generate mock props object from ComponentInfo.
17
+ * When a registrar is provided, string and number values are spy-instrumented
18
+ * for DOM binding detection.
19
+ */
20
+ export declare function generateMockProps(componentInfo: ComponentInfo, registrar?: PropSpyRegistrar): Record<string, unknown>;
@@ -1,5 +1,5 @@
1
1
  import { ComponentInfo } from './information-extractors/ts';
2
- import { CoupledComponentInfo } from './information-extractors/react';
2
+ import { RunExtractorsOptions, CoupledComponentInfo } from './information-extractors/react';
3
3
  import { CSSParserAPI } from './information-extractors/css';
4
4
  import { ComponentType } from 'react';
5
5
  /** A non-fatal issue encountered during component processing. */
@@ -30,4 +30,4 @@ export interface ProcessComponentResult {
30
30
  * encountered during processing. Always returns a component, falling back
31
31
  * to minimal info (without DOM coupling) when rendering fails.
32
32
  */
33
- export declare function processComponent(componentInfo: ComponentInfo, loadComponent: (componentName: string) => ComponentType<unknown> | null, cssImportPaths: string[], loaderHasError?: boolean): ProcessComponentResult;
33
+ export declare function processComponent(componentInfo: ComponentInfo, loadComponent: (componentName: string) => ComponentType<unknown> | null, cssImportPaths: string[], loaderHasError?: boolean, options?: RunExtractorsOptions): ProcessComponentResult;
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.0",
7
+ "version": "1.9.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",
@@ -74,5 +74,5 @@
74
74
  ]
75
75
  }
76
76
  },
77
- "falconPackageHash": "95c3bb44ab2a1406f75946ef5d30cec9f4ce31059113151e31289d75"
77
+ "falconPackageHash": "ce9c2a83ea75ac5b0a6b67e4fd624304cdad011d081dfdf2d8c72b4b"
78
78
  }
@@ -15,6 +15,7 @@
15
15
  import type { Result } from 'neverthrow'
16
16
  import { err, ok } from 'neverthrow'
17
17
  import { ParseError } from '../errors'
18
+ import type { DOMBinding, TrackingStores } from '../information-extractors/react'
18
19
  import type { PropInfo, ResolvedType } from '../information-extractors/ts/types'
19
20
  import type { DataItem } from '../schema'
20
21
  import { DATA, MEDIA } from '../schema'
@@ -24,15 +25,42 @@ const { DATA_TYPE, WIX_TYPE_TO_DATA_TYPE } = DATA
24
25
 
25
26
  type ParseErrorInstance = InstanceType<typeof ParseError>
26
27
 
28
+ function getBindingsForPath(propUsages: TrackingStores['propUsages'], path: string): DOMBinding[] {
29
+ const writeInfo = propUsages.get(path)
30
+ if (!writeInfo) return []
31
+
32
+ const result: DOMBinding[] = []
33
+ for (const [key, attrInfo] of writeInfo.attributes) {
34
+ const elementId = key.split(':')[0]
35
+ const elementInfo = writeInfo.elements.get(elementId)
36
+ if (elementInfo) {
37
+ result.push({
38
+ element: elementInfo.tag,
39
+ attribute: attrInfo.attr,
40
+ concatenated: attrInfo.concatenated,
41
+ elementId: elementInfo.elementId,
42
+ })
43
+ }
44
+ }
45
+ return result
46
+ }
47
+
27
48
  /**
28
49
  * Converts a single PropInfo to a DataItem for the Wix Editor component schema.
29
50
  *
30
51
  * @param propInfo - The resolved TypeScript prop information to convert.
31
52
  * @param defaultValue - Optional default value for the data item.
53
+ * @param propUsages - The full stores.propUsages map for on-demand binding lookups.
54
+ * @param propPath - The full stores.propUsages key for this prop, e.g. "props.linkUrl".
32
55
  * @returns `Ok<DataItem>` on success, `Err<ParseError>` if an invariant is violated
33
56
  * (e.g. an array type missing its element type).
34
57
  */
35
- export function buildDataItem(propInfo: PropInfo, defaultValue?: unknown): Result<DataItem, ParseErrorInstance> {
58
+ export function buildDataItem(
59
+ propInfo: PropInfo,
60
+ defaultValue?: unknown,
61
+ propUsages?: TrackingStores['propUsages'],
62
+ propPath?: string,
63
+ ): Result<DataItem, ParseErrorInstance> {
36
64
  const dataItem: DataItem = {
37
65
  displayName: formatDisplayName(propInfo.name),
38
66
  }
@@ -41,7 +69,7 @@ export function buildDataItem(propInfo: PropInfo, defaultValue?: unknown): Resul
41
69
  dataItem.defaultValue = defaultValue
42
70
  }
43
71
 
44
- const result = applyResolvedTypeToDataItem(dataItem, propInfo.resolvedType, propInfo)
72
+ const result = applyResolvedTypeToDataItem(dataItem, propInfo.resolvedType, propInfo, propUsages, propPath)
45
73
  if (result.isErr()) {
46
74
  return err(result.error)
47
75
  }
@@ -59,10 +87,14 @@ function applyResolvedTypeToDataItem(
59
87
  dataItem: DataItem,
60
88
  resolvedType: ResolvedType,
61
89
  propInfo: PropInfo,
90
+ propUsages?: TrackingStores['propUsages'],
91
+ propPath?: string,
62
92
  ): Result<void, ParseErrorInstance> {
93
+ const bindings = propUsages && propPath ? getBindingsForPath(propUsages, propPath) : undefined
94
+
63
95
  switch (resolvedType.kind) {
64
96
  case 'primitive':
65
- handlePrimitiveType(dataItem, resolvedType, propInfo)
97
+ handlePrimitiveType(dataItem, resolvedType, propInfo, bindings)
66
98
  return ok(undefined)
67
99
 
68
100
  case 'literal':
@@ -74,16 +106,16 @@ function applyResolvedTypeToDataItem(
74
106
  return ok(undefined)
75
107
 
76
108
  case 'array':
77
- return handleArrayType(dataItem, resolvedType)
109
+ return handleArrayType(dataItem, resolvedType, propUsages, propPath)
78
110
 
79
111
  case 'object':
80
- return handleObjectType(dataItem, resolvedType)
112
+ return handleObjectType(dataItem, resolvedType, propUsages, propPath)
81
113
 
82
114
  case 'union':
83
115
  return handleUnionType(dataItem, resolvedType, propInfo)
84
116
 
85
117
  case 'function':
86
- handleFunctionType(dataItem, propInfo)
118
+ handleFunctionType(dataItem, propInfo, bindings)
87
119
  return ok(undefined)
88
120
 
89
121
  case 'semantic':
@@ -100,14 +132,34 @@ function applyResolvedTypeToDataItem(
100
132
 
101
133
  /**
102
134
  * Handles primitive types (string, number, boolean).
135
+ * When bindings are provided, uses bound DOM attributes to infer more specific types:
136
+ * - string bound to `dir` → direction
137
+ * - string bound to `href` on an anchor element (non-concatenated) → webUrl
138
+ * - string bound to `id` → guid
139
+ * - string bound to `pattern` → regex
103
140
  * Unrecognized primitives silently fall back to text.
104
141
  */
105
- function handlePrimitiveType(dataItem: DataItem, resolvedType: ResolvedType, propInfo: PropInfo): void {
142
+ function handlePrimitiveType(
143
+ dataItem: DataItem,
144
+ resolvedType: ResolvedType,
145
+ propInfo: PropInfo,
146
+ bindings?: DOMBinding[],
147
+ ): void {
106
148
  const typeValue = (resolvedType.value as string | undefined)?.toLowerCase() || propInfo.type.toLowerCase()
107
149
 
108
150
  if (typeValue.includes('string')) {
109
- dataItem.dataType = DATA_TYPE.text
110
- dataItem.text = {}
151
+ if (bindings?.some((b) => b.attribute === 'dir')) {
152
+ dataItem.dataType = DATA_TYPE.direction
153
+ } else if (bindings?.some((b) => b.attribute === 'href' && b.element === 'a' && !b.concatenated)) {
154
+ dataItem.dataType = DATA_TYPE.webUrl
155
+ } else if (bindings?.some((b) => b.attribute === 'id')) {
156
+ dataItem.dataType = DATA_TYPE.guid
157
+ } else if (bindings?.some((b) => b.attribute === 'pattern')) {
158
+ dataItem.dataType = DATA_TYPE.regex
159
+ } else {
160
+ dataItem.dataType = DATA_TYPE.text
161
+ dataItem.text = {}
162
+ }
111
163
  } else if (typeValue.includes('number')) {
112
164
  dataItem.dataType = DATA_TYPE.number
113
165
  dataItem.number = {}
@@ -141,8 +193,15 @@ function handleEnumType(dataItem: DataItem): void {
141
193
  /**
142
194
  * Handles array types. Fails with a `ParseError` if the resolved type
143
195
  * has kind 'array' but is missing its `elementType` (invariant violation).
196
+ * propPath is forwarded unchanged — getBindingsForPath strips numeric indices
197
+ * so `props.items.url` matches propUsages like `props.items[0].url`.
144
198
  */
145
- function handleArrayType(dataItem: DataItem, resolvedType: ResolvedType): Result<void, ParseErrorInstance> {
199
+ function handleArrayType(
200
+ dataItem: DataItem,
201
+ resolvedType: ResolvedType,
202
+ propUsages?: TrackingStores['propUsages'],
203
+ propPath?: string,
204
+ ): Result<void, ParseErrorInstance> {
146
205
  if (resolvedType.kind !== 'array' || resolvedType.elementType === undefined) {
147
206
  return err(
148
207
  new ParseError('Invalid array type: resolved type has kind "array" but is missing elementType', {
@@ -153,12 +212,18 @@ function handleArrayType(dataItem: DataItem, resolvedType: ResolvedType): Result
153
212
 
154
213
  dataItem.dataType = DATA_TYPE.arrayItems
155
214
  const elementDataItem: DataItem = {}
156
- const result = applyResolvedTypeToDataItem(elementDataItem, resolvedType.elementType, {
157
- name: 'element',
158
- required: false,
159
- type: resolvedType.elementType.kind,
160
- resolvedType: resolvedType.elementType,
161
- } satisfies PropInfo)
215
+ const result = applyResolvedTypeToDataItem(
216
+ elementDataItem,
217
+ resolvedType.elementType,
218
+ {
219
+ name: 'element',
220
+ required: false,
221
+ type: resolvedType.elementType.kind,
222
+ resolvedType: resolvedType.elementType,
223
+ } satisfies PropInfo,
224
+ propUsages,
225
+ propPath,
226
+ )
162
227
 
163
228
  if (result.isErr()) {
164
229
  return result
@@ -173,16 +238,23 @@ function handleArrayType(dataItem: DataItem, resolvedType: ResolvedType): Result
173
238
 
174
239
  /**
175
240
  * Handles object types with nested properties.
176
- * Recursively calls `buildDataItem` for each property.
241
+ * Recursively calls `buildDataItem` for each property, appending the property name
242
+ * to propPath so nested bindings can be looked up on-demand.
177
243
  */
178
- function handleObjectType(dataItem: DataItem, resolvedType: ResolvedType): Result<void, ParseErrorInstance> {
244
+ function handleObjectType(
245
+ dataItem: DataItem,
246
+ resolvedType: ResolvedType,
247
+ propUsages?: TrackingStores['propUsages'],
248
+ propPath?: string,
249
+ ): Result<void, ParseErrorInstance> {
179
250
  dataItem.dataType = DATA_TYPE.data
180
251
 
181
252
  if (resolvedType.properties) {
182
253
  const nestedItems: Record<string, DataItem> = {}
183
254
 
184
255
  for (const [propName, propInfo] of Object.entries(resolvedType.properties)) {
185
- const result = buildDataItem(propInfo, undefined)
256
+ const childPath = propPath ? `${propPath}.${propName}` : propName
257
+ const result = buildDataItem(propInfo, undefined, propUsages, childPath)
186
258
  if (result.isErr()) {
187
259
  return err(result.error)
188
260
  }
@@ -297,26 +369,39 @@ function handleSemanticType(dataItem: DataItem, resolvedType: ResolvedType): voi
297
369
  dataItem.text = {}
298
370
  }
299
371
 
372
+ const EVENT_HANDLER_ATTR_TO_DATA_TYPE: Record<string, string> = {
373
+ onclick: DATA_TYPE.onClick,
374
+ onchange: DATA_TYPE.onChange,
375
+ onkeypress: DATA_TYPE.onKeyPress,
376
+ onkeyup: DATA_TYPE.onKeyUp,
377
+ onsubmit: DATA_TYPE.onSubmit,
378
+ }
379
+
300
380
  /**
301
381
  * Handles function types — maps to event handlers.
382
+ * Checks bound DOM attributes first (e.g. `onChange` → onChange), then falls back to prop name.
302
383
  */
303
- function handleFunctionType(dataItem: DataItem, propInfo: PropInfo): void {
304
- const propName = propInfo.name.toLowerCase()
305
-
306
- if (propName === 'onclick') {
307
- dataItem.dataType = DATA_TYPE.onClick
308
- } else if (propName === 'onchange') {
309
- dataItem.dataType = DATA_TYPE.onChange
310
- } else if (propName === 'onkeypress') {
311
- dataItem.dataType = DATA_TYPE.onKeyPress
312
- } else if (propName === 'onkeyup') {
313
- dataItem.dataType = DATA_TYPE.onKeyUp
314
- } else if (propName === 'onsubmit') {
315
- dataItem.dataType = DATA_TYPE.onSubmit
316
- } else {
317
- dataItem.dataType = DATA_TYPE.function
318
- dataItem.function = {}
384
+ function handleFunctionType(dataItem: DataItem, propInfo: PropInfo, bindings?: DOMBinding[]): void {
385
+ // Check bound attributes first — they reflect actual DOM wiring regardless of prop name
386
+ if (bindings) {
387
+ for (const binding of bindings) {
388
+ const dataType = EVENT_HANDLER_ATTR_TO_DATA_TYPE[binding.attribute.toLowerCase()]
389
+ if (dataType) {
390
+ dataItem.dataType = dataType
391
+ return
392
+ }
393
+ }
319
394
  }
395
+
396
+ // Fall back to prop name matching
397
+ const dataType = EVENT_HANDLER_ATTR_TO_DATA_TYPE[propInfo.name.toLowerCase()]
398
+ if (dataType) {
399
+ dataItem.dataType = dataType
400
+ return
401
+ }
402
+
403
+ dataItem.dataType = DATA_TYPE.function
404
+ dataItem.function = {}
320
405
  }
321
406
 
322
407
  /**
@@ -5,6 +5,7 @@ import type {
5
5
  CoupledProp,
6
6
  CssPropertiesData,
7
7
  ExtractedElement,
8
+ TrackingStores,
8
9
  } from '../information-extractors/react'
9
10
  import type {
10
11
  CssCustomPropertyItem,
@@ -34,8 +35,8 @@ function buildEditorElement(component: ComponentInfoWithCss): EditorElement {
34
35
  return {
35
36
  selector: buildSelector(rootElement),
36
37
  displayName: formatDisplayName(component.componentName),
37
- data: buildData(component.props),
38
- elements: buildElements(childElements, component.innerElementProps),
38
+ data: buildData(component.props, component.propUsages),
39
+ elements: buildElements(childElements, component.innerElementProps, component.propUsages),
39
40
  cssProperties: buildCssProperties(rootElement),
40
41
  cssCustomProperties: buildCssCustomPropertiesForElement(rootElement),
41
42
  }
@@ -48,7 +49,10 @@ function buildSelector(rootElement?: ExtractedElement): string {
48
49
  return rootElement?.tag ?? ''
49
50
  }
50
51
 
51
- function buildData(props: Record<string, CoupledProp>): Record<string, DataItem> {
52
+ function buildData(
53
+ props: Record<string, CoupledProp>,
54
+ propUsages: TrackingStores['propUsages'],
55
+ ): Record<string, DataItem> {
52
56
  const data: Record<string, DataItem> = {}
53
57
 
54
58
  for (const [name, prop] of Object.entries(props)) {
@@ -58,7 +62,7 @@ function buildData(props: Record<string, CoupledProp>): Record<string, DataItem>
58
62
  // Extract default value for buildDataItem
59
63
  const defaultValue = prop.defaultValue?.kind !== 'unresolved' ? prop.defaultValue?.value : undefined
60
64
 
61
- const result = buildDataItem(prop, defaultValue)
65
+ const result = buildDataItem(prop, defaultValue, propUsages, prop.propPath)
62
66
  if (result.isOk()) {
63
67
  data[name] = result.value
64
68
  }
@@ -70,12 +74,13 @@ function buildData(props: Record<string, CoupledProp>): Record<string, DataItem>
70
74
  function buildElements(
71
75
  elements: ExtractedElement[],
72
76
  innerElementProps?: CoupledComponentInfo['innerElementProps'],
77
+ propUsages?: TrackingStores['propUsages'],
73
78
  ): Record<string, ElementItem> {
74
79
  const result: Record<string, ElementItem> = {}
75
80
 
76
81
  for (const el of elements) {
77
82
  const elementData = innerElementProps?.get(el.traceId)
78
- const data = elementData ? buildData(elementData) : undefined
83
+ const data = elementData && propUsages ? buildData(elementData, propUsages) : undefined
79
84
  const cssProps = buildCssProperties(el)
80
85
  const cssCustomProps = buildCssCustomPropertiesForElement(el)
81
86
 
@@ -91,7 +96,7 @@ function buildElements(
91
96
  // CSS custom properties from matched rules for this element
92
97
  ...(Object.keys(cssCustomProps).length > 0 && { cssCustomProperties: cssCustomProps }),
93
98
  // Recursively build nested elements
94
- elements: el.children.length > 0 ? buildElements(el.children, innerElementProps) : undefined,
99
+ elements: el.children.length > 0 ? buildElements(el.children, innerElementProps, propUsages) : undefined,
95
100
  },
96
101
  }
97
102
  }
package/src/index.ts CHANGED
@@ -13,6 +13,7 @@ import { type NotFoundError, ParseError } from './errors'
13
13
  // Component loader helpers
14
14
  import { findComponent, loadModule } from './module-loader'
15
15
 
16
+ import type { RunExtractorsOptions } from './information-extractors/react'
16
17
  // Pipeline orchestration
17
18
  import { processComponent } from './manifest-pipeline'
18
19
  export type {
@@ -36,6 +37,8 @@ export interface ManifestResult {
36
37
  errors: ExtractionError[]
37
38
  }
38
39
 
40
+ export type { RunExtractorsOptions } from './information-extractors/react'
41
+
39
42
  export interface ExtractionError {
40
43
  componentName: string
41
44
  phase: 'render' | 'coupling' | 'css' | 'loader' | 'conversion'
@@ -59,6 +62,7 @@ export interface ExtractionError {
59
62
  export function extractComponentManifest(
60
63
  componentPath: string,
61
64
  compiledEntryPath: string,
65
+ options?: RunExtractorsOptions,
62
66
  ): ResultAsync<ManifestResult, InstanceType<typeof NotFoundError> | InstanceType<typeof ParseError>> {
63
67
  // Step 1: Load the compiled package module (non-fatal)
64
68
  return loadModule(compiledEntryPath)
@@ -126,7 +130,7 @@ export function extractComponentManifest(
126
130
  }
127
131
 
128
132
  // Step 5: Process the default-exported component (non-fatal)
129
- const processResult = processComponent(componentInfo, loadComponent, cssImportPaths, !!loaderError)
133
+ const processResult = processComponent(componentInfo, loadComponent, cssImportPaths, !!loaderError, options)
130
134
  errors.push(...processResult.warnings)
131
135
  const component = toEditorReactComponent(processResult.component)
132
136
 
@@ -189,14 +193,12 @@ export type {
189
193
  PropTrackerData,
190
194
  PropTrackerExtractorState,
191
195
  CssPropertiesData,
192
- PropSpyContext,
193
196
  } from './information-extractors/react'
194
197
  export type {
195
198
  CoupledComponentInfo,
196
199
  CoupledProp,
197
200
  DOMBinding,
198
201
  TrackingStores,
199
- PropReadInfo,
200
202
  PropWriteInfo,
201
203
  PropSpyMeta,
202
204
  } from './information-extractors/react'
@@ -1,6 +1,6 @@
1
1
  export { ExtractorStore } from './store'
2
2
  export { runExtractors } from './runner'
3
- export type { ExtractionResult } from './runner'
3
+ export type { ExtractionResult, RunExtractorsOptions } from './runner'
4
4
  export { buildElementTree } from './tree-builder'
5
5
  export type { ExtractedElement } from './tree-builder'
6
6
  export type { ReactExtractor, RenderContext, CreateElementEvent, RenderCompleteEvent } from './types'
@@ -12,7 +12,6 @@
12
12
  import type { ComponentType } from 'react'
13
13
  import { type CreateElementListener, renderWithExtractors } from '../../../../component-renderer'
14
14
  import type { ComponentInfo } from '../../../ts/types'
15
- import { generateMockProps, resetMockCounter } from '../../utils/mock-generator'
16
15
  import { ExtractorStore } from './store'
17
16
  import { type ExtractedElement, buildElementTree } from './tree-builder'
18
17
  import type { CreateElementEvent, ReactExtractor, RenderContext } from './types'
@@ -27,6 +26,11 @@ export interface ExtractionResult {
27
26
  elements: ExtractedElement[]
28
27
  }
29
28
 
29
+ export interface RunExtractorsOptions {
30
+ /** Optional HOC to wrap the component before rendering (e.g. a context provider). */
31
+ wrapper?: (component: ComponentType<unknown>) => ComponentType<unknown>
32
+ }
33
+
30
34
  // ─────────────────────────────────────────────────────────────────────────────
31
35
  // Runner
32
36
  // ─────────────────────────────────────────────────────────────────────────────
@@ -43,18 +47,16 @@ export function runExtractors(
43
47
  componentInfo: ComponentInfo,
44
48
  component: ComponentType<unknown>,
45
49
  extractors: ReactExtractor[],
50
+ options?: RunExtractorsOptions,
46
51
  ): ExtractionResult {
47
- // Reset mock counter for reproducible results
48
- resetMockCounter()
49
-
50
52
  // Create shared store
51
53
  const store = new ExtractorStore()
52
54
 
53
- // Create render context with mutable props
55
+ // Create render context; extractors populate props in onBeforeRender
54
56
  const context: RenderContext = {
55
57
  componentInfo,
56
58
  component,
57
- props: generateMockProps(componentInfo),
59
+ props: {},
58
60
  store,
59
61
  }
60
62
 
@@ -74,8 +76,11 @@ export function runExtractors(
74
76
  },
75
77
  ]
76
78
 
79
+ // Apply optional HOC wrapper before rendering
80
+ const renderComponent = options?.wrapper ? options.wrapper(context.component) : context.component
81
+
77
82
  // Phase 2: Render with element creation interception
78
- const html = renderWithExtractors(context.component, context.props, listeners, store)
83
+ const html = renderWithExtractors(renderComponent, context.props, listeners, store)
79
84
 
80
85
  // Phase 3: renderComplete - extractors can post-process
81
86
  for (const ext of extractors) {
@@ -146,7 +146,9 @@ function getTextContent(element: Element): string {
146
146
  */
147
147
  function getElementNamePart(element: Element, getElementById: (id: string) => Element | undefined): string {
148
148
  const id = getAttribute(element, 'id')
149
- if (id) {
149
+ // Skip spy-instrumented ids (mock_propName_XXXXXX) — they are runtime mock values,
150
+ // not semantic identifiers, and would produce garbage element names.
151
+ if (id && !id.startsWith('mock_')) {
150
152
  return pascalCase(id)
151
153
  }
152
154
 
@@ -12,6 +12,7 @@ export {
12
12
  } from './core'
13
13
  export type {
14
14
  ExtractionResult,
15
+ RunExtractorsOptions,
15
16
  ExtractedElement,
16
17
  ReactExtractor,
17
18
  RenderContext,