@wix/zero-config-implementation 1.78.0 → 1.79.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/README.md +4 -0
- package/dist/{index--6I20lGB.js → index-CoU1WRYf.js} +25036 -24768
- package/dist/{index-DAAmOM1c.js → index-DCJue8M7.js} +1 -1
- package/dist/index.js +1 -1
- package/package.json +2 -2
- package/src/converters/to-editor-component.ts +2 -2
- package/src/extensions/context-providers/context.test.ts +181 -0
- package/src/extensions/context-providers/context.ts +83 -0
- package/src/extensions/context-providers/mock-provider.test.ts +44 -0
- package/src/extensions/context-providers/mock-provider.ts +210 -0
- package/src/extensions/context-providers/types.ts +22 -0
- package/src/{ref-elements → extensions/ref-elements}/context.test.ts +4 -4
- package/src/extensions/ref-elements/context.ts +168 -0
- package/src/{ref-elements → extensions/ref-elements}/eligible-paths.ts +1 -1
- package/src/extensions/shared/ast-scanner.ts +85 -0
- package/src/extensions/shared/catalog-loader.ts +64 -0
- package/src/{ref-elements → extensions/shared}/module-resolution.test.ts +2 -2
- package/src/index.ts +108 -67
- package/src/information-extractors/react/extractors/prop-tracker.test.ts +2 -2
- package/src/information-extractors/react/extractors/prop-tracker.ts +3 -3
- package/src/information-extractors/react/utils/mock-generator.test.ts +107 -3
- package/src/information-extractors/react/utils/mock-generator.ts +104 -1
- package/src/manifest-pipeline.ts +4 -1
- package/src/module-loader.test.ts +1 -1
- package/src/module-loader.ts +1 -1
- package/src/react-runtime-loader.ts +28 -2
- package/src/ref-elements/context.ts +0 -280
- /package/src/{ref-elements → extensions/ref-elements}/component-tag.ts +0 -0
- /package/src/{ref-elements → extensions/ref-elements}/path-utils.test.ts +0 -0
- /package/src/{ref-elements → extensions/ref-elements}/path-utils.ts +0 -0
- /package/src/{ref-elements → extensions/ref-elements}/types.ts +0 -0
- /package/src/{ref-elements → extensions/shared}/module-resolution.ts +0 -0
- /package/src/{ref-elements/module-specifier.ts → extensions/shared/package-specifier.ts} +0 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import ts from 'typescript'
|
|
2
|
+
import type { ComponentInfo } from '../../information-extractors/ts/types'
|
|
3
|
+
import { collectAllPackageImportSpecifiers } from '../shared/ast-scanner'
|
|
4
|
+
import { loadNormalizedCatalogEntries } from '../shared/catalog-loader'
|
|
5
|
+
import { resolveModuleUrlFromEntryPath } from '../shared/module-resolution'
|
|
6
|
+
import { extractBasePackageName, extractModuleLeafExportName } from '../shared/package-specifier'
|
|
7
|
+
import { extractEligibleRefElementPaths } from './eligible-paths'
|
|
8
|
+
import type { RefElementContext, RefElementModule } from './types'
|
|
9
|
+
|
|
10
|
+
interface NormalizedRefElementCatalogEntry {
|
|
11
|
+
moduleSpecifier: string
|
|
12
|
+
refComponentType: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function buildRefElementContext(
|
|
16
|
+
program: ts.Program,
|
|
17
|
+
sourceFilePath: string,
|
|
18
|
+
componentInfo: ComponentInfo,
|
|
19
|
+
compiledEntryPath: string,
|
|
20
|
+
): Promise<RefElementContext> {
|
|
21
|
+
const importedModuleSpecifiers = collectAllPackageImportSpecifiers(
|
|
22
|
+
program,
|
|
23
|
+
sourceFilePath,
|
|
24
|
+
referencesRefElementModule,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
eligiblePaths: extractEligibleRefElementPaths(componentInfo),
|
|
29
|
+
runtimeModules: await resolveRuntimeModules(importedModuleSpecifiers, compiledEntryPath),
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function resolveRuntimeModules(
|
|
34
|
+
importedModuleSpecifiers: string[],
|
|
35
|
+
compiledEntryPath: string,
|
|
36
|
+
): Promise<RefElementModule[]> {
|
|
37
|
+
if (importedModuleSpecifiers.length === 0) {
|
|
38
|
+
return []
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const importedModuleSpecifierSet = new Set(importedModuleSpecifiers)
|
|
42
|
+
const catalogEntries = await loadNormalizedCatalogEntries(
|
|
43
|
+
importedModuleSpecifiers,
|
|
44
|
+
compiledEntryPath,
|
|
45
|
+
normalizeRefElementExtensionEntry,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
const runtimeModulesBySpecifier = new Map<string, Set<string>>()
|
|
49
|
+
|
|
50
|
+
for (const catalogEntry of catalogEntries) {
|
|
51
|
+
if (!importedModuleSpecifierSet.has(catalogEntry.moduleSpecifier)) {
|
|
52
|
+
continue
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const refComponentTypes = runtimeModulesBySpecifier.get(catalogEntry.moduleSpecifier) ?? new Set<string>()
|
|
56
|
+
refComponentTypes.add(catalogEntry.refComponentType)
|
|
57
|
+
runtimeModulesBySpecifier.set(catalogEntry.moduleSpecifier, refComponentTypes)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const runtimeModules: RefElementModule[] = []
|
|
61
|
+
|
|
62
|
+
for (const [moduleSpecifier, refComponentTypes] of runtimeModulesBySpecifier.entries()) {
|
|
63
|
+
if (refComponentTypes.size !== 1) {
|
|
64
|
+
continue
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const refComponentType = [...refComponentTypes][0]
|
|
68
|
+
if (!refComponentType) {
|
|
69
|
+
continue
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const resolvedModuleUrl = resolveModuleUrlFromEntryPath(compiledEntryPath, moduleSpecifier)
|
|
73
|
+
if (!resolvedModuleUrl) {
|
|
74
|
+
continue
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
runtimeModules.push({
|
|
78
|
+
exportNames: buildRefElementExportNames(moduleSpecifier),
|
|
79
|
+
moduleSpecifier,
|
|
80
|
+
resolvedModuleUrl,
|
|
81
|
+
refComponentType,
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return runtimeModules
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function normalizeRefElementExtensionEntry(rawEntry: unknown): NormalizedRefElementCatalogEntry | undefined {
|
|
89
|
+
const entry = rawEntry as {
|
|
90
|
+
options?: { type?: string; resources?: { client?: { moduleSpecifier?: string } } }
|
|
91
|
+
type?: string
|
|
92
|
+
resources?: { client?: { moduleSpecifier?: string } }
|
|
93
|
+
}
|
|
94
|
+
const normalizedEntry = entry.options ?? entry
|
|
95
|
+
const moduleSpecifier = normalizedEntry.resources?.client?.moduleSpecifier
|
|
96
|
+
const refComponentType = normalizedEntry.type
|
|
97
|
+
|
|
98
|
+
if (!moduleSpecifier || !refComponentType) {
|
|
99
|
+
return undefined
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
moduleSpecifier,
|
|
104
|
+
refComponentType,
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function referencesRefElementModule(statement: ts.Statement, moduleSpecifier: string): boolean {
|
|
109
|
+
const moduleLeafExportName = extractModuleLeafExportName(moduleSpecifier)
|
|
110
|
+
if (!moduleLeafExportName) {
|
|
111
|
+
return false
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (ts.isImportDeclaration(statement)) {
|
|
115
|
+
return importsRefElementModule(statement, moduleLeafExportName)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (ts.isExportDeclaration(statement)) {
|
|
119
|
+
return reExportsRefElementModule(statement, moduleLeafExportName)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return false
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function importsRefElementModule(importDeclaration: ts.ImportDeclaration, moduleLeafExportName: string): boolean {
|
|
126
|
+
const importClause = importDeclaration.importClause
|
|
127
|
+
if (!importClause) {
|
|
128
|
+
return false
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (importClause.name) {
|
|
132
|
+
return true
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (!importClause.namedBindings || !ts.isNamedImports(importClause.namedBindings)) {
|
|
136
|
+
return false
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return importClause.namedBindings.elements.some((importSpecifier) => {
|
|
140
|
+
const importedName = importSpecifier.propertyName?.text ?? importSpecifier.name.text
|
|
141
|
+
return importedName === moduleLeafExportName
|
|
142
|
+
})
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function reExportsRefElementModule(exportDeclaration: ts.ExportDeclaration, moduleLeafExportName: string): boolean {
|
|
146
|
+
if (!exportDeclaration.exportClause || !ts.isNamedExports(exportDeclaration.exportClause)) {
|
|
147
|
+
return false
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return exportDeclaration.exportClause.elements.some((exportSpecifier) => {
|
|
151
|
+
const exportedSourceName = exportSpecifier.propertyName?.text ?? exportSpecifier.name.text
|
|
152
|
+
return exportedSourceName === 'default' || exportedSourceName === moduleLeafExportName
|
|
153
|
+
})
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function buildRefElementExportNames(moduleSpecifier: string): string[] {
|
|
157
|
+
const exportNames = new Set<string>(['default'])
|
|
158
|
+
if (extractBasePackageName(moduleSpecifier) === moduleSpecifier) {
|
|
159
|
+
return [...exportNames]
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const moduleLeafName = extractModuleLeafExportName(moduleSpecifier)
|
|
163
|
+
if (moduleLeafName) {
|
|
164
|
+
exportNames.add(moduleLeafName)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return [...exportNames]
|
|
168
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ComponentInfo, ResolvedType } from '
|
|
1
|
+
import type { ComponentInfo, ResolvedType } from '../../information-extractors/ts/types'
|
|
2
2
|
|
|
3
3
|
export function extractEligibleRefElementPaths(componentInfo: ComponentInfo): string[] {
|
|
4
4
|
const elementPropsInfo = componentInfo.props.elementProps
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import ts from 'typescript'
|
|
2
|
+
|
|
3
|
+
export function isPackageModuleSpecifier(moduleSpecifier: string): boolean {
|
|
4
|
+
return !moduleSpecifier.startsWith('.') && !moduleSpecifier.startsWith('/') && !moduleSpecifier.startsWith('file:')
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function findSourceFile(program: ts.Program, sourceFilePath: string): ts.SourceFile | undefined {
|
|
8
|
+
return (
|
|
9
|
+
program.getSourceFile(sourceFilePath) ??
|
|
10
|
+
program.getSourceFiles().find((sourceFile) => sourceFile.fileName === sourceFilePath)
|
|
11
|
+
)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function resolveImportedSourceFile(
|
|
15
|
+
program: ts.Program,
|
|
16
|
+
importingFilePath: string,
|
|
17
|
+
moduleSpecifier: string,
|
|
18
|
+
): ts.SourceFile | undefined {
|
|
19
|
+
const resolvedModule = ts.resolveModuleName(moduleSpecifier, importingFilePath, program.getCompilerOptions(), ts.sys)
|
|
20
|
+
const resolvedFileName = resolvedModule.resolvedModule?.resolvedFileName
|
|
21
|
+
|
|
22
|
+
if (!resolvedFileName) {
|
|
23
|
+
return undefined
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const resolvedSourceFile = findSourceFile(program, resolvedFileName)
|
|
27
|
+
if (!resolvedSourceFile || resolvedSourceFile.isDeclarationFile) {
|
|
28
|
+
return undefined
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return resolvedSourceFile
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function readReferencedModuleSpecifier(statement: ts.Statement): string | undefined {
|
|
35
|
+
if (ts.isImportDeclaration(statement) && ts.isStringLiteral(statement.moduleSpecifier)) {
|
|
36
|
+
return statement.moduleSpecifier.text
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (ts.isExportDeclaration(statement) && statement.moduleSpecifier && ts.isStringLiteral(statement.moduleSpecifier)) {
|
|
40
|
+
return statement.moduleSpecifier.text
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return undefined
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function collectAllPackageImportSpecifiers(
|
|
47
|
+
program: ts.Program,
|
|
48
|
+
sourceFilePath: string,
|
|
49
|
+
filter?: (statement: ts.Statement, moduleSpecifier: string) => boolean,
|
|
50
|
+
): string[] {
|
|
51
|
+
const entrySourceFile = findSourceFile(program, sourceFilePath)
|
|
52
|
+
if (!entrySourceFile) {
|
|
53
|
+
return []
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const packageSpecifiers = new Set<string>()
|
|
57
|
+
const visitedSourceFiles = new Set<string>()
|
|
58
|
+
|
|
59
|
+
function visitSourceFile(sourceFile: ts.SourceFile): void {
|
|
60
|
+
if (visitedSourceFiles.has(sourceFile.fileName)) {
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
visitedSourceFiles.add(sourceFile.fileName)
|
|
65
|
+
|
|
66
|
+
for (const statement of sourceFile.statements) {
|
|
67
|
+
const moduleSpecifier = readReferencedModuleSpecifier(statement)
|
|
68
|
+
if (!moduleSpecifier) {
|
|
69
|
+
continue
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (isPackageModuleSpecifier(moduleSpecifier) && (!filter || filter(statement, moduleSpecifier))) {
|
|
73
|
+
packageSpecifiers.add(moduleSpecifier)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const importedSourceFile = resolveImportedSourceFile(program, sourceFile.fileName, moduleSpecifier)
|
|
77
|
+
if (importedSourceFile) {
|
|
78
|
+
visitSourceFile(importedSourceFile)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
visitSourceFile(entrySourceFile)
|
|
84
|
+
return [...packageSpecifiers]
|
|
85
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { isPackageModuleSpecifier } from './ast-scanner'
|
|
2
|
+
import { resolveModuleUrlFromEntryPath } from './module-resolution'
|
|
3
|
+
import { extractBasePackageName } from './package-specifier'
|
|
4
|
+
|
|
5
|
+
interface RawExtensionModule {
|
|
6
|
+
extensions?: unknown[]
|
|
7
|
+
default?: unknown
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function extractRawExtensionEntries(extensionModule: Record<string, unknown>): unknown[] {
|
|
11
|
+
const defaultExport = extensionModule.default
|
|
12
|
+
const extensionEntries = Array.isArray(defaultExport)
|
|
13
|
+
? defaultExport
|
|
14
|
+
: ((defaultExport as { extensions?: unknown[] } | undefined)?.extensions ?? extensionModule.extensions)
|
|
15
|
+
|
|
16
|
+
return Array.isArray(extensionEntries) ? extensionEntries : []
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function loadRawExtensionEntries(packageName: string, compiledEntryPath: string): Promise<unknown[]> {
|
|
20
|
+
const resolvedExtensionsUrl = resolveModuleUrlFromEntryPath(compiledEntryPath, `${packageName}/extensions`)
|
|
21
|
+
if (!resolvedExtensionsUrl) {
|
|
22
|
+
return []
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const extensionModule = (await import(resolvedExtensionsUrl)) as RawExtensionModule & Record<string, unknown>
|
|
27
|
+
return extractRawExtensionEntries(extensionModule)
|
|
28
|
+
} catch (error) {
|
|
29
|
+
// The package declared an /extensions export but the file doesn't exist — skip it.
|
|
30
|
+
if (isModuleNotFoundError(error)) {
|
|
31
|
+
return []
|
|
32
|
+
}
|
|
33
|
+
throw error
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function isModuleNotFoundError(error: unknown): boolean {
|
|
38
|
+
return (
|
|
39
|
+
error instanceof Error &&
|
|
40
|
+
'code' in error &&
|
|
41
|
+
(error.code === 'ERR_MODULE_NOT_FOUND' || error.code === 'MODULE_NOT_FOUND')
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* For each package name derived from `importedSpecifiers`, loads its `/extensions` catalog and
|
|
47
|
+
* normalizes every raw entry through `normalizeEntry`. Returns the flat list of non-undefined results.
|
|
48
|
+
*/
|
|
49
|
+
export async function loadNormalizedCatalogEntries<T>(
|
|
50
|
+
importedSpecifiers: string[],
|
|
51
|
+
compiledEntryPath: string,
|
|
52
|
+
normalizeEntry: (rawEntry: unknown) => T | undefined,
|
|
53
|
+
): Promise<T[]> {
|
|
54
|
+
const packageNames = [...new Set(importedSpecifiers.filter(isPackageModuleSpecifier).map(extractBasePackageName))]
|
|
55
|
+
|
|
56
|
+
const entriesPerPackage = await Promise.all(
|
|
57
|
+
packageNames.map(async (packageName) => {
|
|
58
|
+
const rawEntries = await loadRawExtensionEntries(packageName, compiledEntryPath)
|
|
59
|
+
return rawEntries.map(normalizeEntry).filter((entry): entry is T => entry !== undefined)
|
|
60
|
+
}),
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
return entriesPerPackage.flat()
|
|
64
|
+
}
|
|
@@ -19,7 +19,7 @@ describe('resolveModulePathFromEntryPath', () => {
|
|
|
19
19
|
|
|
20
20
|
const packageRootPath = path.join(temporaryDirectoryPath, 'consumer-package')
|
|
21
21
|
const nodeModulesPath = path.join(packageRootPath, 'node_modules', '@wix')
|
|
22
|
-
const fixturePackagePath = path.resolve(__dirname, '
|
|
22
|
+
const fixturePackagePath = path.resolve(__dirname, '../../../../ref-element-fixtures')
|
|
23
23
|
|
|
24
24
|
fs.mkdirSync(nodeModulesPath, { recursive: true })
|
|
25
25
|
fs.symlinkSync(fixturePackagePath, path.join(nodeModulesPath, 'zero-config-ref-element-fixtures'), 'dir')
|
|
@@ -40,7 +40,7 @@ describe('resolveModulePathFromEntryPath', () => {
|
|
|
40
40
|
describe('resolveModuleUrlFromEntryPath', () => {
|
|
41
41
|
it('returns a file url for the resolved module path', () => {
|
|
42
42
|
const resolvedExtensionsUrl = resolveModuleUrlFromEntryPath(
|
|
43
|
-
path.resolve(__dirname, '
|
|
43
|
+
path.resolve(__dirname, '../../__fixtures__/esm-ref-element-entry.mjs'),
|
|
44
44
|
'@wix/zero-config-ref-element-fixtures/extensions',
|
|
45
45
|
)
|
|
46
46
|
|
package/src/index.ts
CHANGED
|
@@ -5,6 +5,10 @@ import React, { type ComponentType } from 'react'
|
|
|
5
5
|
|
|
6
6
|
import { toEditorReactComponent } from './converters'
|
|
7
7
|
import { BaseError, IoError, type NotFoundError, ParseError } from './errors'
|
|
8
|
+
import { buildContextProviderModules } from './extensions/context-providers/context'
|
|
9
|
+
import { buildContextAwareWrapper, loadMockProviders } from './extensions/context-providers/mock-provider'
|
|
10
|
+
import { buildRefElementContext } from './extensions/ref-elements/context'
|
|
11
|
+
import type { RefElementContext } from './extensions/ref-elements/types'
|
|
8
12
|
import type { ExtractionError } from './extraction-types'
|
|
9
13
|
import type { RunExtractorsOptions } from './information-extractors/react'
|
|
10
14
|
import { extractCssImports, extractDefaultComponentInfo } from './information-extractors/ts'
|
|
@@ -12,8 +16,6 @@ import type { ComponentInfo } from './information-extractors/ts/types'
|
|
|
12
16
|
import { processComponent } from './manifest-pipeline'
|
|
13
17
|
import { findComponent, findDefaultComponent, loadModuleForExtraction } from './module-loader'
|
|
14
18
|
import type { LoadModuleFailure } from './module-loader'
|
|
15
|
-
import { buildRefElementContext } from './ref-elements/context'
|
|
16
|
-
import type { RefElementContext } from './ref-elements/types'
|
|
17
19
|
import { compileTsFile } from './ts-compiler'
|
|
18
20
|
|
|
19
21
|
const defaultWrapper = (Component: ComponentType<unknown>): ComponentType<unknown> => {
|
|
@@ -64,10 +66,6 @@ export function extractComponentManifestResult(
|
|
|
64
66
|
options?.onError?.(error)
|
|
65
67
|
}
|
|
66
68
|
|
|
67
|
-
const optionsWithDefaults: ExtractComponentManifestOptions = {
|
|
68
|
-
...options,
|
|
69
|
-
wrapper: options?.wrapper ?? defaultWrapper,
|
|
70
|
-
}
|
|
71
69
|
// Step 1: Compile TypeScript (fatal)
|
|
72
70
|
return compileTsFile(componentPath)
|
|
73
71
|
.andThen((program) => {
|
|
@@ -91,79 +89,122 @@ export function extractComponentManifestResult(
|
|
|
91
89
|
.andThen(({ program, componentInfo }) => {
|
|
92
90
|
const { componentName } = componentInfo
|
|
93
91
|
|
|
92
|
+
// Step 3a: Build ref-element context
|
|
94
93
|
return ResultAsync.fromPromise(
|
|
95
94
|
buildRefElementContext(program, componentPath, componentInfo, compiledEntryPath),
|
|
96
95
|
(thrown) =>
|
|
97
96
|
new ParseError(
|
|
98
|
-
`Failed to build
|
|
97
|
+
`Failed to build ref-element context for "${componentPath}": ${thrown instanceof Error ? thrown.message : String(thrown)}`,
|
|
99
98
|
{ cause: thrown instanceof Error ? thrown : undefined, props: { phase: 'extract' } },
|
|
100
99
|
),
|
|
101
100
|
).andThen((refElementContext) =>
|
|
102
|
-
// Step
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
})
|
|
125
|
-
}
|
|
126
|
-
report({
|
|
127
|
-
componentName,
|
|
128
|
-
phase: 'loader',
|
|
129
|
-
error: new IoError('CJS require failed', { cause: failure.cjsError, props: { phase: 'loader' } }),
|
|
130
|
-
})
|
|
101
|
+
// Step 3b: Build context provider modules
|
|
102
|
+
ResultAsync.fromPromise(
|
|
103
|
+
buildContextProviderModules(program, componentPath, compiledEntryPath),
|
|
104
|
+
(thrown) =>
|
|
105
|
+
new ParseError(
|
|
106
|
+
`Failed to build context provider modules for "${componentPath}": ${thrown instanceof Error ? thrown.message : String(thrown)}`,
|
|
107
|
+
{ cause: thrown instanceof Error ? thrown : undefined, props: { phase: 'extract' } },
|
|
108
|
+
),
|
|
109
|
+
).andThen((contextProviderModules) => {
|
|
110
|
+
// Step 4: Build context provider wrapper with mock providers
|
|
111
|
+
const buildWrapper = async (): Promise<(Component: ComponentType<unknown>) => ComponentType<unknown>> => {
|
|
112
|
+
const baseWrapper = options?.wrapper ?? defaultWrapper
|
|
113
|
+
if (contextProviderModules.length === 0) {
|
|
114
|
+
return baseWrapper
|
|
115
|
+
}
|
|
116
|
+
const loadedProviders = await loadMockProviders(
|
|
117
|
+
contextProviderModules,
|
|
118
|
+
React,
|
|
119
|
+
refElementContext.runtimeModules,
|
|
120
|
+
)
|
|
121
|
+
if (loadedProviders.length === 0) {
|
|
122
|
+
return baseWrapper
|
|
131
123
|
}
|
|
124
|
+
return buildContextAwareWrapper(loadedProviders, baseWrapper, React)
|
|
125
|
+
}
|
|
132
126
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
`Failed to extract CSS imports: ${thrown instanceof Error ? thrown.message : String(thrown)}`,
|
|
145
|
-
{ cause: thrown instanceof Error ? thrown : undefined, props: { phase: 'css' } },
|
|
146
|
-
),
|
|
147
|
-
})
|
|
127
|
+
return ResultAsync.fromPromise(
|
|
128
|
+
buildWrapper(),
|
|
129
|
+
(thrown) =>
|
|
130
|
+
new ParseError(
|
|
131
|
+
`Failed to build context provider wrapper for "${componentPath}": ${thrown instanceof Error ? thrown.message : String(thrown)}`,
|
|
132
|
+
{ cause: thrown instanceof Error ? thrown : undefined, props: { phase: 'extract' } },
|
|
133
|
+
),
|
|
134
|
+
).andThen((wrapper) => {
|
|
135
|
+
const optionsWithDefaults: ExtractComponentManifestOptions = {
|
|
136
|
+
...options,
|
|
137
|
+
wrapper,
|
|
148
138
|
}
|
|
149
139
|
|
|
150
|
-
// Step 5:
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
loadComponent
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
report,
|
|
158
|
-
optionsWithDefaults,
|
|
159
|
-
compiledEntryPath,
|
|
160
|
-
refElementContext,
|
|
140
|
+
// Step 5: Load the compiled package module (non-fatal) - done after TS extraction
|
|
141
|
+
// so componentName is known when reporting failures
|
|
142
|
+
return loadModuleForExtraction(compiledEntryPath, refElementContext.runtimeModules)
|
|
143
|
+
.map(({ cleanup, moduleExports }) => ({
|
|
144
|
+
loadComponent: (name: string) =>
|
|
145
|
+
findComponent(moduleExports, name) ?? findDefaultComponent(moduleExports),
|
|
146
|
+
failure: null as LoadModuleFailure | null,
|
|
161
147
|
cleanup,
|
|
162
|
-
|
|
163
|
-
)
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
148
|
+
}))
|
|
149
|
+
.orElse((failure) =>
|
|
150
|
+
okAsync({
|
|
151
|
+
loadComponent: () => null as ComponentType<unknown> | null,
|
|
152
|
+
cleanup: async () => {},
|
|
153
|
+
failure,
|
|
154
|
+
}),
|
|
155
|
+
)
|
|
156
|
+
.andThen(({ cleanup, failure, loadComponent }) => {
|
|
157
|
+
if (failure) {
|
|
158
|
+
if (failure.esmError) {
|
|
159
|
+
report({
|
|
160
|
+
componentName,
|
|
161
|
+
phase: 'loader',
|
|
162
|
+
error: new IoError('ESM import failed', { cause: failure.esmError, props: { phase: 'loader' } }),
|
|
163
|
+
})
|
|
164
|
+
}
|
|
165
|
+
report({
|
|
166
|
+
componentName,
|
|
167
|
+
phase: 'loader',
|
|
168
|
+
error: new IoError('CJS require failed', { cause: failure.cjsError, props: { phase: 'loader' } }),
|
|
169
|
+
})
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Step 6: Extract CSS imports (non-fatal)
|
|
173
|
+
let cssImportPaths: string[] = []
|
|
174
|
+
const cssResult = Result.fromThrowable(extractCssImports, (thrown) => thrown)(program)
|
|
175
|
+
if (cssResult.isOk()) {
|
|
176
|
+
cssImportPaths = cssResult.value
|
|
177
|
+
} else {
|
|
178
|
+
const thrown = cssResult.error
|
|
179
|
+
report({
|
|
180
|
+
componentName,
|
|
181
|
+
phase: 'css',
|
|
182
|
+
error: new ParseError(
|
|
183
|
+
`Failed to extract CSS imports: ${thrown instanceof Error ? thrown.message : String(thrown)}`,
|
|
184
|
+
{ cause: thrown instanceof Error ? thrown : undefined, props: { phase: 'css' } },
|
|
185
|
+
),
|
|
186
|
+
})
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Step 7: Process the default-exported component
|
|
190
|
+
return ResultAsync.fromPromise(
|
|
191
|
+
processComponentWithCleanup(
|
|
192
|
+
componentInfo,
|
|
193
|
+
loadComponent,
|
|
194
|
+
cssImportPaths,
|
|
195
|
+
!!failure,
|
|
196
|
+
report,
|
|
197
|
+
optionsWithDefaults,
|
|
198
|
+
compiledEntryPath,
|
|
199
|
+
refElementContext,
|
|
200
|
+
cleanup,
|
|
201
|
+
errors,
|
|
202
|
+
),
|
|
203
|
+
normalizeComponentExtractionCleanupFailure,
|
|
204
|
+
)
|
|
205
|
+
})
|
|
206
|
+
})
|
|
207
|
+
}),
|
|
167
208
|
)
|
|
168
209
|
})
|
|
169
210
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import { createRefElementMarker } from '../../../ref-elements/path-utils'
|
|
3
|
-
import type { RefElementMatch } from '../../../ref-elements/types'
|
|
2
|
+
import { createRefElementMarker } from '../../../extensions/ref-elements/path-utils'
|
|
3
|
+
import type { RefElementMatch } from '../../../extensions/ref-elements/types'
|
|
4
4
|
import { ExtractorStore } from './core/store'
|
|
5
5
|
import { createPropTrackerExtractor } from './prop-tracker'
|
|
6
6
|
|
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
|
|
8
8
|
import type { HTMLAttributes } from 'react'
|
|
9
9
|
import { TRACE_ATTR } from '../../../component-renderer'
|
|
10
|
-
import { readRefElementComponentType } from '../../../ref-elements/component-tag'
|
|
11
|
-
import { parseRefElementMarker } from '../../../ref-elements/path-utils'
|
|
12
|
-
import type { RefElementMatch } from '../../../ref-elements/types'
|
|
10
|
+
import { readRefElementComponentType } from '../../../extensions/ref-elements/component-tag'
|
|
11
|
+
import { parseRefElementMarker } from '../../../extensions/ref-elements/path-utils'
|
|
12
|
+
import type { RefElementMatch } from '../../../extensions/ref-elements/types'
|
|
13
13
|
import { findPreferredSemanticClass, normalizeClassNames } from '../../../utils/css-class'
|
|
14
14
|
import type { PropSpyMeta, TrackingStores } from '../types'
|
|
15
15
|
import { type PropSpyRegistrar, generateMockProps, resetMockCounter } from '../utils/mock-generator'
|