@wix/zero-config-implementation 1.82.0 → 1.84.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-C-TJl-9-.js → index-BHTXuyLh.js} +1 -1
- package/dist/{index-tAvBvLbA.js → index-Byvq-358.js} +23686 -23720
- package/dist/index.d.ts +4 -12
- package/dist/index.js +1 -1
- package/package.json +2 -2
- package/src/converters/data-item-builder.ts +4 -1
- package/src/converters/to-editor-component.ts +11 -5
- package/src/index.css-imports-fatal.test.ts +33 -0
- package/src/index.ts +31 -77
- package/src/information-extractors/css/parse.ts +110 -106
- package/src/information-extractors/ts/components.ts +10 -0
- package/src/information-extractors/ts/types.ts +3 -0
- package/src/manifest-pipeline.css-match-fatal.test.ts +41 -0
- package/src/manifest-pipeline.ts +88 -130
- package/src/module-loader.ts +0 -4
- package/src/extraction-types.ts +0 -8
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import { beforeEach, expect, it, vi } from 'vitest'
|
|
4
|
+
import { processComponent } from './manifest-pipeline'
|
|
5
|
+
|
|
6
|
+
vi.mock('./information-extractors/css', async (importOriginal) => {
|
|
7
|
+
const original = await importOriginal<typeof import('./information-extractors/css')>()
|
|
8
|
+
return {
|
|
9
|
+
...original,
|
|
10
|
+
matchCssSelectors: vi.fn(),
|
|
11
|
+
}
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
vi.mock('node:fs', async (importOriginal) => {
|
|
15
|
+
const original = await importOriginal<typeof import('node:fs')>()
|
|
16
|
+
return { ...original, default: { ...original, readFileSync: vi.fn() } }
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
vi.mocked(fs.readFileSync).mockReturnValue('.root { color: red; }')
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('returns ok:false with IoError when matchCssSelectors throws', async () => {
|
|
24
|
+
const { matchCssSelectors } = await import('./information-extractors/css')
|
|
25
|
+
vi.mocked(matchCssSelectors).mockImplementation(() => {
|
|
26
|
+
throw new Error('simulated selector matching failure')
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
const SimpleComponent: React.FC<unknown> = () => React.createElement('div', { className: 'root' }, 'hello')
|
|
30
|
+
|
|
31
|
+
const result = processComponent({ componentName: 'SimpleComponent', props: {} }, () => SimpleComponent, [
|
|
32
|
+
'/fake/style.css',
|
|
33
|
+
])
|
|
34
|
+
|
|
35
|
+
expect(result.ok).toBe(false)
|
|
36
|
+
if (!result.ok) {
|
|
37
|
+
expect(result.error.name).toBe('IoError')
|
|
38
|
+
expect(result.error.message).toContain('CSS selector matching failed for "SimpleComponent"')
|
|
39
|
+
expect(result.error.message).toContain('simulated selector matching failure')
|
|
40
|
+
}
|
|
41
|
+
})
|
package/src/manifest-pipeline.ts
CHANGED
|
@@ -26,7 +26,6 @@ import { compileSass } from './information-extractors/css/sass-adapter'
|
|
|
26
26
|
import type { ComponentType } from 'react'
|
|
27
27
|
|
|
28
28
|
import { IoError, NotFoundError, ParseError } from './errors'
|
|
29
|
-
import type { ExtractionError } from './extraction-types'
|
|
30
29
|
|
|
31
30
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
32
31
|
// Types
|
|
@@ -58,12 +57,9 @@ export type ProcessComponentResult =
|
|
|
58
57
|
* Processes a single component through the full manifest pipeline:
|
|
59
58
|
* loading, rendering with prop tracking, CSS extraction, and selector matching.
|
|
60
59
|
*
|
|
61
|
-
* On success (`ok: true`), returns enriched component info.
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
* When the component was loaded but render/extractors throw, returns `ok: false`
|
|
66
|
-
* with an {@link IoError} — Tier-1 callers should turn that into a failed `Result`.
|
|
60
|
+
* On success (`ok: true`), returns enriched component info.
|
|
61
|
+
* On failure, returns `ok: false` with a typed error — Tier-1 callers should
|
|
62
|
+
* turn that into a failed `Result`.
|
|
67
63
|
*
|
|
68
64
|
* @param compiledEntryPath - Optional path to the user's compiled JS entry. When
|
|
69
65
|
* provided, the renderer also patches the React module resolved from the
|
|
@@ -73,8 +69,6 @@ export function processComponent(
|
|
|
73
69
|
componentInfo: ComponentInfo,
|
|
74
70
|
loadComponent: (componentName: string) => ComponentType<unknown> | null,
|
|
75
71
|
cssImportPaths: string[],
|
|
76
|
-
loaderHasError: boolean,
|
|
77
|
-
report: (error: ExtractionError) => void,
|
|
78
72
|
options?: RunExtractorsOptions,
|
|
79
73
|
compiledEntryPath?: string,
|
|
80
74
|
eligiblePaths?: string[],
|
|
@@ -85,82 +79,70 @@ export function processComponent(
|
|
|
85
79
|
let Component: ComponentType<unknown> | null = null
|
|
86
80
|
try {
|
|
87
81
|
Component = loadComponent(componentName)
|
|
88
|
-
if (!Component
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
props: { phase: 'loader' },
|
|
94
|
-
|
|
95
|
-
}
|
|
82
|
+
if (!Component) {
|
|
83
|
+
return {
|
|
84
|
+
ok: false,
|
|
85
|
+
error: new NotFoundError(
|
|
86
|
+
`Component "${componentName}" was not found as a named export in the compiled bundle. Ensure "${componentName}" is exported from the package entry point.`,
|
|
87
|
+
{ props: { phase: 'loader' } },
|
|
88
|
+
),
|
|
89
|
+
}
|
|
96
90
|
}
|
|
97
91
|
} catch (thrownError) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
{ cause: thrownError instanceof Error ? thrownError : undefined, props: { phase: 'loader' } },
|
|
105
|
-
),
|
|
106
|
-
})
|
|
92
|
+
return {
|
|
93
|
+
ok: false,
|
|
94
|
+
error: new IoError(
|
|
95
|
+
`Failed to access component "${componentName}" from compiled bundle: ${thrownError instanceof Error ? thrownError.message : String(thrownError)}`,
|
|
96
|
+
{ cause: thrownError instanceof Error ? thrownError : undefined, props: { phase: 'loader' } },
|
|
97
|
+
),
|
|
107
98
|
}
|
|
108
99
|
}
|
|
109
100
|
|
|
110
|
-
let coupledInfo: CoupledComponentInfo | null = null
|
|
111
101
|
let enhancedInfo: CoupledComponentInfo
|
|
112
102
|
let html: string | undefined
|
|
113
103
|
let extractedElements: ExtractedElement[] = []
|
|
114
104
|
|
|
115
105
|
// Render component with mock props and track prop flow (can fail)
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
extractedElements = result.elements
|
|
124
|
-
|
|
125
|
-
const { props: coupledProps, innerElementProps } = buildCoupledProps(componentInfo, state.stores)
|
|
126
|
-
coupledInfo = {
|
|
127
|
-
componentName,
|
|
128
|
-
props: coupledProps,
|
|
129
|
-
elements: convertElements(extractedElements),
|
|
130
|
-
innerElementProps: innerElementProps.size > 0 ? innerElementProps : undefined,
|
|
131
|
-
propUsages: state.stores.propUsages,
|
|
132
|
-
}
|
|
133
|
-
} catch (thrownError) {
|
|
134
|
-
const error = new IoError(thrownError instanceof Error ? thrownError.message : String(thrownError), {
|
|
135
|
-
cause: thrownError instanceof Error ? thrownError : undefined,
|
|
136
|
-
props: { phase: 'render' },
|
|
137
|
-
})
|
|
138
|
-
report({ componentName, phase: 'render', error })
|
|
139
|
-
return { ok: false, error }
|
|
140
|
-
}
|
|
141
|
-
}
|
|
106
|
+
try {
|
|
107
|
+
const { extractor: propTracker, state } = createPropTrackerExtractor({ eligiblePaths })
|
|
108
|
+
const cssExtractor = createCssPropertiesExtractor()
|
|
109
|
+
|
|
110
|
+
const result = runExtractors(componentInfo, Component, [propTracker, cssExtractor], options, compiledEntryPath)
|
|
111
|
+
html = result.html
|
|
112
|
+
extractedElements = result.elements
|
|
142
113
|
|
|
143
|
-
|
|
144
|
-
// (CSS properties are now embedded via css-properties-extractor)
|
|
145
|
-
if (coupledInfo) {
|
|
146
|
-
enhancedInfo = coupledInfo
|
|
147
|
-
} else {
|
|
148
|
-
// Fallback: create minimal info without DOM coupling
|
|
114
|
+
const { props: coupledProps, innerElementProps } = buildCoupledProps(componentInfo, state.stores)
|
|
149
115
|
enhancedInfo = {
|
|
150
116
|
componentName,
|
|
151
|
-
props:
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
117
|
+
props: coupledProps,
|
|
118
|
+
elements: convertElements(extractedElements),
|
|
119
|
+
innerElementProps: innerElementProps.size > 0 ? innerElementProps : undefined,
|
|
120
|
+
propUsages: state.stores.propUsages,
|
|
121
|
+
}
|
|
122
|
+
} catch (thrownError) {
|
|
123
|
+
return {
|
|
124
|
+
ok: false,
|
|
125
|
+
error: new IoError(thrownError instanceof Error ? thrownError.message : String(thrownError), {
|
|
126
|
+
cause: thrownError instanceof Error ? thrownError : undefined,
|
|
127
|
+
props: { phase: 'render' },
|
|
128
|
+
}),
|
|
159
129
|
}
|
|
160
130
|
}
|
|
161
131
|
|
|
162
|
-
// Read and parse CSS imports
|
|
163
|
-
|
|
132
|
+
// Read and parse CSS imports (fatal)
|
|
133
|
+
let css: ExtractedCssInfo[]
|
|
134
|
+
try {
|
|
135
|
+
css = extractCssInfo(cssImportPaths, componentName)
|
|
136
|
+
} catch (thrownError) {
|
|
137
|
+
const error =
|
|
138
|
+
thrownError instanceof ParseError
|
|
139
|
+
? thrownError
|
|
140
|
+
: new ParseError(
|
|
141
|
+
`Failed to parse CSS file for component "${componentName}": ${thrownError instanceof Error ? thrownError.message : String(thrownError)}`,
|
|
142
|
+
{ cause: thrownError instanceof Error ? thrownError : undefined, props: { phase: 'css' } },
|
|
143
|
+
)
|
|
144
|
+
return { ok: false, error }
|
|
145
|
+
}
|
|
164
146
|
|
|
165
147
|
// Match CSS selectors to elements
|
|
166
148
|
let varUsedByTraceId = new Map<string, Set<string>>()
|
|
@@ -175,14 +157,13 @@ export function processComponent(
|
|
|
175
157
|
}
|
|
176
158
|
varUsedByTraceId = matchResult.varUsedByTraceId
|
|
177
159
|
} catch (thrownError) {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
phase: 'css',
|
|
160
|
+
return {
|
|
161
|
+
ok: false,
|
|
181
162
|
error: new IoError(
|
|
182
|
-
`CSS selector matching failed: ${thrownError instanceof Error ? thrownError.message : String(thrownError)}`,
|
|
163
|
+
`CSS selector matching failed for "${componentName}": ${thrownError instanceof Error ? thrownError.message : String(thrownError)}`,
|
|
183
164
|
{ cause: thrownError instanceof Error ? thrownError : undefined, props: { phase: 'css' } },
|
|
184
165
|
),
|
|
185
|
-
}
|
|
166
|
+
}
|
|
186
167
|
}
|
|
187
168
|
}
|
|
188
169
|
|
|
@@ -349,70 +330,47 @@ function convertElements(elements: ExtractedElement[]): CoupledComponentInfo['el
|
|
|
349
330
|
* Reads and parses CSS files, extracting standard and custom properties.
|
|
350
331
|
* Non-fatal parse failures are reported via `reportError`.
|
|
351
332
|
*/
|
|
352
|
-
function extractCssInfo(
|
|
353
|
-
cssImportPaths: string[],
|
|
354
|
-
componentName: string,
|
|
355
|
-
report: (error: ExtractionError) => void,
|
|
356
|
-
): ExtractedCssInfo[] {
|
|
333
|
+
function extractCssInfo(cssImportPaths: string[], componentName: string): ExtractedCssInfo[] {
|
|
357
334
|
const cssInfos: ExtractedCssInfo[] = []
|
|
358
335
|
|
|
359
336
|
for (const cssPath of cssImportPaths) {
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
if (
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
phase: 'css',
|
|
369
|
-
error: new ParseError(`Failed to compile ${cssPath}`, {
|
|
370
|
-
cause: compiledCssResult.error,
|
|
371
|
-
props: { phase: 'css' },
|
|
372
|
-
}),
|
|
373
|
-
})
|
|
374
|
-
continue
|
|
375
|
-
}
|
|
376
|
-
cssContent = compiledCssResult.value
|
|
377
|
-
} else {
|
|
378
|
-
cssContent = fs.readFileSync(cssPath, 'utf-8')
|
|
337
|
+
let cssContent: string
|
|
338
|
+
if (cssPath.endsWith('.scss') || cssPath.endsWith('.sass')) {
|
|
339
|
+
const compiledCssResult = compileSass(cssPath)
|
|
340
|
+
if (compiledCssResult.isErr()) {
|
|
341
|
+
throw new ParseError(`Failed to compile "${cssPath}" for component "${componentName}"`, {
|
|
342
|
+
cause: compiledCssResult.error,
|
|
343
|
+
props: { phase: 'css' },
|
|
344
|
+
})
|
|
379
345
|
}
|
|
346
|
+
cssContent = compiledCssResult.value
|
|
347
|
+
} else {
|
|
348
|
+
cssContent = fs.readFileSync(cssPath, 'utf-8')
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const api = parseCss(cssContent)
|
|
352
|
+
const allProps = api.getAllProperties()
|
|
353
|
+
|
|
354
|
+
const properties = new Map<string, string>()
|
|
355
|
+
const customProperties = new Map<string, string>()
|
|
380
356
|
|
|
381
|
-
|
|
382
|
-
const
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
const customProperties = new Map<string, string>()
|
|
388
|
-
|
|
389
|
-
for (const [, props] of allProps) {
|
|
390
|
-
for (const prop of props) {
|
|
391
|
-
if (prop.name.startsWith('--')) {
|
|
392
|
-
customProperties.set(prop.name, prop.value)
|
|
393
|
-
} else {
|
|
394
|
-
properties.set(prop.name, prop.value)
|
|
395
|
-
}
|
|
357
|
+
for (const [, props] of allProps) {
|
|
358
|
+
for (const prop of props) {
|
|
359
|
+
if (prop.name.startsWith('--')) {
|
|
360
|
+
customProperties.set(prop.name, prop.value)
|
|
361
|
+
} else {
|
|
362
|
+
properties.set(prop.name, prop.value)
|
|
396
363
|
}
|
|
397
364
|
}
|
|
398
|
-
|
|
399
|
-
cssInfos.push({
|
|
400
|
-
filePath: cssPath,
|
|
401
|
-
api,
|
|
402
|
-
properties,
|
|
403
|
-
customProperties,
|
|
404
|
-
isCssModule: /\.module\.(css|scss|sass)$/.test(cssPath),
|
|
405
|
-
})
|
|
406
|
-
} catch (thrownError) {
|
|
407
|
-
report({
|
|
408
|
-
componentName,
|
|
409
|
-
phase: 'css',
|
|
410
|
-
error: new ParseError(
|
|
411
|
-
`Failed to parse ${cssPath}: ${thrownError instanceof Error ? thrownError.message : String(thrownError)}`,
|
|
412
|
-
{ cause: thrownError instanceof Error ? thrownError : undefined, props: { phase: 'css' } },
|
|
413
|
-
),
|
|
414
|
-
})
|
|
415
365
|
}
|
|
366
|
+
|
|
367
|
+
cssInfos.push({
|
|
368
|
+
filePath: cssPath,
|
|
369
|
+
api,
|
|
370
|
+
properties,
|
|
371
|
+
customProperties,
|
|
372
|
+
isCssModule: /\.module\.(css|scss|sass)$/.test(cssPath),
|
|
373
|
+
})
|
|
416
374
|
}
|
|
417
375
|
|
|
418
376
|
return cssInfos
|
package/src/module-loader.ts
CHANGED
|
@@ -148,10 +148,6 @@ export function findComponent(moduleExports: Record<string, unknown>, name: stri
|
|
|
148
148
|
return null
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
-
/**
|
|
152
|
-
* Finds the default-exported React component from a module.
|
|
153
|
-
* Returns null when the default export is missing or is not component-like.
|
|
154
|
-
*/
|
|
155
151
|
export function findDefaultComponent(moduleExports: Record<string, unknown>): ComponentType<unknown> | null {
|
|
156
152
|
const defaultExport = moduleExports.default
|
|
157
153
|
if (isComponent(defaultExport)) {
|
package/src/extraction-types.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { BaseError } from './errors'
|
|
2
|
-
|
|
3
|
-
/** A non-fatal issue encountered during component extraction. */
|
|
4
|
-
export interface ExtractionError {
|
|
5
|
-
componentName: string
|
|
6
|
-
phase: 'render' | 'coupling' | 'css' | 'loader' | 'conversion'
|
|
7
|
-
error: InstanceType<typeof BaseError>
|
|
8
|
-
}
|