@wix/zero-config-implementation 1.13.0 → 1.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -0
- package/dist/index.js +4642 -4555
- package/dist/information-extractors/ts/components.d.ts +5 -0
- package/dist/information-extractors/ts/utils/semantic-type-resolver.d.ts +10 -1
- package/dist/manifest-pipeline.d.ts +1 -0
- package/package.json +3 -2
- package/src/index.ts +14 -3
- package/src/information-extractors/react/utils/mock-generator.ts +30 -14
- package/src/information-extractors/ts/components.ts +97 -12
- package/src/information-extractors/ts/utils/semantic-type-resolver.ts +77 -22
- package/src/manifest-pipeline.ts +5 -0
- package/src/ts-compiler.ts +1 -8
- package/vite.config.ts +3 -2
|
@@ -5,5 +5,10 @@ export declare function extractAllComponentInfo(program: ts.Program, filePath: s
|
|
|
5
5
|
* Extracts component info for only the default-exported component in a file.
|
|
6
6
|
* Returns `undefined` if the file has no default export or if the default export
|
|
7
7
|
* is not a recognized React component.
|
|
8
|
+
*
|
|
9
|
+
* Handles the re-export case where the entry file only re-exports the component
|
|
10
|
+
* from another file (e.g. `export { Foo as default } from './Foo'`). In that
|
|
11
|
+
* situation react-docgen-typescript finds no component in the entry file, so we
|
|
12
|
+
* follow the re-export to the defining file and extract from there instead.
|
|
8
13
|
*/
|
|
9
14
|
export declare function extractDefaultComponentInfo(program: ts.Program, filePath: string): ComponentInfo | undefined;
|
|
@@ -1,3 +1,12 @@
|
|
|
1
1
|
import { default as ts } from 'typescript';
|
|
2
2
|
import { ResolvedType } from '../types';
|
|
3
|
-
|
|
3
|
+
interface ResolveTypeOptions {
|
|
4
|
+
type: ts.Type;
|
|
5
|
+
checker: ts.TypeChecker;
|
|
6
|
+
depth?: number;
|
|
7
|
+
typeString?: string;
|
|
8
|
+
declaredSymbol?: ts.Symbol;
|
|
9
|
+
seenTypes?: Set<ts.Type>;
|
|
10
|
+
}
|
|
11
|
+
export declare function resolveType({ type, checker, depth, typeString, declaredSymbol, seenTypes, }: ResolveTypeOptions): ResolvedType;
|
|
12
|
+
export {};
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"registry": "https://registry.npmjs.org/",
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
7
|
-
"version": "1.
|
|
7
|
+
"version": "1.15.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",
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
},
|
|
24
24
|
"scripts": {
|
|
25
25
|
"build": "vite build",
|
|
26
|
+
"build:dev": "vite build --mode development",
|
|
26
27
|
"lint": "biome check src/",
|
|
27
28
|
"test": "vitest run --passWithNoTests",
|
|
28
29
|
"test:watch": "vitest --passWithNoTests",
|
|
@@ -74,5 +75,5 @@
|
|
|
74
75
|
]
|
|
75
76
|
}
|
|
76
77
|
},
|
|
77
|
-
"falconPackageHash": "
|
|
78
|
+
"falconPackageHash": "1d9fc602e0ba357d71e273c769064f5c8c38b293c6fbf5e964991825"
|
|
78
79
|
}
|
package/src/index.ts
CHANGED
|
@@ -43,6 +43,7 @@ export interface ExtractionError {
|
|
|
43
43
|
componentName: string
|
|
44
44
|
phase: 'render' | 'coupling' | 'css' | 'loader' | 'conversion'
|
|
45
45
|
error: string
|
|
46
|
+
stack?: string
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -66,13 +67,21 @@ export function extractComponentManifest(
|
|
|
66
67
|
): ResultAsync<ManifestResult, InstanceType<typeof NotFoundError> | InstanceType<typeof ParseError>> {
|
|
67
68
|
// Step 1: Load the compiled package module (non-fatal)
|
|
68
69
|
return loadModule(compiledEntryPath)
|
|
69
|
-
.map((moduleExports) => ({
|
|
70
|
+
.map((moduleExports) => ({
|
|
71
|
+
moduleExports,
|
|
72
|
+
loaderError: null as string | null,
|
|
73
|
+
loaderStack: undefined as string | undefined,
|
|
74
|
+
}))
|
|
70
75
|
.orElse((err) =>
|
|
71
76
|
ResultAsync.fromSafePromise(
|
|
72
|
-
Promise.resolve({
|
|
77
|
+
Promise.resolve({
|
|
78
|
+
moduleExports: null as Record<string, unknown> | null,
|
|
79
|
+
loaderError: err.message,
|
|
80
|
+
loaderStack: err.stack,
|
|
81
|
+
}),
|
|
73
82
|
),
|
|
74
83
|
)
|
|
75
|
-
.andThen(({ moduleExports, loaderError }) => {
|
|
84
|
+
.andThen(({ moduleExports, loaderError, loaderStack }) => {
|
|
76
85
|
const loadComponent: (componentName: string) => ComponentType<unknown> | null = moduleExports
|
|
77
86
|
? (name) => findComponent(moduleExports, name)
|
|
78
87
|
: () => null
|
|
@@ -111,6 +120,7 @@ export function extractComponentManifest(
|
|
|
111
120
|
componentName: componentInfo.componentName,
|
|
112
121
|
phase: 'loader',
|
|
113
122
|
error: loaderError,
|
|
123
|
+
stack: loaderStack,
|
|
114
124
|
})
|
|
115
125
|
}
|
|
116
126
|
|
|
@@ -126,6 +136,7 @@ export function extractComponentManifest(
|
|
|
126
136
|
componentName: componentPath,
|
|
127
137
|
phase: 'css',
|
|
128
138
|
error: `Failed to extract CSS imports: ${thrown instanceof Error ? (thrown as Error).message : String(thrown)}`,
|
|
139
|
+
stack: thrown instanceof Error ? (thrown as Error).stack : undefined,
|
|
129
140
|
})
|
|
130
141
|
}
|
|
131
142
|
|
|
@@ -236,14 +236,20 @@ function generateUnionValue(
|
|
|
236
236
|
const types = resolvedType.types ?? []
|
|
237
237
|
|
|
238
238
|
// Prefer string literals for textEnum-like unions
|
|
239
|
-
const stringLiteral = types.find(
|
|
239
|
+
const stringLiteral = types.find(
|
|
240
|
+
(candidateType) => candidateType.kind === 'literal' && typeof candidateType.value === 'string',
|
|
241
|
+
)
|
|
240
242
|
if (stringLiteral) {
|
|
241
243
|
return stringLiteral.value
|
|
242
244
|
}
|
|
243
245
|
|
|
244
|
-
//
|
|
245
|
-
|
|
246
|
-
|
|
246
|
+
// Skip undefined/null when picking a representative type — Partial<T> produces T | undefined
|
|
247
|
+
// unions where TypeScript may place undefined first, which would generate useless mock values
|
|
248
|
+
const isNullish = (candidateType: ResolvedType) =>
|
|
249
|
+
candidateType.kind === 'primitive' && (candidateType.value === 'undefined' || candidateType.value === 'null')
|
|
250
|
+
const representative = types.find((candidateType) => !isNullish(candidateType)) ?? types[0]
|
|
251
|
+
if (representative) {
|
|
252
|
+
return generateValueFromResolvedType(representative, propName, path, registrar)
|
|
247
253
|
}
|
|
248
254
|
|
|
249
255
|
return `mock_${propName}_${faker.string.alphanumeric(6)}`
|
|
@@ -317,7 +323,7 @@ function generateObjectValue(
|
|
|
317
323
|
*/
|
|
318
324
|
function generateEnumValue(resolvedType: ResolvedType): unknown {
|
|
319
325
|
const types = resolvedType.types ?? []
|
|
320
|
-
const firstLiteral = types.find((
|
|
326
|
+
const firstLiteral = types.find((candidateType) => candidateType.kind === 'literal')
|
|
321
327
|
|
|
322
328
|
if (firstLiteral) {
|
|
323
329
|
return firstLiteral.value
|
|
@@ -334,12 +340,12 @@ function generateMockFunction(
|
|
|
334
340
|
path: string,
|
|
335
341
|
registrar?: PropSpyRegistrar,
|
|
336
342
|
): (...args: unknown[]) => void {
|
|
337
|
-
const
|
|
343
|
+
const mockCallback = function mockFn(): void {
|
|
338
344
|
// No-op mock function
|
|
339
345
|
}
|
|
340
|
-
Object.defineProperty(
|
|
341
|
-
if (registrar) registrar.registerFunction(path, propName,
|
|
342
|
-
return
|
|
346
|
+
Object.defineProperty(mockCallback, 'name', { value: `mock_${propName}` })
|
|
347
|
+
if (registrar) registrar.registerFunction(path, propName, mockCallback)
|
|
348
|
+
return mockCallback
|
|
343
349
|
}
|
|
344
350
|
|
|
345
351
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -358,12 +364,21 @@ function generateMockImage(): Record<string, unknown> {
|
|
|
358
364
|
}
|
|
359
365
|
|
|
360
366
|
function generateMockVideo(): Record<string, unknown> {
|
|
367
|
+
const videoUrl = `${faker.internet.url()}/video.mp4`
|
|
361
368
|
return {
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
369
|
+
uri: videoUrl,
|
|
370
|
+
sources: [
|
|
371
|
+
{
|
|
372
|
+
quality: '720p',
|
|
373
|
+
width: 1280,
|
|
374
|
+
height: 720,
|
|
375
|
+
types: [{ format: 'mp4', uri: videoUrl, url: videoUrl }],
|
|
376
|
+
},
|
|
377
|
+
],
|
|
378
|
+
adaptiveSources: [],
|
|
379
|
+
hasAudio: true,
|
|
380
|
+
fps: 30,
|
|
381
|
+
poster: generateMockImage(),
|
|
367
382
|
}
|
|
368
383
|
}
|
|
369
384
|
|
|
@@ -387,6 +402,7 @@ function generateMockVectorArt(): Record<string, unknown> {
|
|
|
387
402
|
return {
|
|
388
403
|
svgId: faker.string.uuid(),
|
|
389
404
|
url: `${faker.internet.url()}/icon.svg`,
|
|
405
|
+
svgContent: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/></svg>',
|
|
390
406
|
}
|
|
391
407
|
}
|
|
392
408
|
|
|
@@ -8,9 +8,14 @@ import { resolveType } from './utils/semantic-type-resolver'
|
|
|
8
8
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
9
9
|
|
|
10
10
|
const sharedPropFilter = (prop: PropItem) => {
|
|
11
|
-
// Exclude props
|
|
11
|
+
// Exclude props whose every declaration comes from a @types/* package (e.g. HTMLAttributes,
|
|
12
|
+
// React.HTMLProps). Props from non-@types node_modules (e.g. Wix component libraries) are
|
|
13
|
+
// intentionally kept so that props like `label` from StylableButtonPlatformProps are visible.
|
|
12
14
|
if (prop.declarations && prop.declarations.length > 0) {
|
|
13
|
-
return !prop.declarations.every((declaration) =>
|
|
15
|
+
return !prop.declarations.every((declaration) => {
|
|
16
|
+
const fileName = declaration.fileName
|
|
17
|
+
return fileName.includes('node_modules/@types/') || fileName.includes('node_modules/typescript/')
|
|
18
|
+
})
|
|
14
19
|
}
|
|
15
20
|
return true
|
|
16
21
|
}
|
|
@@ -42,7 +47,9 @@ export function extractAllComponentInfo(program: ts.Program, filePath: string):
|
|
|
42
47
|
const componentNameResolver = (_exp: ts.Symbol, source: ts.SourceFile): string | undefined => {
|
|
43
48
|
const moduleSymbol = checker.getSymbolAtLocation(source)
|
|
44
49
|
if (!moduleSymbol) return undefined
|
|
45
|
-
const defaultSym = checker
|
|
50
|
+
const defaultSym = checker
|
|
51
|
+
.getExportsOfModule(moduleSymbol)
|
|
52
|
+
.find((exportSymbol) => exportSymbol.getName() === 'default')
|
|
46
53
|
if (!defaultSym || !(defaultSym.getFlags() & ts.SymbolFlags.Alias)) return undefined
|
|
47
54
|
const aliased = checker.getAliasedSymbol(defaultSym)
|
|
48
55
|
const name = aliased.getName()
|
|
@@ -60,13 +67,25 @@ export function extractAllComponentInfo(program: ts.Program, filePath: string):
|
|
|
60
67
|
* Extracts component info for only the default-exported component in a file.
|
|
61
68
|
* Returns `undefined` if the file has no default export or if the default export
|
|
62
69
|
* is not a recognized React component.
|
|
70
|
+
*
|
|
71
|
+
* Handles the re-export case where the entry file only re-exports the component
|
|
72
|
+
* from another file (e.g. `export { Foo as default } from './Foo'`). In that
|
|
73
|
+
* situation react-docgen-typescript finds no component in the entry file, so we
|
|
74
|
+
* follow the re-export to the defining file and extract from there instead.
|
|
63
75
|
*/
|
|
64
76
|
export function extractDefaultComponentInfo(program: ts.Program, filePath: string): ComponentInfo | undefined {
|
|
65
77
|
const defaultName = findDefaultExportName(program, filePath)
|
|
66
78
|
if (!defaultName) return undefined
|
|
67
79
|
|
|
68
|
-
const
|
|
69
|
-
|
|
80
|
+
const found = extractAllComponentInfo(program, filePath).find((c) => c.componentName === defaultName)
|
|
81
|
+
if (found) return found
|
|
82
|
+
|
|
83
|
+
const definingFilePath = resolveDefaultReExportFilePath(program, filePath)
|
|
84
|
+
if (definingFilePath) {
|
|
85
|
+
return extractAllComponentInfo(program, definingFilePath).find((c) => c.componentName === defaultName)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return undefined
|
|
70
89
|
}
|
|
71
90
|
|
|
72
91
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -75,7 +94,7 @@ export function extractDefaultComponentInfo(program: ts.Program, filePath: strin
|
|
|
75
94
|
|
|
76
95
|
/**
|
|
77
96
|
* Resolves the name of the default-exported symbol in a file using the
|
|
78
|
-
* TypeScript type checker.
|
|
97
|
+
* TypeScript type checker, with an AST fallback for cross-module re-exports.
|
|
79
98
|
*
|
|
80
99
|
* Returns `undefined` for anonymous default exports (e.g. `export default () => ...`)
|
|
81
100
|
* where no stable name can be determined.
|
|
@@ -91,17 +110,83 @@ function findDefaultExportName(program: ts.Program, filePath: string): string |
|
|
|
91
110
|
const defaultSymbol = checker.getExportsOfModule(moduleSymbol).find((symbol) => symbol.getName() === 'default')
|
|
92
111
|
if (!defaultSymbol) return undefined
|
|
93
112
|
|
|
94
|
-
// For `export default Foo` the symbol is an alias — resolve it to get 'Foo'
|
|
95
113
|
if (defaultSymbol.getFlags() & ts.SymbolFlags.Alias) {
|
|
96
|
-
const
|
|
97
|
-
const name =
|
|
98
|
-
//
|
|
99
|
-
|
|
114
|
+
const aliasedSymbol = checker.getAliasedSymbol(defaultSymbol)
|
|
115
|
+
const name = aliasedSymbol.getName()
|
|
116
|
+
// Cross-module re-exports (e.g. `export { Foo as default } from './Foo'`) produce a
|
|
117
|
+
// transient alias with no declarations. Fall back to reading the name from the AST.
|
|
118
|
+
const hasDeclarations = (aliasedSymbol.getDeclarations()?.length ?? 0) > 0
|
|
119
|
+
if (name !== 'default' && hasDeclarations) return name
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return findDefaultReExportNameFromAST(sourceFile)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Scans the AST for a named re-export that aliases a symbol to `default`
|
|
127
|
+
* (i.e. `export { Foo as default } from './other'`) and returns the original
|
|
128
|
+
* symbol name (`Foo`).
|
|
129
|
+
*/
|
|
130
|
+
function findDefaultReExportNameFromAST(sourceFile: ts.SourceFile): string | undefined {
|
|
131
|
+
const specifier = findDefaultReExportSpecifier(sourceFile)
|
|
132
|
+
// propertyName is the original name before aliasing; when absent, the specifier
|
|
133
|
+
// name is both the local and exported name (which here would be 'default').
|
|
134
|
+
return specifier?.propertyName?.text
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Resolves the absolute file path of the module targeted by a default re-export.
|
|
139
|
+
*
|
|
140
|
+
* For `export { Foo as default } from './other'`, returns the resolved path of
|
|
141
|
+
* `'./other'`. Uses `ts.resolveModuleName` because the type checker's alias chain
|
|
142
|
+
* ends at a transient symbol with no declarations for cross-module re-exports.
|
|
143
|
+
*
|
|
144
|
+
* Returns `undefined` when the file has no default re-export or the module
|
|
145
|
+
* specifier cannot be resolved.
|
|
146
|
+
*/
|
|
147
|
+
function resolveDefaultReExportFilePath(program: ts.Program, filePath: string): string | undefined {
|
|
148
|
+
const sourceFile = program.getSourceFile(filePath)
|
|
149
|
+
if (!sourceFile) return undefined
|
|
150
|
+
|
|
151
|
+
const exportDeclaration = findDefaultReExportSpecifier(sourceFile)?.parent?.parent
|
|
152
|
+
if (!exportDeclaration || !ts.isExportDeclaration(exportDeclaration)) return undefined
|
|
153
|
+
if (!exportDeclaration.moduleSpecifier || !ts.isStringLiteral(exportDeclaration.moduleSpecifier)) return undefined
|
|
154
|
+
|
|
155
|
+
const resolveResult = ts.resolveModuleName(
|
|
156
|
+
exportDeclaration.moduleSpecifier.text,
|
|
157
|
+
filePath,
|
|
158
|
+
program.getCompilerOptions(),
|
|
159
|
+
ts.sys,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
const resolvedPath = resolveResult.resolvedModule?.resolvedFileName
|
|
163
|
+
if (resolvedPath && resolvedPath !== filePath) {
|
|
164
|
+
return resolvedPath
|
|
100
165
|
}
|
|
101
166
|
|
|
102
167
|
return undefined
|
|
103
168
|
}
|
|
104
169
|
|
|
170
|
+
/**
|
|
171
|
+
* Finds the `ExportSpecifier` node for the specifier that aliases something
|
|
172
|
+
* to `default` in a re-export declaration (e.g. the `Foo as default` part of
|
|
173
|
+
* `export { Foo as default } from './other'`).
|
|
174
|
+
*/
|
|
175
|
+
function findDefaultReExportSpecifier(sourceFile: ts.SourceFile): ts.ExportSpecifier | undefined {
|
|
176
|
+
for (const statement of sourceFile.statements) {
|
|
177
|
+
if (!ts.isExportDeclaration(statement)) continue
|
|
178
|
+
if (!statement.moduleSpecifier) continue
|
|
179
|
+
if (!statement.exportClause || !ts.isNamedExports(statement.exportClause)) continue
|
|
180
|
+
|
|
181
|
+
for (const specifier of statement.exportClause.elements) {
|
|
182
|
+
if (specifier.name.text === 'default') {
|
|
183
|
+
return specifier
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return undefined
|
|
188
|
+
}
|
|
189
|
+
|
|
105
190
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
106
191
|
// ComponentDoc → ComponentInfo conversion
|
|
107
192
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -132,7 +217,7 @@ function convertComponentDoc(doc: ComponentDoc, program: ts.Program, checker: ts
|
|
|
132
217
|
|
|
133
218
|
const typeString = declaredTypeInfo?.name ?? propItem.type.name
|
|
134
219
|
const resolvedType = propType
|
|
135
|
-
? resolveType(propType, checker,
|
|
220
|
+
? resolveType({ type: propType, checker, typeString, declaredSymbol: declaredTypeInfo?.symbol })
|
|
136
221
|
: { kind: 'primitive' as const, value: propItem.type.name }
|
|
137
222
|
|
|
138
223
|
// Convert default value
|
|
@@ -119,18 +119,49 @@ function checkForReactNode(type: ts.Type, checker: ts.TypeChecker): boolean {
|
|
|
119
119
|
return false
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
-
|
|
123
|
-
type: ts.Type
|
|
124
|
-
checker: ts.TypeChecker
|
|
122
|
+
interface ResolveTypeOptions {
|
|
123
|
+
type: ts.Type
|
|
124
|
+
checker: ts.TypeChecker
|
|
125
|
+
depth?: number
|
|
126
|
+
typeString?: string
|
|
127
|
+
declaredSymbol?: ts.Symbol
|
|
128
|
+
seenTypes?: Set<ts.Type>
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function resolveType({
|
|
132
|
+
type,
|
|
133
|
+
checker,
|
|
125
134
|
depth = 0,
|
|
126
|
-
typeString
|
|
127
|
-
declaredSymbol
|
|
128
|
-
|
|
129
|
-
|
|
135
|
+
typeString,
|
|
136
|
+
declaredSymbol,
|
|
137
|
+
seenTypes = new Set(),
|
|
138
|
+
}: ResolveTypeOptions): ResolvedType {
|
|
139
|
+
// Prevent infinite recursion on self-referential types
|
|
140
|
+
if (seenTypes.has(type)) {
|
|
141
|
+
return { kind: 'primitive', value: 'any' }
|
|
142
|
+
}
|
|
130
143
|
if (depth > MAX_RESOLVE_DEPTH) {
|
|
131
144
|
throw new Error(`Max resolve depth reached for type: ${checker.typeToString(type)}`)
|
|
132
145
|
}
|
|
146
|
+
// Track the type while we recurse into it. We delete it after resolution so
|
|
147
|
+
// that sibling properties sharing the same ts.Type (e.g. two `string` props)
|
|
148
|
+
// are not misidentified as cycles. Only types currently on the call stack
|
|
149
|
+
// trigger the cycle guard above.
|
|
150
|
+
seenTypes.add(type)
|
|
151
|
+
const result = resolveTypeBody({ type, checker, depth, typeString, declaredSymbol, seenTypes })
|
|
152
|
+
seenTypes.delete(type)
|
|
153
|
+
return result
|
|
154
|
+
}
|
|
133
155
|
|
|
156
|
+
function resolveTypeBody({
|
|
157
|
+
type,
|
|
158
|
+
checker,
|
|
159
|
+
depth,
|
|
160
|
+
typeString,
|
|
161
|
+
declaredSymbol,
|
|
162
|
+
seenTypes,
|
|
163
|
+
}: Required<Pick<ResolveTypeOptions, 'type' | 'checker' | 'seenTypes'>> &
|
|
164
|
+
Pick<ResolveTypeOptions, 'typeString' | 'declaredSymbol'> & { depth: number }): ResolvedType {
|
|
134
165
|
// Check for semantic types from React or Wix packages
|
|
135
166
|
const semanticType = checkForSemanticType(type, checker)
|
|
136
167
|
if (semanticType) {
|
|
@@ -182,17 +213,17 @@ export function resolveType(
|
|
|
182
213
|
const resolvers = [
|
|
183
214
|
() => resolvePrimitiveType(type),
|
|
184
215
|
() => resolveLiteralType(type, checker),
|
|
185
|
-
() => resolveUnionType(type, checker, depth),
|
|
186
|
-
() => resolveIntersectionType(type, checker, depth),
|
|
187
|
-
() => resolveArrayType(type, checker, depth),
|
|
216
|
+
() => resolveUnionType(type, checker, depth, seenTypes),
|
|
217
|
+
() => resolveIntersectionType(type, checker, depth, seenTypes),
|
|
218
|
+
() => resolveArrayType(type, checker, depth, seenTypes),
|
|
188
219
|
() => resolveFunctionType(type, checker),
|
|
189
|
-
() => resolveObjectType(type, checker, depth),
|
|
220
|
+
() => resolveObjectType(type, checker, depth, seenTypes),
|
|
190
221
|
]
|
|
191
222
|
|
|
192
223
|
for (const resolver of resolvers) {
|
|
193
|
-
const
|
|
194
|
-
if (
|
|
195
|
-
return
|
|
224
|
+
const resolved = resolver()
|
|
225
|
+
if (resolved) {
|
|
226
|
+
return resolved
|
|
196
227
|
}
|
|
197
228
|
}
|
|
198
229
|
|
|
@@ -253,36 +284,55 @@ function resolveLiteralType(type: ts.Type, checker: ts.TypeChecker): ResolvedTyp
|
|
|
253
284
|
return undefined
|
|
254
285
|
}
|
|
255
286
|
|
|
256
|
-
function resolveUnionType(
|
|
287
|
+
function resolveUnionType(
|
|
288
|
+
type: ts.Type,
|
|
289
|
+
checker: ts.TypeChecker,
|
|
290
|
+
depth: number,
|
|
291
|
+
seenTypes: Set<ts.Type>,
|
|
292
|
+
): ResolvedType | undefined {
|
|
257
293
|
if (type.flags & ts.TypeFlags.Union) {
|
|
258
294
|
const unionType = type as ts.UnionType
|
|
259
295
|
return {
|
|
260
296
|
kind: 'union',
|
|
261
|
-
types: unionType.types.map((
|
|
297
|
+
types: unionType.types.map((memberType) =>
|
|
298
|
+
resolveType({ type: memberType, checker, depth: depth + 1, seenTypes }),
|
|
299
|
+
),
|
|
262
300
|
}
|
|
263
301
|
}
|
|
264
302
|
return undefined
|
|
265
303
|
}
|
|
266
304
|
|
|
267
|
-
function resolveIntersectionType(
|
|
305
|
+
function resolveIntersectionType(
|
|
306
|
+
type: ts.Type,
|
|
307
|
+
checker: ts.TypeChecker,
|
|
308
|
+
depth: number,
|
|
309
|
+
seenTypes: Set<ts.Type>,
|
|
310
|
+
): ResolvedType | undefined {
|
|
268
311
|
if (type.flags & ts.TypeFlags.Intersection) {
|
|
269
312
|
const intersectionType = type as ts.IntersectionType
|
|
270
313
|
return {
|
|
271
314
|
kind: 'intersection',
|
|
272
|
-
types: intersectionType.types.map((
|
|
315
|
+
types: intersectionType.types.map((memberType) =>
|
|
316
|
+
resolveType({ type: memberType, checker, depth: depth + 1, seenTypes }),
|
|
317
|
+
),
|
|
273
318
|
}
|
|
274
319
|
}
|
|
275
320
|
return undefined
|
|
276
321
|
}
|
|
277
322
|
|
|
278
|
-
function resolveArrayType(
|
|
323
|
+
function resolveArrayType(
|
|
324
|
+
type: ts.Type,
|
|
325
|
+
checker: ts.TypeChecker,
|
|
326
|
+
depth: number,
|
|
327
|
+
seenTypes: Set<ts.Type>,
|
|
328
|
+
): ResolvedType | undefined {
|
|
279
329
|
if (checker.isArrayType(type)) {
|
|
280
330
|
const arrayType = type as ts.TypeReference
|
|
281
331
|
const typeArgs = checker.getTypeArguments(arrayType)
|
|
282
332
|
if (typeArgs.length > 0) {
|
|
283
333
|
return {
|
|
284
334
|
kind: 'array',
|
|
285
|
-
elementType: resolveType(typeArgs[0], checker, depth + 1),
|
|
335
|
+
elementType: resolveType({ type: typeArgs[0], checker, depth: depth + 1, seenTypes }),
|
|
286
336
|
}
|
|
287
337
|
}
|
|
288
338
|
return { kind: 'array', elementType: { kind: 'primitive', value: 'any' } }
|
|
@@ -314,7 +364,12 @@ function resolveFunctionType(type: ts.Type, checker: ts.TypeChecker): ResolvedTy
|
|
|
314
364
|
return undefined
|
|
315
365
|
}
|
|
316
366
|
|
|
317
|
-
function resolveObjectType(
|
|
367
|
+
function resolveObjectType(
|
|
368
|
+
type: ts.Type,
|
|
369
|
+
checker: ts.TypeChecker,
|
|
370
|
+
depth: number,
|
|
371
|
+
seenTypes: Set<ts.Type>,
|
|
372
|
+
): ResolvedType | undefined {
|
|
318
373
|
const properties = type.getProperties()
|
|
319
374
|
if (properties.length === 0) {
|
|
320
375
|
return undefined
|
|
@@ -337,7 +392,7 @@ function resolveObjectType(type: ts.Type, checker: ts.TypeChecker, depth: number
|
|
|
337
392
|
name: propName,
|
|
338
393
|
type: checker.typeToString(propType),
|
|
339
394
|
required: !isOptional,
|
|
340
|
-
resolvedType: resolveType(propType, checker, depth + 1),
|
|
395
|
+
resolvedType: resolveType({ type: propType, checker, depth: depth + 1, seenTypes }),
|
|
341
396
|
}
|
|
342
397
|
}
|
|
343
398
|
|
package/src/manifest-pipeline.ts
CHANGED
|
@@ -27,6 +27,7 @@ export interface ExtractionWarning {
|
|
|
27
27
|
componentName: string
|
|
28
28
|
phase: 'render' | 'coupling' | 'css' | 'loader' | 'conversion'
|
|
29
29
|
error: string
|
|
30
|
+
stack?: string
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
export interface ExtractedCssInfo {
|
|
@@ -84,6 +85,7 @@ export function processComponent(
|
|
|
84
85
|
componentName: componentInfo.componentName,
|
|
85
86
|
phase: 'loader',
|
|
86
87
|
error: `Failed to load "${componentInfo.componentName}": ${error instanceof Error ? error.message : String(error)}`,
|
|
88
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
87
89
|
})
|
|
88
90
|
}
|
|
89
91
|
}
|
|
@@ -116,6 +118,7 @@ export function processComponent(
|
|
|
116
118
|
componentName: componentInfo.componentName,
|
|
117
119
|
phase: 'render',
|
|
118
120
|
error: error instanceof Error ? error.message : String(error),
|
|
121
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
119
122
|
})
|
|
120
123
|
}
|
|
121
124
|
}
|
|
@@ -156,6 +159,7 @@ export function processComponent(
|
|
|
156
159
|
componentName: componentInfo.componentName,
|
|
157
160
|
phase: 'css',
|
|
158
161
|
error: `CSS selector matching failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
162
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
159
163
|
})
|
|
160
164
|
}
|
|
161
165
|
}
|
|
@@ -360,6 +364,7 @@ function extractCssInfo(
|
|
|
360
364
|
componentName,
|
|
361
365
|
phase: 'css',
|
|
362
366
|
error: `Failed to parse ${cssPath}: ${error instanceof Error ? error.message : String(error)}`,
|
|
367
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
363
368
|
})
|
|
364
369
|
}
|
|
365
370
|
}
|
package/src/ts-compiler.ts
CHANGED
|
@@ -30,12 +30,5 @@ export function compileTsFile(
|
|
|
30
30
|
cause: error as Error,
|
|
31
31
|
props: { phase: 'compile' },
|
|
32
32
|
}),
|
|
33
|
-
).map(({
|
|
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
|
-
})
|
|
33
|
+
).map(({ result }) => ts.createProgram([filePath], result.options))
|
|
41
34
|
}
|
package/vite.config.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { resolve } from 'node:path'
|
|
|
3
3
|
import { defineConfig } from 'vite'
|
|
4
4
|
import dts from 'vite-plugin-dts'
|
|
5
5
|
|
|
6
|
-
export default defineConfig({
|
|
6
|
+
export default defineConfig(({ mode }) => ({
|
|
7
7
|
plugins: [
|
|
8
8
|
dts({
|
|
9
9
|
include: ['src/**/*.ts'],
|
|
@@ -11,6 +11,7 @@ export default defineConfig({
|
|
|
11
11
|
}),
|
|
12
12
|
],
|
|
13
13
|
build: {
|
|
14
|
+
minify: mode !== 'development',
|
|
14
15
|
lib: {
|
|
15
16
|
entry: {
|
|
16
17
|
index: resolve(__dirname, 'src/index.ts'),
|
|
@@ -48,4 +49,4 @@ export default defineConfig({
|
|
|
48
49
|
test: {
|
|
49
50
|
globals: true,
|
|
50
51
|
},
|
|
51
|
-
})
|
|
52
|
+
}))
|