@vuer-ai/vuer-uikit 0.0.98 → 0.0.100

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.
Files changed (78) hide show
  1. package/dist/SyncScroll/SyncScroll.cjs +9 -9
  2. package/dist/SyncScroll/SyncScroll.mjs +2 -2
  3. package/dist/SyncScroll/index.cjs +9 -9
  4. package/dist/SyncScroll/index.mjs +2 -2
  5. package/dist/chunk-AIYM5PFP.cjs +66 -0
  6. package/dist/chunk-OWEYAVGT.mjs +62 -0
  7. package/dist/{chunk-7GWDO25E.cjs → chunk-XBTBTLID.cjs} +2 -2
  8. package/dist/{chunk-TTYSYGVE.mjs → chunk-XGXWOY3U.mjs} +2 -2
  9. package/dist/dial/DialPanel.cjs +23 -22
  10. package/dist/dial/DialPanel.mjs +22 -21
  11. package/dist/dial/index.cjs +39 -38
  12. package/dist/dial/index.mjs +22 -21
  13. package/dist/dial/wrapped-inputs/ControlledInputs.cjs +26 -25
  14. package/dist/dial/wrapped-inputs/ControlledInputs.mjs +22 -21
  15. package/dist/dial/wrapped-inputs/DialInputs.cjs +33 -32
  16. package/dist/dial/wrapped-inputs/DialInputs.mjs +22 -21
  17. package/dist/dial/wrapped-inputs/DialVectorInput.cjs +23 -22
  18. package/dist/dial/wrapped-inputs/DialVectorInput.mjs +22 -21
  19. package/dist/dial/wrapped-inputs/index.cjs +38 -37
  20. package/dist/dial/wrapped-inputs/index.mjs +22 -21
  21. package/dist/highlight-cursor/cursor-provider.cjs +3 -3
  22. package/dist/highlight-cursor/cursor-provider.mjs +2 -2
  23. package/dist/highlight-cursor/enhanced-components.cjs +1 -1
  24. package/dist/highlight-cursor/enhanced-components.mjs +1 -1
  25. package/dist/highlight-cursor/index.cjs +3 -3
  26. package/dist/highlight-cursor/index.mjs +2 -2
  27. package/dist/index.cjs +151 -138
  28. package/dist/index.css +7 -1
  29. package/dist/index.d.cts +1 -0
  30. package/dist/index.d.ts +1 -0
  31. package/dist/index.mjs +22 -21
  32. package/dist/ui/DialBadge.cjs +28 -0
  33. package/dist/ui/DialBadge.d.cts +33 -0
  34. package/dist/ui/DialBadge.d.ts +33 -0
  35. package/dist/ui/DialBadge.mjs +11 -0
  36. package/dist/ui/UIKitBadge.cjs +5 -5
  37. package/dist/ui/UIKitBadge.mjs +1 -1
  38. package/dist/ui/badge.d.cts +1 -1
  39. package/dist/ui/badge.d.ts +1 -1
  40. package/dist/ui/index.cjs +77 -64
  41. package/dist/ui/index.d.cts +1 -0
  42. package/dist/ui/index.d.ts +1 -0
  43. package/dist/ui/index.mjs +18 -17
  44. package/dist/ui/inputs/index.cjs +15 -15
  45. package/dist/ui/inputs/index.mjs +3 -3
  46. package/dist/ui/inputs/input.d.cts +1 -1
  47. package/dist/ui/inputs/input.d.ts +1 -1
  48. package/dist/ui/inputs/number-inputs/index.cjs +10 -10
  49. package/dist/ui/inputs/number-inputs/index.mjs +2 -2
  50. package/dist/ui/select.d.cts +1 -1
  51. package/dist/ui/select.d.ts +1 -1
  52. package/dist/ui/textarea.d.cts +1 -1
  53. package/dist/ui/textarea.d.ts +1 -1
  54. package/dist/ui/tree-view-legacy.cjs +8 -8
  55. package/dist/ui/tree-view-legacy.mjs +4 -4
  56. package/dist/ui/waterfall/index.cjs +6 -6
  57. package/dist/ui/waterfall/index.mjs +5 -5
  58. package/package.json +2 -7
  59. package/src/ui/DialBadge.tsx +97 -0
  60. package/src/ui/index.ts +1 -0
  61. package/cli/dial-cli.js +0 -833
  62. package/dist/chunk-4KWGGESI.cjs +0 -494
  63. package/dist/chunk-A5LCX2UQ.cjs +0 -208
  64. package/dist/chunk-BEJIZ56L.mjs +0 -300
  65. package/dist/chunk-C7VGRU3O.mjs +0 -283
  66. package/dist/chunk-O66RESRR.cjs +0 -285
  67. package/dist/chunk-VA3PEYFM.mjs +0 -489
  68. package/dist/chunk-VBBJSIY7.cjs +0 -308
  69. package/dist/chunk-WWGF6TBZ.mjs +0 -206
  70. package/dist/chunk-ZGN4UEJR.cjs +0 -679
  71. package/dist/chunk-ZQLRMOUW.mjs +0 -661
  72. package/src/cli/dial-cli.ts +0 -1133
  73. package/dist/{chunk-XMUP5MIM.mjs → chunk-G3EIVAVR.mjs} +0 -0
  74. package/dist/{chunk-7IS37C3P.cjs → chunk-K4I4YU6N.cjs} +1 -1
  75. package/dist/{chunk-OX2U5RAG.cjs → chunk-KFPS5CCR.cjs} +0 -0
  76. package/dist/{chunk-2OZK5DY5.mjs → chunk-KXKEZ3MH.mjs} +1 -1
  77. package/dist/{chunk-LJMNHTTG.cjs → chunk-OEI7NCF6.cjs} +4 -4
  78. package/dist/{chunk-W4JCKCW7.mjs → chunk-QHPFLC2O.mjs} +4 -4
@@ -1,1133 +0,0 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
- import { basename, dirname, join, resolve } from "node:path";
3
- import { parseArgs } from "node:util";
4
- import { fileURLToPath } from "node:url";
5
- import { execSync } from "node:child_process";
6
- import * as docgen from "react-docgen-typescript";
7
- import * as ts from "typescript";
8
-
9
- // Get package version and git hash
10
- function getVersionInfo(): { version: string; gitHash?: string } {
11
- try {
12
- // Get the directory of this CLI file
13
- const __filename = fileURLToPath(import.meta.url);
14
- const __dirname = dirname(__filename);
15
-
16
- // Try multiple possible locations for package.json
17
- // When built, the CLI is in ./cli/dial-cli.js, so package.json is at ../package.json
18
- // When running from source, it's in ./src/cli/, so package.json is at ../../package.json
19
- const possiblePaths = [
20
- resolve(__dirname, '../package.json'), // Built version: cli/dial-cli.js -> package.json
21
- resolve(__dirname, '../../package.json'), // Source version: src/cli/dial-cli.ts -> package.json
22
- ];
23
-
24
- let packageJson: any = null;
25
- for (const path of possiblePaths) {
26
- try {
27
- if (existsSync(path)) {
28
- packageJson = JSON.parse(readFileSync(path, 'utf-8'));
29
- break;
30
- }
31
- } catch {
32
- // Continue to next path
33
- }
34
- }
35
-
36
- const version = packageJson?.version || 'unknown';
37
-
38
- // Try to get git hash
39
- let gitHash: string | undefined;
40
- try {
41
- gitHash = execSync('git rev-parse --short HEAD', { encoding: 'utf-8', cwd: dirname(__dirname) }).trim();
42
- } catch {
43
- // Git not available or not a git repo
44
- gitHash = undefined;
45
- }
46
-
47
- return { version, gitHash };
48
- } catch (error) {
49
- // Fallback if we can't read package.json
50
- return { version: 'unknown' };
51
- }
52
- }
53
-
54
- interface DialTag {
55
- grouping?: string;
56
- min?: number;
57
- max?: number;
58
- step?: number;
59
- options?: unknown;
60
- icon?: string;
61
- placeholders?: string[];
62
- tooltips?: string[];
63
- dtypes?: string[];
64
- mins?: number[];
65
- maxs?: number[];
66
- steps?: number[];
67
- column?: boolean;
68
- rowCount?: number;
69
- colCount?: number;
70
- labelPosition?: string;
71
- dtype?: string;
72
- type?: string;
73
- default?: unknown;
74
- noWrap?: boolean;
75
- }
76
-
77
- interface ElementData {
78
- index: number;
79
- raw: string;
80
- name?: string;
81
- type?: string;
82
- comment?: string;
83
- min?: number;
84
- max?: number;
85
- step?: number;
86
- dtype?: string;
87
- dialTags?: DialTag;
88
- }
89
-
90
- interface TypeDefinition {
91
- name: string;
92
- kind: string;
93
- raw: string;
94
- dialTags?: DialTag;
95
- jsDoc?: string;
96
- type?: string;
97
- elements?: ElementData[];
98
- elementType?: string;
99
- typeNode?: string;
100
- }
101
-
102
- interface PropertySchema {
103
- name: string;
104
- dtype: string;
105
- min?: number;
106
- max?: number;
107
- step?: number;
108
- icon?: string;
109
- value?: unknown;
110
- options?: unknown;
111
- placeholders?: string[];
112
- tooltips?: string[];
113
- dtypes?: string[];
114
- mins?: number[];
115
- maxs?: number[];
116
- steps?: number[];
117
- tags?: Record<string, unknown>;
118
- typeDefinition?: TypeDefinition;
119
- }
120
-
121
- // Parse type alias definition from TypeScript source
122
- function parseTypeAlias(sourceFile: ts.SourceFile, typeName: string): TypeDefinition | null {
123
- let typeDefinition: TypeDefinition | null = null;
124
-
125
- function visit(node: ts.Node): void {
126
- if (ts.isTypeAliasDeclaration(node) && node.name.text === typeName) {
127
- typeDefinition = {
128
- name: typeName,
129
- kind: "typeAlias",
130
- raw: node.getFullText().trim(),
131
- };
132
-
133
- // Parse JSDoc comments for the type alias
134
- const fullText = node.getFullText();
135
- const jsDocMatch = fullText.match(/\/\*\*([\s\S]*?)\*\//);
136
- if (jsDocMatch) {
137
- const jsDocContent = jsDocMatch[1];
138
- typeDefinition.dialTags = parseDialTags(jsDocContent);
139
- typeDefinition.jsDoc = jsDocContent.trim();
140
- }
141
-
142
- if (ts.isTupleTypeNode(node.type)) {
143
- typeDefinition.type = "tuple";
144
- typeDefinition.elements = [];
145
-
146
- node.type.elements.forEach((element, index) => {
147
- const elementData: ElementData = {
148
- index,
149
- raw: element.getFullText().trim(),
150
- };
151
-
152
- if (ts.isNamedTupleMember(element)) {
153
- elementData.name = element.name?.getText();
154
- elementData.type = element.type?.getText();
155
-
156
- // Extract inline comment with @dial annotations
157
- const sourceText = element.getFullText();
158
- const commentMatch = sourceText.match(/\/\/(.+)/);
159
- if (commentMatch) {
160
- elementData.comment = commentMatch[1].trim();
161
-
162
- // Parse @dial annotations from the comment
163
- const dialTags: DialTag = {};
164
-
165
- // Parse @dial-min
166
- const minMatch = commentMatch[1].match(/@dial-min\s+([\d.]+)/);
167
- if (minMatch) {
168
- dialTags.min = parseFloat(minMatch[1]);
169
- elementData.min = dialTags.min;
170
- }
171
-
172
- // Parse @dial-max
173
- const maxMatch = commentMatch[1].match(/@dial-max\s+([\d.]+)/);
174
- if (maxMatch) {
175
- dialTags.max = parseFloat(maxMatch[1]);
176
- elementData.max = dialTags.max;
177
- }
178
-
179
- // Parse @dial-step
180
- const stepMatch = commentMatch[1].match(/@dial-step\s+([\d.]+)/);
181
- if (stepMatch) {
182
- dialTags.step = parseFloat(stepMatch[1]);
183
- elementData.step = dialTags.step;
184
- }
185
-
186
- // Parse @dial-dtype
187
- const dtypeMatch = commentMatch[1].match(/@dial-dtype\s+(\w+)/);
188
- if (dtypeMatch) {
189
- dialTags.dtype = dtypeMatch[1];
190
- elementData.dtype = dtypeMatch[1];
191
- } else {
192
- elementData.dtype = "number";
193
- }
194
-
195
- elementData.dialTags = dialTags;
196
- }
197
- }
198
-
199
- typeDefinition.elements!.push(elementData);
200
- });
201
- } else if (ts.isArrayTypeNode(node.type)) {
202
- typeDefinition!.type = "array";
203
- typeDefinition!.elementType = node.type.elementType?.getText();
204
- } else {
205
- typeDefinition.type = "other";
206
- typeDefinition.typeNode = node.type ? node.type.getText() : "unknown";
207
- }
208
- }
209
-
210
- ts.forEachChild(node, visit);
211
- }
212
-
213
- visit(sourceFile);
214
- return typeDefinition;
215
- }
216
-
217
- // Parse interface properties from TypeScript source
218
- function parseInterfaceProperties(
219
- sourceFile: ts.SourceFile,
220
- interfaceName: string,
221
- ): PropertySchema[] {
222
- const properties: PropertySchema[] = [];
223
- let interfaceDialTags: DialTag = {};
224
- const groupConfigs: Record<string, any> = {};
225
-
226
- function visit(node: ts.Node) {
227
- if (ts.isInterfaceDeclaration(node) && node.name.text === interfaceName) {
228
- // First, get JSDoc comments for the interface itself
229
- const interfaceJsDocNodes = ts.getJSDocCommentsAndTags(node);
230
- if (interfaceJsDocNodes && interfaceJsDocNodes.length > 0) {
231
- const interfaceFullText = interfaceJsDocNodes[0].getFullText();
232
- interfaceDialTags = parseDialTags(interfaceFullText);
233
-
234
- // Parse group-level configurations from interface JSDoc
235
- const lines = interfaceFullText.split('\n');
236
- lines.forEach(line => {
237
- // Match patterns like "@dial transform @dial-no-wrap"
238
- const groupMatch = line.match(/@dial\s+(\w+)(.*)/);
239
- if (groupMatch) {
240
- const groupName = groupMatch[1];
241
- const configStr = groupMatch[2];
242
- if (configStr.includes('@dial-no-wrap')) {
243
- groupConfigs[groupName] = { noWrap: true };
244
- }
245
- }
246
- });
247
- }
248
-
249
- node.members.forEach((member) => {
250
- if (ts.isPropertySignature(member) && member.name && ts.isIdentifier(member.name)) {
251
- const propName = member.name.text;
252
-
253
- // Get JSDoc comments for the property
254
- const jsDocNodes = ts.getJSDocCommentsAndTags(member);
255
- let propertyDialTags: DialTag = {};
256
-
257
- if (jsDocNodes && jsDocNodes.length > 0) {
258
- const fullText = jsDocNodes[0].getFullText();
259
- propertyDialTags = parseDialTags(fullText);
260
- }
261
-
262
- // Merge interface-level tags with property-level tags (property-level takes precedence)
263
- const mergedDialTags = { ...interfaceDialTags, ...propertyDialTags };
264
-
265
- // Build property schema
266
- const prop: PropertySchema = {
267
- name: propName,
268
- dtype: mergedDialTags.dtype || "number",
269
- };
270
-
271
- // Add numeric constraints
272
- if (mergedDialTags.min !== undefined) prop.min = mergedDialTags.min;
273
- if (mergedDialTags.max !== undefined) prop.max = mergedDialTags.max;
274
- if (mergedDialTags.step !== undefined) prop.step = mergedDialTags.step;
275
- if (mergedDialTags.icon) prop.icon = mergedDialTags.icon;
276
-
277
- // Add grouping and other tags
278
- if (mergedDialTags.grouping || mergedDialTags.column || mergedDialTags.rowCount ||
279
- mergedDialTags.colCount || mergedDialTags.labelPosition || mergedDialTags.noWrap) {
280
- prop.tags = {};
281
- if (mergedDialTags.grouping) {
282
- prop.tags.grouping = mergedDialTags.grouping;
283
- // Check if this group has noWrap configuration
284
- if (groupConfigs[mergedDialTags.grouping]?.noWrap) {
285
- prop.tags.noWrap = true;
286
- }
287
- }
288
- if (mergedDialTags.column) prop.tags.layout = "column";
289
- if (mergedDialTags.rowCount) prop.tags.row = mergedDialTags.rowCount;
290
- if (mergedDialTags.colCount) prop.tags.col = mergedDialTags.colCount;
291
- if (mergedDialTags.labelPosition) prop.tags.labelPosition = mergedDialTags.labelPosition;
292
- if (mergedDialTags.noWrap) prop.tags.noWrap = true;
293
- }
294
-
295
- properties.push(prop);
296
- }
297
- });
298
- }
299
-
300
- ts.forEachChild(node, visit);
301
- }
302
-
303
- visit(sourceFile);
304
- return properties;
305
- }
306
-
307
- // Parse @dial annotations from JSDoc comments
308
- function parseDialTags(description: string): DialTag & { ignore?: boolean } {
309
- const dialTags: DialTag & { ignore?: boolean } = {};
310
-
311
- if (!description) return dialTags;
312
-
313
- // Check for @dial-ignore first
314
- if (/@dial-ignore\b/i.test(description)) {
315
- dialTags.ignore = true;
316
- return dialTags; // Return early if ignore is set
317
- }
318
-
319
- // Extract @dial grouping tags (without hyphen)
320
- const groupingPattern = /@dial\s+(transform|visibility|geometry|segments)\b/gm;
321
- let match;
322
-
323
- while ((match = groupingPattern.exec(description)) !== null) {
324
- dialTags.grouping = match[1];
325
- }
326
-
327
- // Extract @dial-* property tags (with hyphen)
328
- const propertyPattern = /@dial-([\w-]+)(?:\s+([^\n@]+?))?(?:\n|@|$)/gm;
329
-
330
- while ((match = propertyPattern.exec(description)) !== null) {
331
- const [, key, value] = match;
332
-
333
- switch (key) {
334
- case "options":
335
- // Parse array-like values
336
- if (value) {
337
- try {
338
- dialTags.options = JSON.parse(value);
339
- } catch {
340
- dialTags.options = value;
341
- }
342
- }
343
- break;
344
-
345
- case "min":
346
- case "max":
347
- case "step":
348
- // Parse numeric values
349
- if (value) {
350
- dialTags[key] = parseFloat(value);
351
- }
352
- break;
353
-
354
- case "col":
355
- case "column":
356
- // Boolean flag for column layout
357
- dialTags.column = true;
358
- break;
359
-
360
- case "row":
361
- // Parse row layout
362
- if (value && /^\d+$/.test(value)) {
363
- dialTags.rowCount = parseInt(value, 10);
364
- }
365
- break;
366
-
367
- case "col-3":
368
- case "col-2":
369
- case "col-4": {
370
- // Parse column layout with number
371
- const colMatch = key.match(/col-(\d+)/);
372
- if (colMatch) {
373
- dialTags.colCount = parseInt(colMatch[1], 10);
374
- }
375
- break;
376
- }
377
-
378
- case "row-3":
379
- case "row-2":
380
- case "row-4": {
381
- // Parse row layout with number
382
- const rowMatch = key.match(/row-(\d+)/);
383
- if (rowMatch) {
384
- dialTags.rowCount = parseInt(rowMatch[1], 10);
385
- }
386
- break;
387
- }
388
-
389
- case "dtype":
390
- // Data type
391
- if (value) {
392
- dialTags.dtype = value.trim();
393
- }
394
- break;
395
-
396
- case "dtypes":
397
- // Comma-separated data types for vectors
398
- if (value) {
399
- dialTags.dtypes = value.split(",").map((s) => s.trim());
400
- }
401
- break;
402
-
403
- case "type":
404
- // Type reference
405
- if (value) {
406
- dialTags.type = value.trim();
407
- }
408
- break;
409
-
410
- case "icon":
411
- // Lucide icon name
412
- if (value) {
413
- dialTags.icon = value.trim();
414
- }
415
- break;
416
-
417
- case "default":
418
- // Default value for the property
419
- if (value) {
420
- try {
421
- // Try to parse as JSON first (for arrays, objects, booleans, numbers)
422
- dialTags.default = JSON.parse(value);
423
- } catch {
424
- // If not valid JSON, use as string
425
- dialTags.default = value.trim();
426
- }
427
- }
428
- break;
429
-
430
- case "placeholders":
431
- // Comma-separated placeholders for vector types
432
- if (value) {
433
- dialTags.placeholders = value.split(",").map((s) => s.trim());
434
- }
435
- break;
436
-
437
- case "tooltips":
438
- // Comma-separated tooltips for vector types
439
- if (value) {
440
- dialTags.tooltips = value.split(",").map((s) => s.trim());
441
- }
442
- break;
443
-
444
- case "mins":
445
- // Comma-separated min values
446
- if (value) {
447
- dialTags.mins = value.split(",").map((s) => parseFloat(s.trim()));
448
- }
449
- break;
450
-
451
- case "maxs":
452
- // Comma-separated max values
453
- if (value) {
454
- dialTags.maxs = value.split(",").map((s) => parseFloat(s.trim()));
455
- }
456
- break;
457
-
458
- case "steps":
459
- // Comma-separated step values
460
- if (value) {
461
- dialTags.steps = value.split(",").map((s) => parseFloat(s.trim()));
462
- }
463
- break;
464
-
465
- case "label-left":
466
- case "left":
467
- // Label position left
468
- dialTags.labelPosition = "left";
469
- break;
470
-
471
- case "label-right":
472
- case "right":
473
- // Label position right
474
- dialTags.labelPosition = "right";
475
- break;
476
-
477
- case "label-center":
478
- case "label-middle":
479
- case "center":
480
- case "middle":
481
- // Label position center
482
- dialTags.labelPosition = "center";
483
- break;
484
-
485
- case "label-top":
486
- case "top":
487
- // Label position top
488
- dialTags.labelPosition = "top";
489
- break;
490
-
491
- case "label-bottom":
492
- case "bottom":
493
- // Label position bottom
494
- dialTags.labelPosition = "bottom";
495
- break;
496
-
497
- case "label-inline":
498
- case "inline":
499
- // Label position inline
500
- dialTags.labelPosition = "inline";
501
- break;
502
-
503
- case "no-wrap":
504
- // No wrap flag for preventing line wrapping
505
- dialTags.noWrap = true;
506
- break;
507
-
508
- default:
509
- // Other key-value pairs could be handled here
510
- break;
511
- }
512
- }
513
-
514
- return dialTags;
515
- }
516
-
517
- // Convert prop data to dial schema format
518
- function propToDialSchema(propName: string, propData: Record<string, unknown>, groupConfigs?: Record<string, any>): PropertySchema | null {
519
- const description = (propData.description as string) || "";
520
- const dialTags = parseDialTags(description);
521
-
522
- // Skip properties with @dial-ignore
523
- if (dialTags.ignore) {
524
- return null;
525
- }
526
-
527
- // Determine dtype based on type or dial tags
528
- let dtype = dialTags.dtype;
529
- if (!dtype) {
530
- const type = propData.type as { name?: string } | undefined;
531
- const typeName = type?.name || "";
532
- if (typeName === "boolean") {
533
- dtype = "boolean";
534
- } else if (typeName === "number") {
535
- dtype = "number";
536
- } else if (typeName.includes("[]")) {
537
- dtype = "array";
538
- } else {
539
- dtype = "string";
540
- }
541
- }
542
-
543
- // Build schema object
544
- const schema: PropertySchema = {
545
- name: propName,
546
- dtype,
547
- };
548
-
549
- // Add default value
550
- // Priority: @dial-default annotation > component default > undefined
551
- if (dialTags.default !== undefined) {
552
- schema.value = dialTags.default;
553
- } else {
554
- const defaultValue = propData.defaultValue as { value?: string } | undefined;
555
- if (defaultValue?.value) {
556
- try {
557
- schema.value = JSON.parse(defaultValue.value);
558
- } catch {
559
- schema.value = defaultValue.value;
560
- }
561
- }
562
- }
563
-
564
- // Add dial tags
565
- if (dialTags.min !== undefined) schema.min = dialTags.min;
566
- if (dialTags.max !== undefined) schema.max = dialTags.max;
567
- if (dialTags.step !== undefined) schema.step = dialTags.step;
568
- if (dialTags.options) schema.options = dialTags.options;
569
- if (dialTags.icon) schema.icon = dialTags.icon;
570
-
571
- // Add vector-specific metadata
572
- if (dialTags.placeholders) schema.placeholders = dialTags.placeholders;
573
- if (dialTags.tooltips) schema.tooltips = dialTags.tooltips;
574
- if (dialTags.dtypes) schema.dtypes = dialTags.dtypes;
575
- if (dialTags.mins) schema.mins = dialTags.mins;
576
- if (dialTags.maxs) schema.maxs = dialTags.maxs;
577
- if (dialTags.steps) schema.steps = dialTags.steps;
578
-
579
- // Add tags for grouping and layout
580
- const tags: Record<string, unknown> = {};
581
- if (dialTags.grouping) {
582
- tags.grouping = dialTags.grouping;
583
-
584
- // Check if this group has noWrap configuration
585
- if (groupConfigs && groupConfigs[dialTags.grouping]?.noWrap) {
586
- tags.noWrap = true;
587
- }
588
- }
589
- if (dialTags.column) {
590
- tags.col = true;
591
- }
592
- if (dialTags.rowCount) {
593
- tags.row = dialTags.rowCount;
594
- tags.layout = "row";
595
- }
596
- if (dialTags.colCount) {
597
- tags.col = dialTags.colCount;
598
- tags.layout = "column";
599
- }
600
- if (dialTags.labelPosition) {
601
- tags.labelPosition = dialTags.labelPosition;
602
- }
603
-
604
- if (Object.keys(tags).length > 0) {
605
- schema.tags = tags;
606
- }
607
-
608
- return schema;
609
- }
610
-
611
- // Process a single file
612
- async function processFile(
613
- filePath: string,
614
- outputDir: string,
615
- options: docgen.ParserOptions,
616
- flags: { verbose?: boolean; quiet?: boolean; ignoreList?: string[] } = {},
617
- ): Promise<void> {
618
- if (!flags.quiet) {
619
- console.log(`\n📦 Processing ${filePath}...`);
620
- }
621
-
622
- // Parse with react-docgen-typescript
623
- const docs = docgen.parse(filePath, options);
624
-
625
- // Also parse the TypeScript source
626
- const sourceCode = readFileSync(filePath, "utf-8");
627
- const sourceFile = ts.createSourceFile(filePath, sourceCode, ts.ScriptTarget.Latest, true);
628
-
629
- // Process each component
630
- const enhancedMetadata = docs.map((doc) => {
631
- const dialSchema: PropertySchema[] = [];
632
-
633
- // Parse component-level dial configuration from description
634
- const groupConfigs: Record<string, any> = {};
635
- if (doc.description) {
636
- const componentTags = parseDialTags(doc.description);
637
- // Extract group-specific configuration
638
- const descLines = doc.description.split('\n');
639
- descLines.forEach(line => {
640
- // Match patterns like "@dial transform @dial-no-wrap"
641
- const groupMatch = line.match(/@dial\s+(\w+)(.*)/);
642
- if (groupMatch) {
643
- const groupName = groupMatch[1];
644
- const configStr = groupMatch[2];
645
- if (configStr.includes('@dial-no-wrap')) {
646
- groupConfigs[groupName] = { noWrap: true };
647
- }
648
- }
649
- });
650
- }
651
-
652
- // Also look for the Props interface and parse its JSDoc for group configurations
653
- // The props interface is typically named [ComponentName]Props
654
- const propsInterfaceName = doc.displayName + 'Props';
655
- function findInterfaceJSDoc(node: ts.Node): void {
656
- if (ts.isInterfaceDeclaration(node) && node.name.text === propsInterfaceName) {
657
- const interfaceJsDocNodes = ts.getJSDocCommentsAndTags(node);
658
- if (interfaceJsDocNodes && interfaceJsDocNodes.length > 0) {
659
- const interfaceFullText = interfaceJsDocNodes[0].getFullText();
660
- // Parse group-level configurations from interface JSDoc
661
- const lines = interfaceFullText.split('\n');
662
- lines.forEach(line => {
663
- // Match patterns like "@dial transform @dial-no-wrap"
664
- const groupMatch = line.match(/@dial\s+(\w+)(.*)/);
665
- if (groupMatch) {
666
- const groupName = groupMatch[1];
667
- const configStr = groupMatch[2];
668
- if (configStr.includes('@dial-no-wrap')) {
669
- groupConfigs[groupName] = { noWrap: true };
670
- }
671
- }
672
- });
673
- }
674
- }
675
- ts.forEachChild(node, findInterfaceJSDoc);
676
- }
677
- findInterfaceJSDoc(sourceFile);
678
-
679
- // Convert each prop to dial schema format
680
- for (const [propName, propData] of Object.entries(doc.props || {})) {
681
- const propDescription = (propData as { description?: string }).description || "";
682
- const propDialTags = parseDialTags(propDescription);
683
-
684
- // Skip properties with @dial-ignore
685
- if (propDialTags.ignore) {
686
- continue;
687
- }
688
-
689
- const propType = (propData as { type?: { name?: string } }).type;
690
- const referencedType = propDialTags.type || propType?.name;
691
-
692
- // Check if this prop references a type with dial annotations
693
- if (referencedType) {
694
- const typeDefinition = parseTypeAlias(sourceFile, referencedType);
695
-
696
- if (typeDefinition) {
697
- // Create schema with type definition
698
- const schema = propToDialSchema(propName, propData as unknown as Record<string, unknown>, groupConfigs);
699
-
700
- // Skip if the property is ignored
701
- if (!schema) {
702
- continue;
703
- }
704
-
705
- // Handle vector types with element annotations
706
- if (typeDefinition.elements && typeDefinition.elements.length > 0) {
707
- schema.dtype = "vector";
708
-
709
- // Build arrays from individual element annotations
710
- const mins: number[] = [];
711
- const maxs: number[] = [];
712
- const steps: number[] = [];
713
- const dtypes: string[] = [];
714
- const placeholders: string[] = [];
715
- const tooltips: string[] = [];
716
-
717
- typeDefinition.elements.forEach((element) => {
718
- if (element.name) {
719
- mins.push(element.min !== undefined ? element.min : 0);
720
- maxs.push(element.max !== undefined ? element.max : 100);
721
- steps.push(element.step !== undefined ? element.step : 1);
722
- dtypes.push(element.dtype || "number");
723
- placeholders.push(element.name);
724
-
725
- // Create descriptive tooltips
726
- const tooltip = element.name.includes("Segments")
727
- ? `${element.name.replace("Segments", " segments")}`
728
- : element.name.charAt(0).toUpperCase() + element.name.slice(1);
729
- tooltips.push(tooltip);
730
- }
731
- });
732
-
733
- if (mins.length > 0) schema.mins = mins;
734
- if (maxs.length > 0) schema.maxs = maxs;
735
- if (steps.length > 0) schema.steps = steps;
736
- if (dtypes.length > 0) schema.dtypes = dtypes;
737
- if (placeholders.length > 0) schema.placeholders = placeholders;
738
- if (tooltips.length > 0) schema.tooltips = tooltips;
739
-
740
- schema.typeDefinition = typeDefinition;
741
- }
742
-
743
- dialSchema.push(schema);
744
-
745
- // Also parse interface properties if it's an interface
746
- const interfaceProps = parseInterfaceProperties(sourceFile, referencedType);
747
- interfaceProps.forEach((prop) => {
748
- dialSchema.push({
749
- ...prop,
750
- name: `${propName}.${prop.name}`,
751
- tags: {
752
- grouping: "nested",
753
- parent: propName,
754
- hidden: true,
755
- metadata: true,
756
- },
757
- });
758
- });
759
- } else {
760
- // Regular prop without type definition
761
- const schema = propToDialSchema(propName, propData as unknown as Record<string, unknown>, groupConfigs);
762
- if (schema) {
763
- dialSchema.push(schema);
764
- }
765
- }
766
- } else {
767
- // Regular prop without type reference
768
- const schema = propToDialSchema(propName, propData as unknown as Record<string, unknown>, groupConfigs);
769
- if (schema) {
770
- dialSchema.push(schema);
771
- }
772
- }
773
- }
774
-
775
- return {
776
- displayName: doc.displayName,
777
- description: doc.description,
778
- dialSchema,
779
- groupConfigs,
780
- props: doc.props,
781
- };
782
- });
783
-
784
- // Create output directory if it doesn't exist
785
- if (!existsSync(outputDir)) {
786
- mkdirSync(outputDir, { recursive: true });
787
- }
788
-
789
- const baseName = basename(filePath, ".tsx").toLowerCase();
790
-
791
- // Write raw docgen output
792
- writeFileSync(join(outputDir, `${baseName}-raw.json`), JSON.stringify(docs, null, 2));
793
- if (!flags.quiet) {
794
- console.log(`📝 Wrote raw metadata to ${baseName}-raw.json`);
795
- }
796
-
797
- // Write enhanced metadata with dial schema
798
- writeFileSync(
799
- join(outputDir, `${baseName}-combined.json`),
800
- JSON.stringify(enhancedMetadata, null, 2),
801
- );
802
- if (!flags.quiet) {
803
- console.log(`📝 Wrote enhanced metadata to ${baseName}-combined.json`);
804
- }
805
-
806
- // Write just the dial schemas with groups
807
- const schemas = enhancedMetadata.map((c) => {
808
- // Convert groupConfigs to groups array
809
- const groups = Object.entries(c.groupConfigs || {}).map(([name, config]) => ({
810
- name,
811
- ...config
812
- }));
813
-
814
- return {
815
- component: c.displayName,
816
- schemas: c.dialSchema,
817
- groups: groups.length > 0 ? groups : undefined,
818
- config: c.dialConfig,
819
- };
820
- });
821
- writeFileSync(join(outputDir, `${baseName}-schemas.json`), JSON.stringify(schemas, null, 2));
822
- if (!flags.quiet) {
823
- console.log(`📝 Wrote dial schemas to ${baseName}-schemas.json`);
824
- }
825
-
826
- // Log sample output in verbose mode
827
- if (flags.verbose && enhancedMetadata.length > 0) {
828
- console.log("\n📊 Generated dial schemas:");
829
- enhancedMetadata.forEach((meta) => {
830
- console.log(`\n Component: ${meta.displayName}`);
831
- console.log(` Properties: ${meta.dialSchema.length}`);
832
- if (meta.dialSchema.length > 0) {
833
- console.log(` Sample schema:`);
834
- console.log(JSON.stringify(meta.dialSchema[0], null, 4).split('\n').map(line => ' ' + line).join('\n'));
835
- }
836
- });
837
- }
838
- }
839
-
840
- // Help text for the CLI (with dynamic version)
841
- function getHelpText(): string {
842
- const { version, gitHash } = getVersionInfo();
843
- const versionString = gitHash ? `v${version} (${gitHash})` : `v${version}`;
844
-
845
- return `
846
- dial-cli - Generate dial metadata from TypeScript/React components
847
-
848
- SYNOPSIS
849
- dial-cli [options] <file.tsx> [file2.tsx ...]
850
-
851
- DESCRIPTION
852
- The dial-cli tool parses TypeScript and React component files to extract
853
- @dial annotations from JSDoc comments and generates JSON metadata files
854
- that can be used to automatically create UI controls.
855
-
856
- The tool generates three types of output files for each input:
857
- • *-raw.json - Raw output from react-docgen-typescript
858
- • *-combined.json - Enhanced metadata with dial schema information
859
- • *-schemas.json - Just the dial schemas for UI generation
860
-
861
- OPTIONS
862
- -o, --output <dir>
863
- Output directory for generated metadata files (default: ./metadata)
864
-
865
- -i, --ignore <prop>
866
- Property names or patterns to exclude from dial schema generation
867
- Can be specified multiple times: -i prop1 -i prop2
868
- Or as a comma-separated list: -i prop1,prop2,prop3
869
- Supports glob patterns: -i "*Style" -i "on*" -i "_*"
870
-
871
- -h, --help
872
- Display this help message and exit
873
-
874
- -v, --version
875
- Display version information and exit
876
-
877
- --verbose
878
- Enable verbose output with detailed processing information
879
-
880
- --quiet
881
- Suppress all output except errors
882
-
883
- DIAL ANNOTATIONS
884
- The tool recognizes the following @dial annotations in JSDoc comments:
885
-
886
- Grouping:
887
- @dial <group> Group related properties (transform, visibility, etc.)
888
-
889
- Property Configuration:
890
- @dial-dtype <type> Data type (vector3, euler, boolean, int, etc.)
891
- @dial-min <number> Minimum value for numeric inputs
892
- @dial-max <number> Maximum value for numeric inputs
893
- @dial-step <number> Step increment for numeric inputs
894
- @dial-default <value> Default value (overrides component default)
895
- @dial-options [...] Array of preset values
896
- @dial-icon <name> Lucide icon name for the control
897
- @dial-ignore Exclude this property from dial schema generation
898
-
899
- FormLayout:
900
- @dial-col-<n> Display in column layout with n columns
901
- @dial-row-<n> Display in row layout with n items per row
902
- @dial-label-<pos> Label position (left, right, top, bottom, inline)
903
-
904
- Vector Types:
905
- @dial-mins <n,n,n> Comma-separated min values for vector elements
906
- @dial-maxs <n,n,n> Comma-separated max values for vector elements
907
- @dial-steps <n,n,n> Comma-separated step values for vector elements
908
- @dial-dtypes <t,t,t> Comma-separated data types for vector elements
909
-
910
- EXAMPLES
911
- Generate metadata for a single component:
912
- $ dial-cli MyComponent.tsx
913
-
914
- Specify a custom output directory:
915
- $ dial-cli -o ./docs/metadata MyComponent.tsx
916
-
917
- Process multiple files:
918
- $ dial-cli Component1.tsx Component2.tsx Component3.tsx
919
-
920
- Process all components in a directory:
921
- $ dial-cli src/components/*.tsx
922
-
923
- Generate metadata with verbose output:
924
- $ dial-cli --verbose MyComponent.tsx
925
-
926
- Exclude specific properties from schema generation:
927
- $ dial-cli -i className -i style -i children MyComponent.tsx
928
- $ dial-cli --ignore ref,key,id MyComponent.tsx
929
-
930
- EXAMPLE COMPONENT
931
- interface Props {
932
- /**
933
- * Position in 3D space
934
- * @dial transform
935
- * @dial-dtype vector3
936
- * @dial-mins -10,-10,-10
937
- * @dial-maxs 10,10,10
938
- * @dial-steps 0.1,0.1,0.1
939
- * @dial-icon Move3d
940
- */
941
- position: [number, number, number];
942
-
943
- /**
944
- * Visibility toggle
945
- * @dial visibility
946
- * @dial-dtype boolean
947
- * @dial-icon Eye
948
- */
949
- visible: boolean;
950
- }
951
-
952
- FILES
953
- The tool generates the following files in the output directory:
954
-
955
- <component>-raw.json
956
- Raw docgen output with all prop information
957
-
958
- <component>-combined.json
959
- Enhanced metadata including dial schemas and type definitions
960
-
961
- <component>-schemas.json
962
- Extracted dial schemas ready for UI generation
963
-
964
- ENVIRONMENT
965
- NODE_ENV
966
- Set to 'development' for debug output
967
-
968
- SEE ALSO
969
- Documentation: https://uikit.vuer.ai/dial
970
- GitHub: https://github.com/vuer-ai/vuer-uikit
971
-
972
- VERSION
973
- dial-cli ${versionString} (part of @vuer-ai/vuer-uikit)
974
-
975
- AUTHORS
976
- Vuer AI Team <team@vuer.ai>
977
- `;
978
- }
979
-
980
- // Main CLI function
981
- async function main() {
982
- const { values, positionals } = parseArgs({
983
- args: process.argv.slice(2),
984
- options: {
985
- output: {
986
- type: "string",
987
- short: "o",
988
- default: "./metadata",
989
- },
990
- ignore: {
991
- type: "string",
992
- short: "i",
993
- multiple: true,
994
- default: [],
995
- },
996
- help: {
997
- type: "boolean",
998
- short: "h",
999
- },
1000
- version: {
1001
- type: "boolean",
1002
- short: "v",
1003
- },
1004
- verbose: {
1005
- type: "boolean",
1006
- },
1007
- quiet: {
1008
- type: "boolean",
1009
- },
1010
- },
1011
- allowPositionals: true,
1012
- });
1013
-
1014
- // Handle version flag
1015
- if (values.version) {
1016
- const { version, gitHash } = getVersionInfo();
1017
- const versionString = gitHash ? `v${version} (${gitHash})` : `v${version}`;
1018
- console.log(`dial-cli ${versionString} (part of @vuer-ai/vuer-uikit)`);
1019
- process.exit(0);
1020
- }
1021
-
1022
- // Handle help flag or no arguments
1023
- if (values.help || positionals.length === 0) {
1024
- console.log(getHelpText());
1025
- process.exit(0);
1026
- }
1027
-
1028
- // Check for man page style help requests
1029
- if (positionals.length === 1 && (positionals[0] === "man" || positionals[0] === "help")) {
1030
- console.log(getHelpText());
1031
- process.exit(0);
1032
- }
1033
-
1034
- const outputDir = resolve(process.cwd(), values.output as string);
1035
-
1036
- // Process ignore list - handle both multiple -i flags and comma-separated values
1037
- const ignoreList: string[] = [];
1038
- const ignoreValues = values.ignore as string | string[] | undefined;
1039
- if (ignoreValues) {
1040
- if (Array.isArray(ignoreValues)) {
1041
- ignoreValues.forEach(val => {
1042
- // Split by comma in case user provided comma-separated list
1043
- ignoreList.push(...val.split(',').map(s => s.trim()));
1044
- });
1045
- } else if (typeof ignoreValues === 'string') {
1046
- ignoreList.push(...ignoreValues.split(',').map(s => s.trim()));
1047
- }
1048
- }
1049
-
1050
- // Parser options for react-docgen-typescript
1051
- const options: docgen.ParserOptions = {
1052
- savePropValueAsString: true,
1053
- shouldExtractLiteralValuesFromEnum: true,
1054
- shouldRemoveUndefinedFromOptional: true,
1055
- propFilter: (prop) => {
1056
- // Include all props except React internals and ignored props
1057
- if (prop.name === "ref" || prop.name === "key") {
1058
- return false;
1059
- }
1060
- // Check if prop name matches any ignore pattern
1061
- // Support both exact matches and patterns with wildcards
1062
- for (const pattern of ignoreList) {
1063
- if (pattern.includes('*') || pattern.includes('?')) {
1064
- // Simple glob pattern matching
1065
- const regex = new RegExp('^' + pattern.replace(/\*/g, '.*').replace(/\?/g, '.') + '$');
1066
- if (regex.test(prop.name)) {
1067
- return false;
1068
- }
1069
- } else if (prop.name === pattern) {
1070
- // Exact match
1071
- return false;
1072
- }
1073
- }
1074
- return true;
1075
- },
1076
- };
1077
-
1078
- const flags = {
1079
- verbose: values.verbose as boolean | undefined,
1080
- quiet: values.quiet as boolean | undefined,
1081
- ignoreList,
1082
- };
1083
-
1084
- if (!flags.quiet) {
1085
- const { version, gitHash } = getVersionInfo();
1086
- const versionString = gitHash ? `v${version} (${gitHash})` : `v${version}`;
1087
- console.log(`🚀 Dial Documentation Generator ${versionString}`);
1088
- console.log(`📂 Output directory: ${outputDir}`);
1089
- if (flags.verbose && ignoreList.length > 0) {
1090
- console.log(`🚫 Ignoring properties: ${ignoreList.join(', ')}`);
1091
- }
1092
- }
1093
-
1094
- // Process each file
1095
- let filesProcessed = 0;
1096
- let filesErrored = 0;
1097
-
1098
- for (const file of positionals) {
1099
- const filePath = resolve(process.cwd(), file);
1100
-
1101
- if (!existsSync(filePath)) {
1102
- console.error(`❌ File not found: ${filePath}`);
1103
- filesErrored++;
1104
- continue;
1105
- }
1106
-
1107
- try {
1108
- await processFile(filePath, outputDir, options, flags);
1109
- filesProcessed++;
1110
- } catch (error) {
1111
- console.error(`❌ Error processing ${filePath}:`, error);
1112
- filesErrored++;
1113
- }
1114
- }
1115
-
1116
- if (!flags.quiet) {
1117
- console.log("\n✅ Documentation generation complete!");
1118
- if (flags.verbose) {
1119
- console.log(` Files processed: ${filesProcessed}`);
1120
- if (filesErrored > 0) {
1121
- console.log(` Files with errors: ${filesErrored}`);
1122
- }
1123
- }
1124
- }
1125
- }
1126
-
1127
- // Run the CLI - always run when this is the main module
1128
- main().catch((error) => {
1129
- console.error("Fatal error:", error);
1130
- process.exit(1);
1131
- });
1132
-
1133
- export { parseDialTags, propToDialSchema, parseTypeAlias, parseInterfaceProperties };