@wix/zero-config-implementation 1.14.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.
@@ -1,3 +1,12 @@
1
1
  import { default as ts } from 'typescript';
2
2
  import { ResolvedType } from '../types';
3
- export declare function resolveType(type: ts.Type, checker: ts.TypeChecker, depth?: number, typeString?: string, declaredSymbol?: ts.Symbol): ResolvedType;
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 {};
@@ -7,6 +7,7 @@ export interface ExtractionWarning {
7
7
  componentName: string;
8
8
  phase: 'render' | 'coupling' | 'css' | 'loader' | 'conversion';
9
9
  error: string;
10
+ stack?: string;
10
11
  }
11
12
  export interface ExtractedCssInfo {
12
13
  filePath: string;
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "registry": "https://registry.npmjs.org/",
5
5
  "access": "public"
6
6
  },
7
- "version": "1.14.0",
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": "1365e0b6dc9053e4dd5346884137f7cc61e12eed845e32e0e86a6122"
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) => ({ moduleExports, loaderError: null as string | null }))
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({ moduleExports: null as Record<string, unknown> | null, loaderError: err.message }),
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((t) => t.kind === 'literal' && typeof t.value === 'string')
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
- // Otherwise, use the first type
245
- if (types.length > 0) {
246
- return generateValueFromResolvedType(types[0], propName, path, registrar)
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((t) => t.kind === 'literal')
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 fn = function mockFn(): void {
343
+ const mockCallback = function mockFn(): void {
338
344
  // No-op mock function
339
345
  }
340
- Object.defineProperty(fn, 'name', { value: `mock_${propName}` })
341
- if (registrar) registrar.registerFunction(path, propName, fn)
342
- return fn
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
- url: `${faker.internet.url()}/video.mp4`,
363
- width: faker.number.int({ min: 640, max: 1920 }),
364
- height: faker.number.int({ min: 480, max: 1080 }),
365
- duration: faker.number.int({ min: 10, max: 300 }),
366
- name: faker.lorem.words(2),
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 inherited from node_modules (e.g. HTMLAttributes)
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) => declaration.fileName.includes('node_modules'))
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.getExportsOfModule(moduleSymbol).find((s) => s.getName() === 'default')
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()
@@ -210,7 +217,7 @@ function convertComponentDoc(doc: ComponentDoc, program: ts.Program, checker: ts
210
217
 
211
218
  const typeString = declaredTypeInfo?.name ?? propItem.type.name
212
219
  const resolvedType = propType
213
- ? resolveType(propType, checker, 0, typeString, declaredTypeInfo?.symbol)
220
+ ? resolveType({ type: propType, checker, typeString, declaredSymbol: declaredTypeInfo?.symbol })
214
221
  : { kind: 'primitive' as const, value: propItem.type.name }
215
222
 
216
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
- export function resolveType(
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?: string,
127
- declaredSymbol?: ts.Symbol,
128
- ): ResolvedType {
129
- // Prevent infinite recursion
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 result = resolver()
194
- if (result) {
195
- return result
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(type: ts.Type, checker: ts.TypeChecker, depth: number): ResolvedType | undefined {
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((t) => resolveType(t, checker, depth + 1)),
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(type: ts.Type, checker: ts.TypeChecker, depth: number): ResolvedType | undefined {
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((t) => resolveType(t, checker, depth + 1)),
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(type: ts.Type, checker: ts.TypeChecker, depth: number): ResolvedType | undefined {
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(type: ts.Type, checker: ts.TypeChecker, depth: number): ResolvedType | undefined {
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
 
@@ -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/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
+ }))