reactolith 1.0.19
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/LICENSE +21 -0
- package/README.md +523 -0
- package/dist/cli/generate-web-types.cjs +607 -0
- package/dist/cli/generate-web-types.cjs.map +1 -0
- package/dist/index.cjs +611 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +236 -0
- package/dist/index.mjs +599 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +88 -0
|
@@ -0,0 +1,607 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var tsMorph = require('ts-morph');
|
|
5
|
+
var fs = require('fs');
|
|
6
|
+
var path = require('path');
|
|
7
|
+
|
|
8
|
+
function generateWebTypes(options) {
|
|
9
|
+
const project = new tsMorph.Project({
|
|
10
|
+
tsConfigFilePath: options.tsconfig || "./tsconfig.json",
|
|
11
|
+
});
|
|
12
|
+
const componentsDir = path.resolve(options.componentsDir || "components/ui");
|
|
13
|
+
const files = fs
|
|
14
|
+
.readdirSync(componentsDir)
|
|
15
|
+
.filter((f) => f.endsWith(".tsx") || f.endsWith(".ts"));
|
|
16
|
+
const elements = [];
|
|
17
|
+
const prefix = options.prefix || "";
|
|
18
|
+
files.forEach((file) => {
|
|
19
|
+
const filePath = path.join(componentsDir, file);
|
|
20
|
+
const sourceFile = project.addSourceFileAtPath(filePath);
|
|
21
|
+
if (!sourceFile)
|
|
22
|
+
return;
|
|
23
|
+
// Strategy 1: Look for exported *Props types (existing behavior)
|
|
24
|
+
const propsFromTypes = extractFromExportedPropsTypes(sourceFile);
|
|
25
|
+
// Strategy 2: Look for exported React components and extract their props
|
|
26
|
+
const propsFromComponents = extractFromComponentFunctions(sourceFile);
|
|
27
|
+
// Merge results, preferring explicit Props types
|
|
28
|
+
const componentMap = new Map();
|
|
29
|
+
propsFromComponents.forEach((info) => {
|
|
30
|
+
componentMap.set(info.name, info);
|
|
31
|
+
});
|
|
32
|
+
propsFromTypes.forEach((info) => {
|
|
33
|
+
componentMap.set(info.name, info);
|
|
34
|
+
});
|
|
35
|
+
componentMap.forEach((info) => {
|
|
36
|
+
if (info.propsType === undefined)
|
|
37
|
+
return;
|
|
38
|
+
const { attributes, slots } = extractAttributesAndSlots(info.propsType, info.propsNode || info.sourceFile);
|
|
39
|
+
const tagName = prefix +
|
|
40
|
+
info.name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
|
|
41
|
+
const element = {
|
|
42
|
+
name: tagName,
|
|
43
|
+
description: `${info.name} component`,
|
|
44
|
+
attributes,
|
|
45
|
+
};
|
|
46
|
+
// Only add slots if there are any
|
|
47
|
+
if (slots.length > 0) {
|
|
48
|
+
element.slots = slots;
|
|
49
|
+
}
|
|
50
|
+
elements.push(element);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
elements.sort((a, b) => a.name.localeCompare(b.name));
|
|
54
|
+
const webTypes = {
|
|
55
|
+
$schema: "https://raw.githubusercontent.com/JetBrains/web-types/master/schema/web-types.json",
|
|
56
|
+
name: options.libraryName || "reactolith-components",
|
|
57
|
+
version: options.libraryVersion || "1.0.0",
|
|
58
|
+
"js-types-syntax": "typescript",
|
|
59
|
+
"description-markup": "markdown",
|
|
60
|
+
contributions: {
|
|
61
|
+
html: {
|
|
62
|
+
elements,
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
const outFile = options.outFile || "web-types.json";
|
|
67
|
+
fs.writeFileSync(outFile, JSON.stringify(webTypes, null, 2));
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Strategy 1: Extract from exported types ending with "Props"
|
|
71
|
+
*/
|
|
72
|
+
function extractFromExportedPropsTypes(sourceFile) {
|
|
73
|
+
const results = [];
|
|
74
|
+
const exported = sourceFile.getExportedDeclarations();
|
|
75
|
+
for (const [name, decls] of exported) {
|
|
76
|
+
if (!name.endsWith("Props"))
|
|
77
|
+
continue;
|
|
78
|
+
const decl = decls[0];
|
|
79
|
+
if (!decl)
|
|
80
|
+
continue;
|
|
81
|
+
const type = decl.getType?.();
|
|
82
|
+
if (!type)
|
|
83
|
+
continue;
|
|
84
|
+
const componentName = name.substring(0, name.length - 5);
|
|
85
|
+
results.push({
|
|
86
|
+
name: componentName,
|
|
87
|
+
propsType: type,
|
|
88
|
+
propsNode: decl,
|
|
89
|
+
sourceFile,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
return results;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Strategy 2: Extract props from exported React component functions
|
|
96
|
+
*/
|
|
97
|
+
function extractFromComponentFunctions(sourceFile) {
|
|
98
|
+
const results = [];
|
|
99
|
+
const exported = sourceFile.getExportedDeclarations();
|
|
100
|
+
for (const [name, decls] of exported) {
|
|
101
|
+
// Skip Props types (handled by Strategy 1)
|
|
102
|
+
if (name.endsWith("Props"))
|
|
103
|
+
continue;
|
|
104
|
+
// Skip non-PascalCase names (not React components)
|
|
105
|
+
if (!/^[A-Z]/.test(name))
|
|
106
|
+
continue;
|
|
107
|
+
const decl = decls[0];
|
|
108
|
+
if (!decl)
|
|
109
|
+
continue;
|
|
110
|
+
const propsInfo = extractPropsFromDeclaration(decl);
|
|
111
|
+
if (propsInfo) {
|
|
112
|
+
results.push({
|
|
113
|
+
name,
|
|
114
|
+
propsType: propsInfo.type,
|
|
115
|
+
propsNode: propsInfo.node,
|
|
116
|
+
sourceFile,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// Also check for default export
|
|
121
|
+
const defaultExport = sourceFile.getDefaultExportSymbol();
|
|
122
|
+
if (defaultExport) {
|
|
123
|
+
const decls = defaultExport.getDeclarations();
|
|
124
|
+
for (const decl of decls) {
|
|
125
|
+
let propsInfo = extractPropsFromDeclaration(decl);
|
|
126
|
+
// If no props found directly, check if it's an ExportAssignment with an Identifier
|
|
127
|
+
if (!propsInfo && tsMorph.Node.isExportAssignment(decl)) {
|
|
128
|
+
const expr = decl.getExpression?.();
|
|
129
|
+
if (expr && tsMorph.Node.isIdentifier(expr)) {
|
|
130
|
+
// Try to resolve the identifier to its declaration
|
|
131
|
+
propsInfo = resolveIdentifierToProps(expr, sourceFile);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (propsInfo) {
|
|
135
|
+
// Use filename as component name for default exports, converting kebab-case to PascalCase
|
|
136
|
+
const fileName = path.basename(sourceFile.getFilePath(), path.extname(sourceFile.getFilePath()));
|
|
137
|
+
const componentName = fileName
|
|
138
|
+
.split("-")
|
|
139
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
140
|
+
.join("");
|
|
141
|
+
results.push({
|
|
142
|
+
name: componentName,
|
|
143
|
+
propsType: propsInfo.type,
|
|
144
|
+
propsNode: propsInfo.node,
|
|
145
|
+
sourceFile,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return results;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Resolve an identifier to its props - handles local variables and imports
|
|
154
|
+
*/
|
|
155
|
+
function resolveIdentifierToProps(identifier, sourceFile) {
|
|
156
|
+
const identifierName = identifier.getText();
|
|
157
|
+
// First, check if it's a local variable declaration
|
|
158
|
+
const localVar = sourceFile.getVariableDeclaration(identifierName);
|
|
159
|
+
if (localVar) {
|
|
160
|
+
return extractPropsFromDeclaration(localVar);
|
|
161
|
+
}
|
|
162
|
+
// Check if it's a local function declaration
|
|
163
|
+
const localFunc = sourceFile.getFunction(identifierName);
|
|
164
|
+
if (localFunc) {
|
|
165
|
+
return extractPropsFromDeclaration(localFunc);
|
|
166
|
+
}
|
|
167
|
+
// Check if it's an imported symbol - try to get props directly from the type
|
|
168
|
+
const identifierType = identifier.getType();
|
|
169
|
+
const callSignatures = identifierType.getCallSignatures();
|
|
170
|
+
if (callSignatures.length > 0) {
|
|
171
|
+
const sig = callSignatures[0];
|
|
172
|
+
const params = sig.getParameters();
|
|
173
|
+
if (params.length > 0) {
|
|
174
|
+
const firstParam = params[0];
|
|
175
|
+
const paramType = firstParam.getTypeAtLocation(identifier);
|
|
176
|
+
const returnType = sig.getReturnType();
|
|
177
|
+
if (isJsxReturnType(returnType)) {
|
|
178
|
+
return { type: paramType, node: identifier };
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
// No params but returns JSX - component without props
|
|
183
|
+
const returnType = sig.getReturnType();
|
|
184
|
+
if (isJsxReturnType(returnType)) {
|
|
185
|
+
return { type: null, node: identifier };
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
// Fallback: try to resolve from the imported module source file
|
|
190
|
+
const importDecls = sourceFile.getImportDeclarations();
|
|
191
|
+
for (const importDecl of importDecls) {
|
|
192
|
+
// Check named imports
|
|
193
|
+
const namedImports = importDecl.getNamedImports();
|
|
194
|
+
for (const namedImport of namedImports) {
|
|
195
|
+
const importedName = namedImport.getAliasNode()?.getText() || namedImport.getName();
|
|
196
|
+
if (importedName === identifierName) {
|
|
197
|
+
// Found the import - resolve from the imported module
|
|
198
|
+
return resolveImportedComponent(importDecl, namedImport.getName());
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
// Check default import
|
|
202
|
+
const defaultImport = importDecl.getDefaultImport();
|
|
203
|
+
if (defaultImport && defaultImport.getText() === identifierName) {
|
|
204
|
+
return resolveImportedComponent(importDecl, "default");
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Resolve props from an imported component
|
|
211
|
+
*/
|
|
212
|
+
function resolveImportedComponent(importDecl, exportName, _currentSourceFile) {
|
|
213
|
+
try {
|
|
214
|
+
const resolvedModule = importDecl.getModuleSpecifierSourceFile();
|
|
215
|
+
if (!resolvedModule) {
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
// Get the exported declaration from the module
|
|
219
|
+
const exported = resolvedModule.getExportedDeclarations();
|
|
220
|
+
if (exportName === "default") {
|
|
221
|
+
const defaultExport = resolvedModule.getDefaultExportSymbol();
|
|
222
|
+
if (defaultExport) {
|
|
223
|
+
const decls = defaultExport.getDeclarations();
|
|
224
|
+
for (const decl of decls) {
|
|
225
|
+
const propsInfo = extractPropsFromDeclaration(decl);
|
|
226
|
+
if (propsInfo)
|
|
227
|
+
return propsInfo;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
const decls = exported.get(exportName);
|
|
233
|
+
if (decls && decls.length > 0) {
|
|
234
|
+
for (const decl of decls) {
|
|
235
|
+
const propsInfo = extractPropsFromDeclaration(decl);
|
|
236
|
+
if (propsInfo)
|
|
237
|
+
return propsInfo;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
// Module resolution failed - this is okay for external modules
|
|
244
|
+
}
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Extract props type from a function/variable declaration
|
|
249
|
+
*/
|
|
250
|
+
function extractPropsFromDeclaration(decl) {
|
|
251
|
+
// Handle function declarations: export function Button(props: ButtonProps) {}
|
|
252
|
+
if (tsMorph.Node.isFunctionDeclaration(decl)) {
|
|
253
|
+
return extractPropsFromFunction(decl);
|
|
254
|
+
}
|
|
255
|
+
// Handle variable declarations: export const Button = (props: ButtonProps) => {}
|
|
256
|
+
if (tsMorph.Node.isVariableDeclaration(decl)) {
|
|
257
|
+
const varDecl = decl;
|
|
258
|
+
const initializer = varDecl.getInitializer();
|
|
259
|
+
if (initializer && tsMorph.Node.isArrowFunction(initializer)) {
|
|
260
|
+
return extractPropsFromArrowFunction(initializer);
|
|
261
|
+
}
|
|
262
|
+
if (initializer && tsMorph.Node.isFunctionExpression(initializer)) {
|
|
263
|
+
return extractPropsFromFunctionExpression(initializer);
|
|
264
|
+
}
|
|
265
|
+
// Handle React.forwardRef, React.memo, etc.
|
|
266
|
+
if (initializer && tsMorph.Node.isCallExpression(initializer)) {
|
|
267
|
+
const args = initializer.getArguments();
|
|
268
|
+
for (const arg of args) {
|
|
269
|
+
if (tsMorph.Node.isArrowFunction(arg)) {
|
|
270
|
+
return extractPropsFromArrowFunction(arg);
|
|
271
|
+
}
|
|
272
|
+
if (tsMorph.Node.isFunctionExpression(arg)) {
|
|
273
|
+
return extractPropsFromFunctionExpression(arg);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
// Handle property access expressions: const Select = SelectPrimitive.Root
|
|
278
|
+
if (initializer && (tsMorph.Node.isPropertyAccessExpression(initializer) || tsMorph.Node.isIdentifier(initializer))) {
|
|
279
|
+
// Try to get the type from the variable declaration
|
|
280
|
+
const varType = varDecl.getType();
|
|
281
|
+
// Check if this is a React component type (has Props in the call signature)
|
|
282
|
+
const callSignatures = varType.getCallSignatures();
|
|
283
|
+
if (callSignatures.length > 0) {
|
|
284
|
+
const sig = callSignatures[0];
|
|
285
|
+
const params = sig.getParameters();
|
|
286
|
+
if (params.length > 0) {
|
|
287
|
+
const firstParam = params[0];
|
|
288
|
+
const paramType = firstParam.getTypeAtLocation(varDecl);
|
|
289
|
+
const returnType = sig.getReturnType();
|
|
290
|
+
if (isJsxReturnType(returnType)) {
|
|
291
|
+
return { type: paramType, node: varDecl };
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
// Handle export default function() {}
|
|
298
|
+
if (tsMorph.Node.isExportAssignment(decl)) {
|
|
299
|
+
const expr = decl.getExpression?.();
|
|
300
|
+
if (expr) {
|
|
301
|
+
if (tsMorph.Node.isArrowFunction(expr)) {
|
|
302
|
+
return extractPropsFromArrowFunction(expr);
|
|
303
|
+
}
|
|
304
|
+
if (tsMorph.Node.isFunctionExpression(expr)) {
|
|
305
|
+
return extractPropsFromFunctionExpression(expr);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
function extractPropsFromFunction(func) {
|
|
312
|
+
// Check if this looks like a React component (returns JSX)
|
|
313
|
+
const returnType = func.getReturnType();
|
|
314
|
+
if (!isJsxReturnType(returnType))
|
|
315
|
+
return null;
|
|
316
|
+
const params = func.getParameters();
|
|
317
|
+
if (params.length === 0) {
|
|
318
|
+
// No params - return empty props type
|
|
319
|
+
return { type: null, node: func };
|
|
320
|
+
}
|
|
321
|
+
const firstParam = params[0];
|
|
322
|
+
const type = firstParam.getType();
|
|
323
|
+
return { type, node: firstParam };
|
|
324
|
+
}
|
|
325
|
+
function extractPropsFromArrowFunction(func) {
|
|
326
|
+
// Check if this looks like a React component (returns JSX)
|
|
327
|
+
const returnType = func.getReturnType();
|
|
328
|
+
if (!isJsxReturnType(returnType))
|
|
329
|
+
return null;
|
|
330
|
+
const params = func.getParameters();
|
|
331
|
+
if (params.length === 0) {
|
|
332
|
+
// No params - return empty props type
|
|
333
|
+
return { type: null, node: func };
|
|
334
|
+
}
|
|
335
|
+
const firstParam = params[0];
|
|
336
|
+
const type = firstParam.getType();
|
|
337
|
+
return { type, node: firstParam };
|
|
338
|
+
}
|
|
339
|
+
function extractPropsFromFunctionExpression(func) {
|
|
340
|
+
// Check if this looks like a React component (returns JSX)
|
|
341
|
+
const returnType = func.getReturnType();
|
|
342
|
+
if (!isJsxReturnType(returnType))
|
|
343
|
+
return null;
|
|
344
|
+
const params = func.getParameters();
|
|
345
|
+
if (params.length === 0) {
|
|
346
|
+
// No params - return empty props type
|
|
347
|
+
return { type: null, node: func };
|
|
348
|
+
}
|
|
349
|
+
const firstParam = params[0];
|
|
350
|
+
const type = firstParam.getType();
|
|
351
|
+
return { type, node: firstParam };
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Check if a return type looks like JSX (React.ReactElement, JSX.Element, etc.)
|
|
355
|
+
*/
|
|
356
|
+
function isJsxReturnType(type) {
|
|
357
|
+
const text = type.getText();
|
|
358
|
+
return (text.includes("Element") ||
|
|
359
|
+
text.includes("ReactNode") ||
|
|
360
|
+
text.includes("ReactElement") ||
|
|
361
|
+
text.includes("JSX") ||
|
|
362
|
+
text === "null" ||
|
|
363
|
+
text.includes("| null"));
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Check if a type represents a React slot (ReactNode, ReactElement, etc.)
|
|
367
|
+
*/
|
|
368
|
+
function isSlotType(typeText) {
|
|
369
|
+
// Exact patterns that indicate a slot type
|
|
370
|
+
const slotPatterns = [
|
|
371
|
+
"ReactNode",
|
|
372
|
+
"ReactElement",
|
|
373
|
+
"JSX.Element",
|
|
374
|
+
];
|
|
375
|
+
// Check if type is a slot type (but not a function returning ReactNode or event handler)
|
|
376
|
+
const isSlot = slotPatterns.some((pattern) => typeText.includes(pattern));
|
|
377
|
+
// Exclude event handlers and functions
|
|
378
|
+
const isFunction = typeText.includes("=>") ||
|
|
379
|
+
typeText.includes("EventHandler") ||
|
|
380
|
+
typeText.includes("Handler<");
|
|
381
|
+
return isSlot && !isFunction;
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Extract attributes and slots from a Type
|
|
385
|
+
*/
|
|
386
|
+
function extractAttributesAndSlots(type, contextNode) {
|
|
387
|
+
const attributes = [];
|
|
388
|
+
const slots = [];
|
|
389
|
+
// Handle null type (components with no props)
|
|
390
|
+
if (!type) {
|
|
391
|
+
return { attributes, slots };
|
|
392
|
+
}
|
|
393
|
+
type.getProperties().forEach((prop) => {
|
|
394
|
+
const propName = prop.getName();
|
|
395
|
+
// Skip internal React props
|
|
396
|
+
if (["key", "ref"].includes(propName))
|
|
397
|
+
return;
|
|
398
|
+
const propType = prop.getTypeAtLocation(contextNode);
|
|
399
|
+
const typeText = cleanTypeText(propType.getText());
|
|
400
|
+
const description = getPropertyDescription(prop);
|
|
401
|
+
const required = !prop.isOptional();
|
|
402
|
+
// Check if this prop is a slot (ReactNode type)
|
|
403
|
+
if (isSlotType(typeText)) {
|
|
404
|
+
// "children" becomes the "default" slot
|
|
405
|
+
const slotName = propName === "children" ? "default" : propName;
|
|
406
|
+
slots.push({
|
|
407
|
+
name: slotName,
|
|
408
|
+
description: description || `Content for the ${slotName} slot`,
|
|
409
|
+
});
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
// Skip children if it's not a ReactNode (e.g., string children)
|
|
413
|
+
if (propName === "children")
|
|
414
|
+
return;
|
|
415
|
+
// Regular attribute
|
|
416
|
+
const attr = {
|
|
417
|
+
name: toKebabCase(propName),
|
|
418
|
+
description: description || undefined,
|
|
419
|
+
required,
|
|
420
|
+
};
|
|
421
|
+
// Check for boolean types first (including optional booleans)
|
|
422
|
+
if (typeText === "boolean" || typeText === "boolean | undefined") {
|
|
423
|
+
// Boolean attributes can be used without value
|
|
424
|
+
attr.value = {
|
|
425
|
+
kind: "no-value",
|
|
426
|
+
type: "boolean",
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
else if (typeText.includes("|")) {
|
|
430
|
+
// Parse union types for enum-like values
|
|
431
|
+
const values = typeText
|
|
432
|
+
.split("|")
|
|
433
|
+
.map((v) => v.trim())
|
|
434
|
+
.filter((v) => v !== "undefined" && v !== "null");
|
|
435
|
+
// Check if all values are string literals (quoted strings only)
|
|
436
|
+
// Exclude primitive types like boolean, string, number, etc.
|
|
437
|
+
const primitiveTypes = [
|
|
438
|
+
"boolean",
|
|
439
|
+
"string",
|
|
440
|
+
"number",
|
|
441
|
+
"object",
|
|
442
|
+
"any",
|
|
443
|
+
"unknown",
|
|
444
|
+
"never",
|
|
445
|
+
];
|
|
446
|
+
const stringLiteralValues = values.filter((v) => /^["'].*["']$/.test(v));
|
|
447
|
+
const hasOnlyStringLiterals = stringLiteralValues.length === values.length && values.length > 0;
|
|
448
|
+
const hasOnlyPrimitives = values.every((v) => primitiveTypes.includes(v));
|
|
449
|
+
if (hasOnlyStringLiterals) {
|
|
450
|
+
attr.value = {
|
|
451
|
+
kind: "plain",
|
|
452
|
+
type: typeText,
|
|
453
|
+
};
|
|
454
|
+
// Add enum values for better autocomplete
|
|
455
|
+
attr.values = stringLiteralValues
|
|
456
|
+
.map((v) => v.replace(/['"]/g, ""))
|
|
457
|
+
.map((v) => ({ name: v }));
|
|
458
|
+
}
|
|
459
|
+
else if (hasOnlyPrimitives ||
|
|
460
|
+
values.some((v) => v.includes("=>"))) {
|
|
461
|
+
// Function types or primitive unions
|
|
462
|
+
attr.value = {
|
|
463
|
+
kind: "expression",
|
|
464
|
+
type: typeText,
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
else {
|
|
468
|
+
attr.value = {
|
|
469
|
+
kind: "plain",
|
|
470
|
+
type: typeText,
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
else {
|
|
475
|
+
attr.value = {
|
|
476
|
+
kind: "plain",
|
|
477
|
+
type: typeText,
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
attributes.push(attr);
|
|
481
|
+
});
|
|
482
|
+
return { attributes, slots };
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Clean up type text for display
|
|
486
|
+
*/
|
|
487
|
+
function cleanTypeText(text) {
|
|
488
|
+
// Remove import(...) paths
|
|
489
|
+
return text
|
|
490
|
+
.replace(/import\([^)]+\)\./g, "")
|
|
491
|
+
.replace(/\s+/g, " ")
|
|
492
|
+
.trim();
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Convert camelCase to kebab-case
|
|
496
|
+
*/
|
|
497
|
+
function toKebabCase(str) {
|
|
498
|
+
return str.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Try to extract JSDoc description from a property
|
|
502
|
+
*/
|
|
503
|
+
function getPropertyDescription(prop) {
|
|
504
|
+
const declarations = prop.getDeclarations();
|
|
505
|
+
for (const decl of declarations) {
|
|
506
|
+
const jsDocs = decl.getJsDocs?.();
|
|
507
|
+
if (jsDocs && jsDocs.length > 0) {
|
|
508
|
+
return jsDocs[0].getDescription?.()?.trim();
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
return undefined;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Detect the default tsconfig file.
|
|
516
|
+
* Prefers tsconfig.app.json (common in Vite/modern setups) over tsconfig.json
|
|
517
|
+
*/
|
|
518
|
+
function detectDefaultTsconfig() {
|
|
519
|
+
if (fs.existsSync("./tsconfig.app.json")) {
|
|
520
|
+
return "./tsconfig.app.json";
|
|
521
|
+
}
|
|
522
|
+
return "./tsconfig.json";
|
|
523
|
+
}
|
|
524
|
+
function printHelp() {
|
|
525
|
+
console.log(`
|
|
526
|
+
Usage: generate-web-types [options]
|
|
527
|
+
|
|
528
|
+
Options:
|
|
529
|
+
--components, -c <dir> Components directory (default: components/ui)
|
|
530
|
+
--tsconfig, -t <file> TypeScript config file (default: tsconfig.app.json if exists, else tsconfig.json)
|
|
531
|
+
--out, -o <file> Output file (default: web-types.json)
|
|
532
|
+
--name, -n <name> Library name (default: reactolith-components)
|
|
533
|
+
--version, -v <version> Library version (default: 1.0.0)
|
|
534
|
+
--prefix, -p <prefix> Element name prefix (default: "")
|
|
535
|
+
--help, -h Show this help message
|
|
536
|
+
|
|
537
|
+
Examples:
|
|
538
|
+
generate-web-types -c src/components -o web-types.json
|
|
539
|
+
generate-web-types --components ./ui --prefix ui- --name my-ui-lib
|
|
540
|
+
`);
|
|
541
|
+
}
|
|
542
|
+
function parseArgs(args) {
|
|
543
|
+
const result = {};
|
|
544
|
+
let i = 0;
|
|
545
|
+
while (i < args.length) {
|
|
546
|
+
const arg = args[i];
|
|
547
|
+
if (arg === "--help" || arg === "-h") {
|
|
548
|
+
printHelp();
|
|
549
|
+
process.exit(0);
|
|
550
|
+
}
|
|
551
|
+
if (arg.startsWith("-")) {
|
|
552
|
+
const key = arg.replace(/^-+/, "");
|
|
553
|
+
const value = args[i + 1];
|
|
554
|
+
switch (key) {
|
|
555
|
+
case "components":
|
|
556
|
+
case "c":
|
|
557
|
+
result.componentsDir = value;
|
|
558
|
+
break;
|
|
559
|
+
case "tsconfig":
|
|
560
|
+
case "t":
|
|
561
|
+
result.tsconfig = value;
|
|
562
|
+
break;
|
|
563
|
+
case "out":
|
|
564
|
+
case "o":
|
|
565
|
+
result.outFile = value;
|
|
566
|
+
break;
|
|
567
|
+
case "name":
|
|
568
|
+
case "n":
|
|
569
|
+
result.libraryName = value;
|
|
570
|
+
break;
|
|
571
|
+
case "version":
|
|
572
|
+
case "v":
|
|
573
|
+
result.libraryVersion = value;
|
|
574
|
+
break;
|
|
575
|
+
case "prefix":
|
|
576
|
+
case "p":
|
|
577
|
+
result.prefix = value;
|
|
578
|
+
break;
|
|
579
|
+
}
|
|
580
|
+
i += 2;
|
|
581
|
+
}
|
|
582
|
+
else {
|
|
583
|
+
// Positional arguments (legacy support)
|
|
584
|
+
if (!result.componentsDir) {
|
|
585
|
+
result.componentsDir = arg;
|
|
586
|
+
}
|
|
587
|
+
else if (!result.tsconfig) {
|
|
588
|
+
result.tsconfig = arg;
|
|
589
|
+
}
|
|
590
|
+
else if (!result.outFile) {
|
|
591
|
+
result.outFile = arg;
|
|
592
|
+
}
|
|
593
|
+
i++;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
return result;
|
|
597
|
+
}
|
|
598
|
+
const options = parseArgs(process.argv.slice(2));
|
|
599
|
+
generateWebTypes({
|
|
600
|
+
componentsDir: options.componentsDir || "components/ui",
|
|
601
|
+
tsconfig: options.tsconfig || detectDefaultTsconfig(),
|
|
602
|
+
outFile: options.outFile || "web-types.json",
|
|
603
|
+
libraryName: options.libraryName || "reactolith-components",
|
|
604
|
+
libraryVersion: options.libraryVersion || "1.0.0",
|
|
605
|
+
prefix: options.prefix || "",
|
|
606
|
+
});
|
|
607
|
+
//# sourceMappingURL=generate-web-types.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate-web-types.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|