vibe-design-system 2.9.1 → 2.9.2

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibe-design-system",
3
- "version": "2.9.1",
3
+ "version": "2.9.2",
4
4
  "description": "Auto-generate design systems for vibe coding projects",
5
5
  "homepage": "https://vibedesign.tech",
6
6
  "repository": {
@@ -5356,8 +5356,9 @@ function extractComponentProps(content, componentName) {
5356
5356
 
5357
5357
  /**
5358
5358
  * Generate a mock value string for a TypeScript type, using available data exports.
5359
+ * Pass sourceContent to enable local interface/type parsing and nested property lookup.
5359
5360
  */
5360
- function mockValueForType(type, localExports) {
5361
+ function mockValueForType(type, localExports, sourceContent) {
5361
5362
  const t = type.trim();
5362
5363
  if (t === "boolean") return "false";
5363
5364
  if (t === "string") return '""';
@@ -5373,26 +5374,60 @@ function mockValueForType(type, localExports) {
5373
5374
  const firstLiteral = t.match(/["']([^"']+)["']/);
5374
5375
  if (firstLiteral && (t.startsWith('"') || t.startsWith("'"))) return `"${firstLiteral[1]}"`;
5375
5376
 
5376
- // Try matching named type to available array exports
5377
- // Strip common suffixes to get the "base" name (e.g., RouteDef→route, SubStation→substation)
5378
- const typeLower = t.toLowerCase().replace(/def$|id$|key$|type$|interface$|kind$/i, "");
5379
- if (typeLower.length >= 3) {
5377
+ // Named PascalCase type try multiple resolution strategies
5378
+ if (/^[A-Z][A-Za-z0-9]+$/.test(t)) {
5379
+ const typeLower = t.toLowerCase().replace(/def$|id$|key$|type$|interface$|kind$/i, "");
5380
+
5381
+ // Strategy 1: Direct top-level array match (RouteDef → ROUTES[0])
5382
+ if (typeLower.length >= 3) {
5383
+ for (const [expName, expKind] of Object.entries(localExports)) {
5384
+ if (expKind !== "array") continue;
5385
+ const expBase = expName.toLowerCase().replace(/_/g, "").replace(/s$/, "").replace(/data$/, "");
5386
+ if (expBase.slice(0, 4) === typeLower.slice(0, 4) || typeLower.slice(0, 4) === expBase.slice(0, 4)) {
5387
+ return `${expName}[0]`;
5388
+ }
5389
+ }
5390
+ }
5391
+
5392
+ // Strategy 2: Local interface/type alias → generate inline object
5393
+ // e.g. FilterState → { level1: null, level2: null }
5394
+ if (sourceContent) {
5395
+ const esc = t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5396
+ const bodyRe = new RegExp(`(?:interface|type)\\s+${esc}\\s*(?:=\\s*)?\\{([^}]+)\\}`);
5397
+ const m = sourceContent.match(bodyRe);
5398
+ if (m) {
5399
+ const fields = m[1].split(";").map(s => s.trim()).filter(s => s.length > 0 && !s.startsWith("//"));
5400
+ const fieldPairs = fields.map(f => {
5401
+ const ci = f.indexOf(":");
5402
+ if (ci < 0) return null;
5403
+ const fname = f.slice(0, ci).trim().replace(/\?$/, "");
5404
+ const ftype = f.slice(ci + 1).trim();
5405
+ // Resolve field type (no recursion with sourceContent to prevent deep loops)
5406
+ const fmock = mockValueForType(ftype, localExports, null);
5407
+ return ` ${fname}: ${fmock}`;
5408
+ }).filter(Boolean);
5409
+ if (fieldPairs.length > 0) return `{\n${fieldPairs.join(",\n")}\n}`;
5410
+ }
5411
+ }
5412
+
5413
+ // Strategy 3: Nested array property lookup
5414
+ // SubStation → DATA[0]?.subStations?.[0] (camelCase plural of type name)
5415
+ const camel = t[0].toLowerCase() + t.slice(1);
5416
+ const plural = camel + "s";
5380
5417
  for (const [expName, expKind] of Object.entries(localExports)) {
5381
5418
  if (expKind !== "array") continue;
5382
- const expBase = expName.toLowerCase().replace(/_/g, "").replace(/s$/, "").replace(/data$/, "");
5383
- // Direct match: RouteDef → ROUTES (routedef→rout vs routes→rout)
5384
- if (expBase.slice(0, 4) === typeLower.slice(0, 4) || typeLower.slice(0, 4) === expBase.slice(0, 4)) {
5385
- return `${expName}[0]`;
5386
- }
5419
+ return `${expName}[0]?.${plural}?.[0]`;
5387
5420
  }
5388
5421
  }
5422
+
5389
5423
  return "undefined as any";
5390
5424
  }
5391
5425
 
5392
5426
  /**
5393
5427
  * Build the story file content for a single internal component.
5428
+ * Automatically uses render() format when props require complex/nested mock data.
5394
5429
  */
5395
- function buildInternalComponentStoryContent(compName, parentImportPath, dataImportPath, dataSymbols, localExports, props, parentComp) {
5430
+ function buildInternalComponentStoryContent(compName, parentImportPath, dataImportPath, dataSymbols, localExports, props, parentComp, sourceContent) {
5396
5431
  const lines = [];
5397
5432
  lines.push(`// @vds-regenerate — VDS auto-generated. Remove this line to prevent overwrite.`);
5398
5433
  lines.push(`import React from "react";`);
@@ -5418,16 +5453,72 @@ function buildInternalComponentStoryContent(compName, parentImportPath, dataImpo
5418
5453
  lines.push(`type Story = StoryObj<typeof meta>;`);
5419
5454
  lines.push(``);
5420
5455
 
5421
- if (props.length > 0) {
5422
- const argLines = props.map(p => ` ${p.name}: ${mockValueForType(p.type, localExports)},`).join("\n");
5456
+ if (props.length === 0) {
5457
+ lines.push(`export const Default: Story = {};`);
5458
+ return lines.join("\n") + "\n";
5459
+ }
5460
+
5461
+ const mockedProps = props.map(p => ({
5462
+ name: p.name,
5463
+ mock: mockValueForType(p.type, localExports, sourceContent),
5464
+ }));
5465
+
5466
+ // Use render() format when any mock is complex: nested path (?.),
5467
+ // multiline inline object (\n), or unresolved (undefined as any)
5468
+ const needsRender = mockedProps.some(p =>
5469
+ p.mock === "undefined as any" ||
5470
+ p.mock.includes("?.") ||
5471
+ p.mock.includes("\n")
5472
+ );
5473
+
5474
+ if (!needsRender) {
5475
+ // Simple args format — all values are primitives or direct data refs
5476
+ const argLines = mockedProps.map(p => ` ${p.name}: ${p.mock},`).join("\n");
5423
5477
  lines.push(`export const Default: Story = {`);
5424
5478
  lines.push(` args: {`);
5425
5479
  lines.push(argLines);
5426
5480
  lines.push(` },`);
5427
5481
  lines.push(`};`);
5428
5482
  } else {
5429
- lines.push(`export const Default: Story = {};`);
5483
+ // render() format declare complex vars, guard against undefined, then render
5484
+ const varDecls = [];
5485
+ const propAttrs = [];
5486
+ const guardVarNames = [];
5487
+
5488
+ for (const { name, mock } of mockedProps) {
5489
+ if (mock.includes("?.") || mock === "undefined as any") {
5490
+ // Nested/unknown: declare variable + add to guard list
5491
+ const varName = `mock${name[0].toUpperCase() + name.slice(1)}`;
5492
+ varDecls.push(` const ${varName} = ${mock};`);
5493
+ guardVarNames.push(varName);
5494
+ propAttrs.push(` ${name}={${varName} as any}`);
5495
+ } else if (mock.includes("\n")) {
5496
+ // Multiline inline object (e.g. interface mock)
5497
+ const varName = `mock${name[0].toUpperCase() + name.slice(1)}`;
5498
+ const indented = mock.split("\n").map((l, i) => i === 0 ? l : " " + l).join("\n");
5499
+ varDecls.push(` const ${varName} = ${indented} as any;`);
5500
+ propAttrs.push(` ${name}={${varName}}`);
5501
+ } else {
5502
+ propAttrs.push(` ${name}={${mock}}`);
5503
+ }
5504
+ }
5505
+
5506
+ lines.push(`export const Default: Story = {`);
5507
+ lines.push(` render: () => {`);
5508
+ for (const v of varDecls) lines.push(v);
5509
+ if (guardVarNames.length > 0) {
5510
+ const check = guardVarNames.map(v => `!${v}`).join(" || ");
5511
+ lines.push(` if (${check}) return <div style={{ color: "#888", padding: "1rem" }}>⚠️ Mock data unavailable for <strong>${compName}</strong></div>;`);
5512
+ }
5513
+ lines.push(` return (`);
5514
+ lines.push(` <${compName}`);
5515
+ for (const attr of propAttrs) lines.push(attr);
5516
+ lines.push(` />`);
5517
+ lines.push(` );`);
5518
+ lines.push(` },`);
5519
+ lines.push(`};`);
5430
5520
  }
5521
+
5431
5522
  return lines.join("\n") + "\n";
5432
5523
  }
5433
5524
 
@@ -5495,10 +5586,14 @@ function generateInternalComponentStories(comp) {
5495
5586
  // Step 4: Generate individual story files
5496
5587
  for (const name of names) {
5497
5588
  const storyFile = path.join(STORIES_DIR, `${name}.stories.tsx`);
5498
- if (fs.existsSync(storyFile)) continue; // Never overwrite existing stories
5589
+ if (fs.existsSync(storyFile)) {
5590
+ // Preserve user-modified stories; overwrite only VDS-generated ones
5591
+ const existing = fs.readFileSync(storyFile, "utf-8");
5592
+ if (!existing.startsWith("// @vds-regenerate")) continue;
5593
+ }
5499
5594
  const props = extractComponentProps(sourceContent, name);
5500
5595
  const content = buildInternalComponentStoryContent(
5501
- name, parentImportPath, dataImportFromStories, bestSymbols, bestExports, props, comp
5596
+ name, parentImportPath, dataImportFromStories, bestSymbols, bestExports, props, comp, sourceContent
5502
5597
  );
5503
5598
  fs.writeFileSync(storyFile, content, "utf-8");
5504
5599
  console.log(`[VDS] Wrote ${path.relative(PROJECT_ROOT, storyFile)} (sub-component of ${comp.name})`);