vibe-design-system 2.8.28 → 2.8.29
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/package.json
CHANGED
|
@@ -525,6 +525,93 @@ function extractTSProps(content) {
|
|
|
525
525
|
return props.length > 0 ? props : null;
|
|
526
526
|
}
|
|
527
527
|
|
|
528
|
+
/**
|
|
529
|
+
* Scan the entire src/ directory for non-Props TypeScript interface and type alias
|
|
530
|
+
* declarations and build a type registry used by story-generator for realistic mock defaults.
|
|
531
|
+
*
|
|
532
|
+
* Returns { TypeName: Array<{ name, type, required }> } or null.
|
|
533
|
+
* Only includes types with ≥2 fields; skips Props/State/Context/Config/Options/Ref types.
|
|
534
|
+
*/
|
|
535
|
+
function extractProjectTypes() {
|
|
536
|
+
const registry = {};
|
|
537
|
+
const seen = new Set();
|
|
538
|
+
|
|
539
|
+
function scanDir(dir) {
|
|
540
|
+
if (!fs.existsSync(dir)) return;
|
|
541
|
+
let entries;
|
|
542
|
+
try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
|
|
543
|
+
for (const e of entries) {
|
|
544
|
+
if (e.isDirectory()) {
|
|
545
|
+
if (IGNORE_DIRS.includes(e.name) || e.name === "__tests__") continue;
|
|
546
|
+
scanDir(path.join(dir, e.name));
|
|
547
|
+
} else if (/\.(ts|tsx)$/.test(e.name) &&
|
|
548
|
+
!e.name.endsWith(".stories.tsx") &&
|
|
549
|
+
!e.name.endsWith(".test.ts") &&
|
|
550
|
+
!e.name.endsWith(".spec.ts")) {
|
|
551
|
+
const fullPath = path.join(dir, e.name);
|
|
552
|
+
let content;
|
|
553
|
+
try { content = fs.readFileSync(fullPath, "utf-8"); } catch { continue; }
|
|
554
|
+
|
|
555
|
+
// Find all export interface / type declarations whose names start with uppercase
|
|
556
|
+
const declRe = /(?:export\s+)?(?:interface|type)\s+([A-Z][A-Za-z0-9_]*)(?:<[^>]*>)?(?:\s+extends\s+[^{]+)?\s*(?:=\s*(?![\|&]))?(?=\s*\{)/g;
|
|
557
|
+
let dm;
|
|
558
|
+
while ((dm = declRe.exec(content)) !== null) {
|
|
559
|
+
const typeName = dm[1];
|
|
560
|
+
// Skip non-data types
|
|
561
|
+
if (/Props$|State$|Context$|Config$|Options$|Params$|Ref$|Handle$|Return$|Result$|Theme$/.test(typeName)) continue;
|
|
562
|
+
if (/^(FC|React|JSX|HTML|Event|Mouse|Key|Touch|Change|Focus|Blur|Scroll|Input|Submit|SVG)/.test(typeName)) continue;
|
|
563
|
+
if (seen.has(typeName)) continue; // first declaration wins
|
|
564
|
+
|
|
565
|
+
// Locate the opening brace of the body
|
|
566
|
+
const braceIdx = content.indexOf("{", dm.index + dm[0].length - 1);
|
|
567
|
+
if (braceIdx === -1) continue;
|
|
568
|
+
|
|
569
|
+
// Walk forward counting braces to find matching closing brace
|
|
570
|
+
let depth = 1, i = braceIdx + 1;
|
|
571
|
+
while (i < content.length && depth > 0) {
|
|
572
|
+
if (content[i] === "{") depth++;
|
|
573
|
+
if (content[i] === "}") depth--;
|
|
574
|
+
i++;
|
|
575
|
+
}
|
|
576
|
+
const block = content.slice(braceIdx + 1, i - 1);
|
|
577
|
+
|
|
578
|
+
// Parse fields — for data types we only skip React/HTML-specific attrs, NOT id/title/name etc.
|
|
579
|
+
const fields = [];
|
|
580
|
+
const fieldRe = /^[ \t]{1,6}(?:readonly\s+)?(\w+)(\?)?:\s*(.+?)[ \t]*;?[ \t]*$/gm;
|
|
581
|
+
let fm;
|
|
582
|
+
while ((fm = fieldRe.exec(block)) !== null) {
|
|
583
|
+
const fieldName = fm[1].trim();
|
|
584
|
+
// Only skip HTML/React presentation attrs — keep data fields like id, title, name, status
|
|
585
|
+
if (/^(className|style|ref|key|htmlFor|tabIndex|role|draggable|aria-|data-)/.test(fieldName)) continue;
|
|
586
|
+
if (block.slice(fm.index).match(/^[ \t]*\/\//)) continue;
|
|
587
|
+
const optional = fm[2] === "?";
|
|
588
|
+
let type = (fm[3] || "")
|
|
589
|
+
.replace(/\s*\/\/.*$/, "")
|
|
590
|
+
.trim()
|
|
591
|
+
.replace(/;$/, "")
|
|
592
|
+
.trim();
|
|
593
|
+
const openB = (type.match(/\{/g) || []).length;
|
|
594
|
+
const closeB = (type.match(/\}/g) || []).length;
|
|
595
|
+
if (openB !== closeB) continue;
|
|
596
|
+
type = type.replace(/React\.ReactNode/g, "ReactNode");
|
|
597
|
+
if (type.length > 60) type = type.slice(0, 57) + "...";
|
|
598
|
+
fields.push({ name: fieldName, type, required: !optional });
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// Only register types with at least 2 fields (otherwise not useful for mock generation)
|
|
602
|
+
if (fields.length >= 2) {
|
|
603
|
+
registry[typeName] = fields;
|
|
604
|
+
seen.add(typeName);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
scanDir(SRC_DIR);
|
|
612
|
+
return Object.keys(registry).length > 0 ? registry : null;
|
|
613
|
+
}
|
|
614
|
+
|
|
528
615
|
/**
|
|
529
616
|
* Extract named UI patterns from a feature-tier file.
|
|
530
617
|
*
|
|
@@ -2401,6 +2488,8 @@ function scan() {
|
|
|
2401
2488
|
if (Object.keys(colorUsage).length > 0) foundations.colorUsage = colorUsage;
|
|
2402
2489
|
const gridSystem = extractGridSystem(SRC_DIR);
|
|
2403
2490
|
if (gridSystem) foundations.gridSystem = gridSystem;
|
|
2491
|
+
const projectTypes = extractProjectTypes();
|
|
2492
|
+
if (projectTypes) foundations.types = projectTypes;
|
|
2404
2493
|
const componentSuggestions = extractComponentSuggestions();
|
|
2405
2494
|
const unreleasedSectionCandidates = extractUnreleasedSectionCandidates();
|
|
2406
2495
|
const output = {
|
|
@@ -24,6 +24,8 @@ const STORIES_DIR = path.join(SRC_DIR, "stories");
|
|
|
24
24
|
let FOUNDATIONS_DATA = null;
|
|
25
25
|
// componentsRelDir from vds-output.json — defaults to "src/components"
|
|
26
26
|
let COMPONENTS_REL_DIR = "src/components";
|
|
27
|
+
// Type registry extracted by scan.mjs from project's src/ — { TypeName: [{name, type, required}] }
|
|
28
|
+
let PROJECT_TYPE_REGISTRY = {};
|
|
27
29
|
|
|
28
30
|
// CSS is loaded from .storybook/preview.tsx — never add CSS import to story files.
|
|
29
31
|
|
|
@@ -636,10 +638,83 @@ function isDateLikeField(name) {
|
|
|
636
638
|
}
|
|
637
639
|
|
|
638
640
|
/** Build 2-3 mock items for array prop from component source (item type fields). Uses valid ISO date strings for date-like fields to avoid "Invalid time value". */
|
|
641
|
+
/**
|
|
642
|
+
* Generate a mock JavaScript value string from a TypeScript type string.
|
|
643
|
+
* Consults PROJECT_TYPE_REGISTRY for custom object types (Task, Project, etc.).
|
|
644
|
+
* depth prevents infinite recursion on circular types.
|
|
645
|
+
*/
|
|
646
|
+
function genMockValue(type, depth = 0) {
|
|
647
|
+
if (depth > 2 || !type) return '"example"';
|
|
648
|
+
type = type.trim();
|
|
649
|
+
if (/^string(\s*\|.*)?$/.test(type)) return '"example"';
|
|
650
|
+
if (/^number/.test(type)) return '0';
|
|
651
|
+
if (/^boolean/.test(type)) return 'false';
|
|
652
|
+
if (/^Date\b/.test(type) || /^(createdAt|updatedAt|startDate|endDate|dueDate)$/.test(type)) return 'new Date().toISOString()';
|
|
653
|
+
// String literal union (single or double quoted) — pick first option
|
|
654
|
+
if (/^['"][^'"]+['"](\s*\|\s*['"][^'"]+['"])*$/.test(type)) {
|
|
655
|
+
const first = type.match(/['"]([^'"]+)['"]/);
|
|
656
|
+
return first ? `"${first[1]}"` : '"example"';
|
|
657
|
+
}
|
|
658
|
+
// ReactNode
|
|
659
|
+
if (/ReactNode/.test(type)) return '"Content"';
|
|
660
|
+
// Array: Type[] or Array<Type>
|
|
661
|
+
if (/\[\]$/.test(type)) {
|
|
662
|
+
const base = type.replace(/\[\]$/, "").trim();
|
|
663
|
+
if (/^string/.test(base)) return '["Example"]';
|
|
664
|
+
const inner = genMockValue(base, depth + 1);
|
|
665
|
+
return `[${inner}]`;
|
|
666
|
+
}
|
|
667
|
+
const arrMatch = type.match(/^Array<(.+)>$/);
|
|
668
|
+
if (arrMatch) {
|
|
669
|
+
const inner = genMockValue(arrMatch[1].trim(), depth + 1);
|
|
670
|
+
return `[${inner}]`;
|
|
671
|
+
}
|
|
672
|
+
// Custom type from registry — generate mock object
|
|
673
|
+
if (PROJECT_TYPE_REGISTRY[type]) return genMockObjectFromRegistry(type, depth);
|
|
674
|
+
return '"example"';
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
/**
|
|
678
|
+
* Generate a JS object literal string for a type defined in PROJECT_TYPE_REGISTRY.
|
|
679
|
+
* Uses only required fields (or first 3 fields if all are optional).
|
|
680
|
+
*/
|
|
681
|
+
function genMockObjectFromRegistry(typeName, depth = 0) {
|
|
682
|
+
const fields = PROJECT_TYPE_REGISTRY[typeName];
|
|
683
|
+
if (!fields || fields.length === 0) return "{}";
|
|
684
|
+
const required = fields.filter(f => f.required);
|
|
685
|
+
const toUse = required.length > 0 ? required : fields.slice(0, 3);
|
|
686
|
+
const pairs = toUse.map(f => {
|
|
687
|
+
const val = f.name === "id" ? '"1"' : genMockValue(f.type, depth + 1);
|
|
688
|
+
return `${f.name}: ${val}`;
|
|
689
|
+
});
|
|
690
|
+
return `{ ${pairs.join(", ")} }`;
|
|
691
|
+
}
|
|
692
|
+
|
|
639
693
|
function buildMockArrayItems(componentSource, itemTypeName, propName) {
|
|
640
694
|
const fields = getItemTypeFieldsFromSource(componentSource, itemTypeName);
|
|
641
695
|
const propLower = (propName || "").toLowerCase();
|
|
642
696
|
const looksLikeTimeEntries = /entries|rows|logs|data|items|times/.test(propLower) || /time|table|log/.test(propLower);
|
|
697
|
+
|
|
698
|
+
// If source-based lookup failed, try the type registry for realistic field names
|
|
699
|
+
if (fields.length === 0 && itemTypeName && PROJECT_TYPE_REGISTRY[itemTypeName]) {
|
|
700
|
+
const regFields = PROJECT_TYPE_REGISTRY[itemTypeName];
|
|
701
|
+
const baseDate = new Date("2024-01-15T12:00:00.000Z").getTime();
|
|
702
|
+
const items = [];
|
|
703
|
+
for (let i = 1; i <= 2; i++) {
|
|
704
|
+
const item = {};
|
|
705
|
+
for (const f of regFields.slice(0, 8)) {
|
|
706
|
+
if (f.name === "id") item[f.name] = String(i);
|
|
707
|
+
else if (isDateLikeField(f.name) || /^Date/.test(f.type)) item[f.name] = new Date(baseDate + i * 86400000).toISOString();
|
|
708
|
+
else if (/^number/.test(f.type)) item[f.name] = i;
|
|
709
|
+
else if (/^boolean/.test(f.type)) item[f.name] = false;
|
|
710
|
+
else if (/^['"][^'"]+['"]/.test(f.type)) { const m = f.type.match(/['"]([^'"]+)['"]/); item[f.name] = m ? m[1] : "example"; }
|
|
711
|
+
else item[f.name] = `Example ${i}`;
|
|
712
|
+
}
|
|
713
|
+
items.push(item);
|
|
714
|
+
}
|
|
715
|
+
return items;
|
|
716
|
+
}
|
|
717
|
+
|
|
643
718
|
const defaultFields = looksLikeTimeEntries
|
|
644
719
|
? ["id", "date", "start", "end", "title", "value"]
|
|
645
720
|
: ["label", "value", "title", "id", "name", "description"];
|
|
@@ -785,6 +860,27 @@ function buildDefaultArgsForRequiredProps(props, usageFromPages = null, componen
|
|
|
785
860
|
const type = String(p.type || "").trim();
|
|
786
861
|
const name = p.name;
|
|
787
862
|
if (added.has(name)) continue;
|
|
863
|
+
|
|
864
|
+
// ── Registry-based lookup (highest priority for custom object types) ──────
|
|
865
|
+
// Direct type match: prop type IS a known project type (e.g. task: Task)
|
|
866
|
+
if (PROJECT_TYPE_REGISTRY[type]) {
|
|
867
|
+
argLines.push(` ${name}: ${genMockObjectFromRegistry(type)},`);
|
|
868
|
+
added.add(name); continue;
|
|
869
|
+
}
|
|
870
|
+
// Array of known type: tasks: Task[] or tasks: Array<Task>
|
|
871
|
+
const arrTypeMatch = type.match(/^([A-Z][A-Za-z0-9]+)\[\]$/) || type.match(/^Array<([A-Z][A-Za-z0-9]+)>$/);
|
|
872
|
+
if (arrTypeMatch && PROJECT_TYPE_REGISTRY[arrTypeMatch[1]]) {
|
|
873
|
+
argLines.push(` ${name}: [${genMockObjectFromRegistry(arrTypeMatch[1])}],`);
|
|
874
|
+
added.add(name); continue;
|
|
875
|
+
}
|
|
876
|
+
// Prop name matches a registry type (e.g. prop named "task" of type "object")
|
|
877
|
+
const nameCapitalized = name.charAt(0).toUpperCase() + name.slice(1);
|
|
878
|
+
if (/object|any|unknown/.test(type) && PROJECT_TYPE_REGISTRY[nameCapitalized]) {
|
|
879
|
+
argLines.push(` ${name}: ${genMockObjectFromRegistry(nameCapitalized)},`);
|
|
880
|
+
added.add(name); continue;
|
|
881
|
+
}
|
|
882
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
883
|
+
|
|
788
884
|
if (name === "filters" || /dateRange|Filter.*object/.test(type)) {
|
|
789
885
|
argLines.push(` ${name}: ${DATE_RANGE_DEFAULT},`);
|
|
790
886
|
added.add(name);
|
|
@@ -1098,9 +1194,9 @@ function buildArgTypeEntry(prop) {
|
|
|
1098
1194
|
if (/^number/.test(t)) {
|
|
1099
1195
|
return `{ control: "number", description: "number" }`;
|
|
1100
1196
|
}
|
|
1101
|
-
// String union literals: "a" | "b" |
|
|
1102
|
-
if (/^"[^"]+"(\s*\|\s*"[^"]+")+/.test(t)) {
|
|
1103
|
-
const opts = (t.match(/"([^"]+)"/g) || []).map(s => s.replace(/"/g, ""));
|
|
1197
|
+
// String union literals (single or double quoted): "a" | "b" or 'a' | 'b' → select control
|
|
1198
|
+
if (/^['"][^'"]+['"](\s*\|\s*['"][^'"]+['"])+/.test(t)) {
|
|
1199
|
+
const opts = (t.match(/['"]([^'"]+)['"]/g) || []).map(s => s.replace(/['"]/g, ""));
|
|
1104
1200
|
return `{ control: "select", options: ${JSON.stringify(opts)}, description: ${JSON.stringify(t)} }`;
|
|
1105
1201
|
}
|
|
1106
1202
|
// Plain string (including union with null/undefined)
|
|
@@ -2803,6 +2899,10 @@ function main() {
|
|
|
2803
2899
|
const components = Array.isArray(data.components) ? data.components : [];
|
|
2804
2900
|
const foundations = data.foundations || null;
|
|
2805
2901
|
FOUNDATIONS_DATA = foundations;
|
|
2902
|
+
// Load project type registry for realistic mock arg generation
|
|
2903
|
+
if (foundations?.types && typeof foundations.types === "object") {
|
|
2904
|
+
PROJECT_TYPE_REGISTRY = foundations.types;
|
|
2905
|
+
}
|
|
2806
2906
|
|
|
2807
2907
|
const onlyName = process.argv[2] || null;
|
|
2808
2908
|
|