vibe-design-system 2.8.54 → 2.8.58

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/bin/init.js CHANGED
@@ -15,7 +15,7 @@
15
15
  import fs from "fs";
16
16
  import path from "path";
17
17
  import { fileURLToPath } from "url";
18
- import { spawnSync } from "child_process";
18
+ import { spawnSync, spawn } from "child_process";
19
19
 
20
20
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
21
21
  const INSTALLER_ROOT = path.join(__dirname, "..");
@@ -606,9 +606,16 @@ function runStorybookAdapt(projectRoot) {
606
606
  }
607
607
 
608
608
  // ADIM 9b — vds.config.js oluştur (yoksa)
609
- function ensureVdsConfig(projectRoot) {
609
+ function ensureVdsConfig(projectRoot, srcPrefix) {
610
610
  const configPath = path.join(projectRoot, "vds.config.js");
611
611
  if (fs.existsSync(configPath)) return;
612
+
613
+ // includeGroups is intentionally OFF by default.
614
+ // shadcn/ui base components (Button, Avatar, Badge…) are generic library primitives —
615
+ // most projects only want their custom sections and page components in Storybook.
616
+ // To include shadcn/ui components: add includeGroups: ["shadcn", "UI", "ui"] to vds.config.js.
617
+ const includeGroupsLine = ` // includeGroups: ["shadcn", "UI"], // Uncomment to add shadcn/ui primitive stories\n`;
618
+
612
619
  const content = `/**
613
620
  * VDS Configuration — auto-created by VDS installer
614
621
  * All fields are optional. Remove comments to activate overrides.
@@ -617,7 +624,7 @@ module.exports = {
617
624
  // skipList: ["MyHeavyPage"], // Replace the default story skip list entirely
618
625
  // extraSkipList: ["OrderCard"], // Add to the default skip list
619
626
  // extraIgnoreDirs: ["fixtures"], // Additional dirs to skip during component scan
620
- };
627
+ ${includeGroupsLine}};
621
628
  `;
622
629
  fs.writeFileSync(configPath, content, "utf-8");
623
630
  console.log("⚙️ vds.config.js oluşturuldu.");
@@ -697,7 +704,7 @@ ensureStoriesDir(projectRoot, srcPrefix);
697
704
  removeStorybookExamples(projectRoot, srcPrefix);
698
705
 
699
706
  // ADIM 9b
700
- ensureVdsConfig(projectRoot);
707
+ ensureVdsConfig(projectRoot, srcPrefix);
701
708
 
702
709
  // ADIM 7
703
710
  runScan(projectRoot);
@@ -707,8 +714,18 @@ runStorybookAdapt(projectRoot);
707
714
 
708
715
  // ADIM 8
709
716
  console.log("\n✅ VDS kuruldu!");
710
- console.log("→ npm run storybook — design system'ı aç (localhost:6006)");
711
- console.log("→ npm run storybook:bg — Storybook'u arka planda başlat (Cursor kapansa bile çalışır)");
712
717
  console.log("→ npm run vds:stories — tarama + story + provider (tek komut)");
713
718
  console.log("→ npm run vds:watch — otomatik güncelleme (watch modu)");
714
719
  console.log("\nNot: Storybook başlarken (Node 24+) DEP0190 uyarısı çıkarsa Storybook kaynaklıdır, güvenle yok sayabilirsiniz.\n");
720
+
721
+ // ADIM 10 — Storybook'u otomatik başlat
722
+ console.log("🚀 Storybook başlatılıyor → http://localhost:6006\n");
723
+ const sbProc = spawn("npm", ["run", "storybook"], {
724
+ stdio: "inherit",
725
+ cwd: projectRoot,
726
+ shell: process.platform === "win32",
727
+ });
728
+ sbProc.on("error", (err) => {
729
+ console.error("[VDS] Storybook başlatılamadı:", err.message);
730
+ console.log("→ Manuel başlatmak için: npm run storybook");
731
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibe-design-system",
3
- "version": "2.8.54",
3
+ "version": "2.8.58",
4
4
  "description": "Auto-generate design systems for vibe coding projects",
5
5
  "homepage": "https://vibedesign.tech",
6
6
  "repository": {
@@ -246,10 +246,9 @@ const RECIPES = {
246
246
  )`,
247
247
  },
248
248
  Avatar: {
249
- imports: ["AvatarImage", "AvatarFallback"],
249
+ imports: ["AvatarFallback"],
250
250
  render: `(args) => (
251
251
  <ComponentRef {...args}>
252
- <AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
253
252
  <AvatarFallback>JD</AvatarFallback>
254
253
  </ComponentRef>
255
254
  )`,
@@ -425,13 +424,21 @@ function componentWrapsVoidElement(source) {
425
424
  }
426
425
 
427
426
  function toSafeComponentName(name, file) {
428
- if (name && typeof name === "string") {
429
- return name.replace(/[^A-Za-z0-9]+/g, " ").trim().replace(/\s+([a-z])/g, (_, c) => c.toUpperCase()).replace(/^\w/, (c) => c.toUpperCase()).replace(/\s+/g, "");
430
- }
427
+ // Convert any string (spaces, hyphens, underscores) to PascalCase.
428
+ // If no separators present, treat as already-PascalCase and just capitalise the first letter.
429
+ const toPascal = (s) => {
430
+ if (!s) return "Component";
431
+ if (!/[-\s_]/.test(s)) return s.charAt(0).toUpperCase() + s.slice(1); // already camel/PascalCase
432
+ return s.replace(/[^A-Za-z0-9]+/g, " ").trim()
433
+ .split(" ").filter(Boolean)
434
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
435
+ .join("") || "Component";
436
+ };
437
+ if (name && typeof name === "string") return toPascal(name);
431
438
  const base = (file || "").replace(/\.[^.]+$/, "");
432
439
  const parts = base.split(/[\\/]/g);
433
440
  const last = parts[parts.length - 1] || "Component";
434
- return last.charAt(0).toUpperCase() + last.slice(1);
441
+ return toPascal(last);
435
442
  }
436
443
 
437
444
  function parseUnionLiterals(type) {
@@ -873,6 +880,58 @@ const COMPONENT_EXTRA_ARGS = {
873
880
  ...(VDS_CONFIG.componentExtraArgs ?? {}),
874
881
  };
875
882
 
883
+ // ---------------------------------------------------------------------------
884
+ // USAGE SCANNER — reads project sources to find actual component usage patterns
885
+ // e.g. <Button className="bg-[#5409da] text-white rounded-[10px]"> → generates
886
+ // a "ProjectPrimary" story with the real project styling instead of generic CVA variants.
887
+ // ---------------------------------------------------------------------------
888
+ const _SCAN_IGNORE_DIRS = new Set(["node_modules", ".next", "dist", "build", ".git", ".storybook"]);
889
+
890
+ function scanComponentUsages(componentName, projectRoot) {
891
+ const uiPathFrag = path.join("components", "ui") + path.sep;
892
+ const classNameCounts = new Map(); // className → count
893
+
894
+ function walk(dir) {
895
+ let entries;
896
+ try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
897
+ for (const e of entries) {
898
+ if (_SCAN_IGNORE_DIRS.has(e.name)) continue;
899
+ const full = path.join(dir, e.name);
900
+ if (e.isDirectory()) { walk(full); continue; }
901
+ if (!/\.(tsx|jsx|ts|js)$/.test(e.name)) continue;
902
+ if (full.includes(uiPathFrag)) continue; // Skip ui/ directory itself
903
+ if (e.name.includes(".stories.")) continue; // Skip story files
904
+ let src;
905
+ try { src = fs.readFileSync(full, "utf-8"); } catch { continue; }
906
+ if (!src.includes(`<${componentName}`)) continue;
907
+
908
+ // Match <ComponentName ... > or <ComponentName ... />
909
+ const re = new RegExp(`<${componentName}(?:\\s+([^>]*?))?(?:\\s*/>|\\s*>)`, "g");
910
+ let m;
911
+ while ((m = re.exec(src)) !== null) {
912
+ const propsStr = m[1] || "";
913
+ // Extract className="..." — single or double quotes
914
+ const classM = propsStr.match(/className=["']([^"']+)["']/);
915
+ if (!classM) continue;
916
+ const cn = classM[1].trim();
917
+ if (!cn) continue;
918
+ // Only include usages that have project-specific (non-CVA) styling
919
+ // i.e. hex colors, arbitrary Tailwind values, custom font families
920
+ const hasCustomStyling = /bg-\[|text-\[|rounded-\[|#[0-9a-fA-F]{3,8}|\[font-family:/.test(cn);
921
+ if (!hasCustomStyling) continue;
922
+ classNameCounts.set(cn, (classNameCounts.get(cn) || 0) + 1);
923
+ }
924
+ }
925
+ }
926
+
927
+ walk(projectRoot);
928
+
929
+ return [...classNameCounts.entries()]
930
+ .sort((a, b) => b[1] - a[1])
931
+ .slice(0, 4)
932
+ .map(([className, count]) => ({ className, count }));
933
+ }
934
+
876
935
  /** Render'da args'a uygulanacak fallback (useState(estimate.toString()) gibi kullanımlar için; args/Controls undefined yapsa bile). */
877
936
  const RENDER_ARGS_FALLBACKS = {
878
937
  TaskEstimateInput: ", estimate: (args && args.estimate) ?? 0, value: (args && args.value) ?? 0, task: (args && args.task) ?? { id: \"1\", title: \"Example\", estimate: 0 }",
@@ -1686,16 +1745,26 @@ function buildProfileChildrenArgLine(profile) {
1686
1745
 
1687
1746
  function buildStoryFileContent(comp) {
1688
1747
  const componentName = toSafeComponentName(comp.name, comp.file);
1689
- const fileNoExt = comp.file.replace(/\.(tsx|jsx)$/, "");
1690
- // Compute import path relative to src/stories/ using the actual components directory.
1691
- let importPath = "";
1692
- const isPageFile = fileNoExt.startsWith("pages/") || fileNoExt.startsWith("src/pages/");
1693
- if (isPageFile) {
1694
- // Keep full path with src/ prefix so relative is computed correctly from src/stories/
1695
- // e.g. "src/pages/Admin" → "../pages/Admin" (NOT "../../pages/Admin")
1696
- const fullPath = fileNoExt.startsWith("src/") ? fileNoExt : "src/" + fileNoExt;
1697
- importPath = path.posix.relative("src/stories", fullPath);
1748
+ const fileNoExt = comp.file.replace(/\.(tsx|jsx|ts|js)$/, "");
1749
+
1750
+ // Determine if comp.file is project-root-relative (e.g. "client/src/pages/Foo.tsx")
1751
+ // or components-dir-relative (e.g. "ui/badge.tsx").
1752
+ // Try project-root first; fall back to components-dir.
1753
+ const absoluteFromRoot = path.join(PROJECT_ROOT, comp.file);
1754
+ const absoluteFromComponents = path.join(PROJECT_ROOT, COMPONENTS_REL_DIR, comp.file);
1755
+ const isProjectRelative = fs.existsSync(absoluteFromRoot);
1756
+ const srcPath = isProjectRelative ? absoluteFromRoot : absoluteFromComponents;
1757
+
1758
+ // isPageFile: detect components that live inside a pages/ directory
1759
+ const isPageFile = fileNoExt.includes("/pages/") || fileNoExt.startsWith("pages/");
1760
+
1761
+ // Compute import path relative to src/stories/
1762
+ let importPath;
1763
+ if (isProjectRelative) {
1764
+ // File path is already relative to project root — use directly
1765
+ importPath = path.posix.relative("src/stories", fileNoExt);
1698
1766
  } else {
1767
+ // File path is relative to components dir — prefix with components dir
1699
1768
  const targetPath = path.posix.join(COMPONENTS_REL_DIR, fileNoExt);
1700
1769
  importPath = path.posix.relative("src/stories", targetPath);
1701
1770
  }
@@ -1709,10 +1778,6 @@ function buildStoryFileContent(comp) {
1709
1778
  // Title: "Module/ComponentName" (category intentionally dropped — folder is the context)
1710
1779
  const title = `${group}/${componentName}`;
1711
1780
 
1712
- const srcPath = isPageFile
1713
- ? path.join(PROJECT_ROOT, fileNoExt.replace(/^src\//, "src/") + ".tsx")
1714
- : path.join(PROJECT_ROOT, COMPONENTS_REL_DIR, comp.file);
1715
-
1716
1781
  // Read component source once (used for export style, props, CVA parsing)
1717
1782
  let source = "";
1718
1783
  try {
@@ -1853,6 +1918,28 @@ function buildStoryFileContent(comp) {
1853
1918
  lines.push(`};`);
1854
1919
  }
1855
1920
 
1921
+ // --- Project-specific usage stories ---
1922
+ // Scan actual component usages in project source. If custom className overrides found
1923
+ // (hex colors, arbitrary Tailwind values), append stories that show the REAL project styling.
1924
+ const usagePatterns = scanComponentUsages(componentName, PROJECT_ROOT);
1925
+ if (usagePatterns.length > 0) {
1926
+ const usageStoryNames = ["ProjectPrimary", "ProjectSecondary", "ProjectTertiary", "ProjectQuaternary"];
1927
+ lines.push("");
1928
+ lines.push(`// — Project-specific stories auto-detected from actual ${componentName} usage in this codebase.`);
1929
+ lines.push(`// These reflect how ${componentName} is styled in the project, not generic variants.`);
1930
+ for (let i = 0; i < usagePatterns.length; i++) {
1931
+ const { className } = usagePatterns[i];
1932
+ const sName = usageStoryNames[i];
1933
+ lines.push(`export const ${sName}: Story = {`);
1934
+ lines.push(` render: () => (`);
1935
+ lines.push(` <ComponentRef className="${className}">`);
1936
+ lines.push(` ${componentName}`);
1937
+ lines.push(` </ComponentRef>`);
1938
+ lines.push(` ),`);
1939
+ lines.push(`};`);
1940
+ }
1941
+ }
1942
+
1856
1943
  return lines.join("\n");
1857
1944
  }
1858
1945
 
@@ -3482,6 +3569,12 @@ function main() {
3482
3569
  if (!onlyName) {
3483
3570
  try {
3484
3571
  const existing = fs.readdirSync(STORIES_DIR);
3572
+ // Remove macOS copy artifacts: "Button.stories 2.tsx", "Badge.stories 3.tsx", etc.
3573
+ for (const name of existing) {
3574
+ if (/\.stories \d+\.(tsx|ts|jsx|js)$/.test(name)) {
3575
+ try { fs.unlinkSync(path.join(STORIES_DIR, name)); } catch { /* ignore */ }
3576
+ }
3577
+ }
3485
3578
  for (const name of existing) {
3486
3579
  if (name === "foundations") continue;
3487
3580
  if (
@@ -3509,6 +3602,7 @@ function main() {
3509
3602
  }
3510
3603
  }
3511
3604
 
3605
+ let writtenCount = 0;
3512
3606
  for (const comp of components) {
3513
3607
  const componentName = toSafeComponentName(comp.name, comp.file);
3514
3608
  // Skip unclassified and shadcn/ui primitives (UI group) — they're documented at ui.shadcn.com
@@ -3518,8 +3612,9 @@ function main() {
3518
3612
  // Users can override with vds.config.js: includeGroups: ["shadcn"] or includeComponents: ["Button", "Badge"]
3519
3613
  const includeGroups = VDS_CONFIG.includeGroups || [];
3520
3614
  const includeComponents = VDS_CONFIG.includeComponents || [];
3521
- if (g === "Uncategorized" || g === "UI" || g === "shadcn" || g === "ui") {
3522
- const groupIncluded = includeGroups.some((ig) => ig.toLowerCase() === g.toLowerCase());
3615
+ const gLower = g.toLowerCase();
3616
+ if (g === "Uncategorized" || gLower === "ui" || gLower === "shadcn") {
3617
+ const groupIncluded = includeGroups.some((ig) => ig.toLowerCase() === gLower);
3523
3618
  const compIncluded = includeComponents.includes(componentName);
3524
3619
  if (!groupIncluded && !compIncluded) {
3525
3620
  // Clean up leftover story files for components that are now in the skip group
@@ -3542,7 +3637,10 @@ function main() {
3542
3637
  const storyPath = path.join(STORIES_DIR, storyFileName);
3543
3638
  if (SKIP_LIST.includes(componentName)) continue;
3544
3639
  const requiredCount = Array.isArray(comp.props) ? comp.props.filter((p) => p.required === true).length : 0;
3545
- if (requiredCount > 3) continue;
3640
+ if (requiredCount > 3) {
3641
+ console.log(`[VDS] ${componentName} → skipped (${requiredCount} required props — too complex to auto-generate)`);
3642
+ continue;
3643
+ }
3546
3644
 
3547
3645
  // Never overwrite existing story files — only create new ones
3548
3646
  // To regenerate a story: delete the file and re-run VDS
@@ -3554,6 +3652,22 @@ function main() {
3554
3652
  if (content == null) continue;
3555
3653
  fs.writeFileSync(storyPath, content, "utf-8");
3556
3654
  console.log(`[VDS] Wrote ${path.relative(PROJECT_ROOT, storyPath)}`);
3655
+ writtenCount++;
3656
+ }
3657
+
3658
+ // Summary
3659
+ if (writtenCount === 0 && components.length > 0) {
3660
+ const hasShadcnGroup = components.some(c => {
3661
+ const gr = (c.group || "").toLowerCase();
3662
+ return gr === "shadcn" || gr === "ui";
3663
+ });
3664
+ console.log("[VDS] ⚠️ No new component stories were generated.");
3665
+ if (hasShadcnGroup && (VDS_CONFIG.includeGroups || []).length === 0) {
3666
+ console.log("[VDS] Tip: shadcn/ui components detected. Add to vds.config.js:");
3667
+ console.log('[VDS] includeGroups: ["shadcn", "UI"]');
3668
+ }
3669
+ } else if (writtenCount > 0) {
3670
+ console.log(`[VDS] ✓ Generated ${writtenCount} new component stories.`);
3557
3671
  }
3558
3672
  }
3559
3673