vibe-design-system 1.8.5 → 1.9.3

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
@@ -1,10 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
3
  * VDS installer: npx vibe-design-system init
4
- * - Kullanıcı projesine dashboard dosyası kopyalanmaz.
5
- * - Sadece vds-core/scan.mjs ve vds-core/dashboard-server.mjs projeye kopyalanır.
6
- * - package.json'a vds, vds:watch, vds:dashboard scriptleri eklenir.
7
- * - Kurulum sonrası otomatik tarama çalıştırılır.
4
+ *
5
+ * Single command that:
6
+ * 1. Copies vds-core files (scan.mjs, story-generator.mjs, dashboard-server.mjs)
7
+ * 2. Installs Storybook if not present (npx storybook@latest init --yes)
8
+ * 3. Creates/updates .storybook/preview with index.css import and dark decorator
9
+ * 4. Runs node vds-core/scan.mjs
10
+ * 5. Runs node vds-core/story-generator.mjs
11
+ * 6. Adds all scripts to package.json
8
12
  */
9
13
  import fs from "fs";
10
14
  import path from "path";
@@ -24,6 +28,66 @@ function getProjectRoot() {
24
28
  return cwd;
25
29
  }
26
30
 
31
+ function isStorybookInstalled(projectRoot) {
32
+ const storybookDir = path.join(projectRoot, ".storybook");
33
+ if (fs.existsSync(storybookDir)) return true;
34
+ const pkgPath = path.join(projectRoot, "package.json");
35
+ try {
36
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
37
+ const dev = pkg.devDependencies || {};
38
+ if (dev["@storybook/react-vite"] || dev["storybook"]) return true;
39
+ } catch (_) {}
40
+ return false;
41
+ }
42
+
43
+ function installStorybook(projectRoot) {
44
+ console.log("📚 Storybook kuruluyor...");
45
+ const r = spawnSync("npx", ["storybook@latest", "init", "--yes"], {
46
+ cwd: projectRoot,
47
+ stdio: "inherit",
48
+ shell: true,
49
+ });
50
+ if (r.status !== 0) {
51
+ console.warn("⚠️ Storybook init tamamlanamadı; manuel: npx storybook@latest init");
52
+ }
53
+ }
54
+
55
+ const PREVIEW_CONTENT = `import type { Preview } from "@storybook/react-vite";
56
+ import React from "react";
57
+ import "../src/index.css";
58
+
59
+ const preview: Preview = {
60
+ parameters: {
61
+ controls: {
62
+ matchers: {
63
+ color: /(background|color)$/i,
64
+ date: /Date$/i,
65
+ },
66
+ },
67
+ },
68
+ decorators: [
69
+ (Story) => (
70
+ <div className="dark" style={{ minHeight: "100vh", padding: "1rem" }}>
71
+ <Story />
72
+ </div>
73
+ ),
74
+ ],
75
+ };
76
+
77
+ export default preview;
78
+ `;
79
+
80
+ function ensureStorybookPreview(projectRoot) {
81
+ const storybookDir = path.join(projectRoot, ".storybook");
82
+ if (!fs.existsSync(storybookDir)) fs.mkdirSync(storybookDir, { recursive: true });
83
+
84
+ const previewTsx = path.join(storybookDir, "preview.tsx");
85
+ const previewTs = path.join(storybookDir, "preview.ts");
86
+ if (fs.existsSync(previewTs)) try { fs.unlinkSync(previewTs); } catch (_) {}
87
+ fs.writeFileSync(previewTsx, PREVIEW_CONTENT, "utf-8");
88
+ console.log("📝 .storybook/preview.tsx güncellendi (index.css + dark decorator).");
89
+ }
90
+
27
91
  function addScripts(projectRoot) {
28
92
  const pkgPath = path.join(projectRoot, "package.json");
29
93
  const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
@@ -32,6 +96,8 @@ function addScripts(projectRoot) {
32
96
  if (!scripts["vds:watch"]) scripts["vds:watch"] = "node vds-core/scan.mjs --watch";
33
97
  if (!scripts["vds:dashboard"]) scripts["vds:dashboard"] = "node vds-core/dashboard-server.mjs";
34
98
  if (!scripts["vds:stories"]) scripts["vds:stories"] = "node vds-core/story-generator.mjs";
99
+ if (!scripts.storybook) scripts.storybook = "storybook dev -p 6006";
100
+ if (!scripts["build-storybook"]) scripts["build-storybook"] = "storybook build";
35
101
  pkg.scripts = scripts;
36
102
  fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2), "utf-8");
37
103
  }
@@ -41,7 +107,7 @@ function addVdsDependency(projectRoot) {
41
107
  const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
42
108
  const dev = pkg.devDependencies || {};
43
109
  if (!dev["vibe-design-system"]) {
44
- dev["vibe-design-system"] = "^1.4.1";
110
+ dev["vibe-design-system"] = "^1.9.0";
45
111
  pkg.devDependencies = dev;
46
112
  fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2), "utf-8");
47
113
  const r = spawnSync("npm", ["install"], { cwd: projectRoot, stdio: "inherit", shell: true });
@@ -64,9 +130,6 @@ function ensureVdsCore(projectRoot) {
64
130
  const serverSrc = path.join(TEMPLATE_DIR, "dashboard-server.mjs");
65
131
  if (fs.existsSync(serverSrc)) {
66
132
  fs.copyFileSync(serverSrc, path.join(vdsCoreDest, "dashboard-server.mjs"));
67
- } else {
68
- console.error("vds-core-template/dashboard-server.mjs bulunamadı.");
69
- process.exit(1);
70
133
  }
71
134
 
72
135
  const storyGenSrc = path.join(TEMPLATE_DIR, "story-generator.mjs");
@@ -78,6 +141,7 @@ function ensureVdsCore(projectRoot) {
78
141
  function runScan(projectRoot) {
79
142
  const scanPath = path.join(projectRoot, "vds-core", "scan.mjs");
80
143
  if (!fs.existsSync(scanPath)) return;
144
+ console.log("🔍 VDS taraması çalıştırılıyor...");
81
145
  const r = spawnSync("node", [scanPath], {
82
146
  cwd: projectRoot,
83
147
  stdio: "inherit",
@@ -86,6 +150,18 @@ function runScan(projectRoot) {
86
150
  if (r.status !== 0) process.exitCode = r.status ?? 1;
87
151
  }
88
152
 
153
+ function runStoryGenerator(projectRoot) {
154
+ const genPath = path.join(projectRoot, "vds-core", "story-generator.mjs");
155
+ if (!fs.existsSync(genPath)) return;
156
+ console.log("📖 Story dosyaları oluşturuluyor...");
157
+ const r = spawnSync("node", [genPath], {
158
+ cwd: projectRoot,
159
+ stdio: "inherit",
160
+ shell: false,
161
+ });
162
+ if (r.status !== 0) process.exitCode = r.status ?? 1;
163
+ }
164
+
89
165
  console.log("🎨 VDS kuruluyor...");
90
166
 
91
167
  const projectRoot = getProjectRoot();
@@ -96,9 +172,29 @@ if (!fs.existsSync(pkgPath)) {
96
172
  process.exit(1);
97
173
  }
98
174
 
175
+ // 1. Copy vds-core files
99
176
  ensureVdsCore(projectRoot);
177
+
178
+ // 2. Install Storybook if not present
179
+ if (!isStorybookInstalled(projectRoot)) {
180
+ installStorybook(projectRoot);
181
+ } else {
182
+ console.log("📚 Storybook zaten mevcut.");
183
+ }
184
+
185
+ // 3. Create/update .storybook/preview with index.css + dark decorator
186
+ ensureStorybookPreview(projectRoot);
187
+
188
+ // 6. Add all scripts to package.json (before scan so scripts are there)
100
189
  addScripts(projectRoot);
101
190
  addVdsDependency(projectRoot);
191
+
192
+ // 4. Run scan
102
193
  runScan(projectRoot);
103
194
 
104
- console.log("✅ Kurulum tamamlandı. Dashboard: npm run vds:dashboard ile başlatın; terminalde adres gösterilir.");
195
+ // 5. Run story generator
196
+ runStoryGenerator(projectRoot);
197
+
198
+ console.log("✅ Kurulum tamamlandı.");
199
+ console.log(" Storybook: npm run storybook");
200
+ console.log(" Dashboard: npm run vds:dashboard");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibe-design-system",
3
- "version": "1.8.5",
3
+ "version": "1.9.3",
4
4
  "description": "Auto-generate design systems for vibe coding projects",
5
5
  "type": "module",
6
6
  "bin": {
@@ -2,8 +2,9 @@
2
2
  /**
3
3
  * VDS Story generator
4
4
  *
5
- * Reads vds-output.json and generates Storybook .stories.tsx files
6
- * under src/stories for each component.
5
+ * Reads vds-output.json and generates:
6
+ * - Storybook .stories.tsx files under src/stories for each component
7
+ * - Foundations .stories.tsx: Colors, Typography, Brand under src/stories/foundations/
7
8
  *
8
9
  * Usage:
9
10
  * node vds-core/story-generator.mjs # generate for all components
@@ -19,6 +20,12 @@ const VDS_OUTPUT = path.join(PROJECT_ROOT, "vds-output.json");
19
20
  const SRC_DIR = path.join(PROJECT_ROOT, "src");
20
21
  const STORIES_DIR = path.join(SRC_DIR, "stories");
21
22
 
23
+ // Top of every story file for CSS variables
24
+ const STORY_CSS_HEADER = `// @ts-ignore
25
+ import '../../index.css'
26
+
27
+ `;
28
+
22
29
  // Components we don't want to auto-generate stories for (project-specific dashboards, heavy UIs, etc.)
23
30
  const SKIP_LIST = [
24
31
  "AnalysisDashboard",
@@ -37,28 +44,41 @@ const SKIP_LIST = [
37
44
  "TestComponent",
38
45
  ];
39
46
 
40
- // Components that need a router context
41
- const ROUTER_DECORATOR_LIST = ["Breadcrumb", "NavigationMenu", "Sidebar", "Menubar"];
42
-
43
47
  function ensureDir(dir) {
44
48
  if (!fs.existsSync(dir)) {
45
49
  fs.mkdirSync(dir, { recursive: true });
46
50
  }
47
51
  }
48
52
 
53
+ /** Detect if component uses router (useLocation, useNavigate, or Link from react-router-dom). */
54
+ function needsRouter(source) {
55
+ if (!source || typeof source !== "string") return false;
56
+ if (/\buseLocation\b|\buseNavigate\b/.test(source)) return true;
57
+ if (/from\s+['"]react-router-dom['"]/.test(source) && /\bLink\b/.test(source)) return true;
58
+ return false;
59
+ }
60
+
61
+ /** If component has <img and accepts children prop, we should omit children from story args (void element). */
62
+ function componentHasImgAndChildren(source) {
63
+ if (!source || typeof source !== "string") return false;
64
+ const hasImg = /return\s+<img|<\s*img\s+/.test(source);
65
+ if (!hasImg) return false;
66
+ return /\bchildren\b/.test(source);
67
+ }
68
+
49
69
  function toSafeComponentName(name, file) {
50
- if (name && typeof name === "string") {
51
- return name
52
- .replace(/[^A-Za-z0-9]+/g, " ")
53
- .trim()
54
- .replace(/\s+([a-z])/g, (_, c) => c.toUpperCase())
55
- .replace(/^\w/, (c) => c.toUpperCase())
56
- .replace(/\s+/g, "");
57
- }
58
- const base = (file || "").replace(/\.[^.]+$/, "");
59
- const parts = base.split(/[\\/]/g);
60
- const last = parts[parts.length - 1] || "Component";
61
- return last.charAt(0).toUpperCase() + last.slice(1);
70
+ if (!name || typeof name !== "string") {
71
+ const base = (file || "").replace(/\.[^.]+$/, "");
72
+ const parts = base.split(/[\\/]/g);
73
+ const last = parts[parts.length - 1] || "Component";
74
+ return last.charAt(0).toUpperCase() + last.slice(1);
75
+ }
76
+ return name
77
+ .replace(/[^A-Za-z0-9]+/g, " ")
78
+ .trim()
79
+ .replace(/\s+([a-z])/g, (_, c) => c.toUpperCase())
80
+ .replace(/^\w/, (c) => c.toUpperCase())
81
+ .replace(/\s+/g, "");
62
82
  }
63
83
 
64
84
  function parseUnionLiterals(type) {
@@ -308,6 +328,7 @@ function buildStoryFileContent(comp) {
308
328
  // ignore
309
329
  }
310
330
  const exportStyle = detectExportStyle(source, componentName);
331
+ const omitChildren = componentHasImgAndChildren(source);
311
332
 
312
333
  const lines = [];
313
334
  lines.push(`import type { Meta, StoryObj } from "@storybook/react";`);
@@ -347,7 +368,8 @@ function buildStoryFileContent(comp) {
347
368
  `import { CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from "@/components/ui/card";`,
348
369
  );
349
370
  }
350
- if (ROUTER_DECORATOR_LIST.includes(componentName)) {
371
+ const useRouterDecorator = needsRouter(source);
372
+ if (useRouterDecorator) {
351
373
  lines.push(`import { MemoryRouter } from "react-router-dom";`);
352
374
  }
353
375
 
@@ -356,7 +378,7 @@ function buildStoryFileContent(comp) {
356
378
  lines.push(` title: ${JSON.stringify(title)},`);
357
379
  lines.push(` component: ComponentRef,`);
358
380
  lines.push(` tags: ["autodocs"],`);
359
- if (ROUTER_DECORATOR_LIST.includes(componentName)) {
381
+ if (useRouterDecorator) {
360
382
  lines.push(` decorators: [(Story) => (`);
361
383
  lines.push(` <MemoryRouter>`);
362
384
  lines.push(` <Story />`);
@@ -373,14 +395,14 @@ function buildStoryFileContent(comp) {
373
395
  const specialStories = buildSpecialStories(componentName, variants);
374
396
  if (specialStories) {
375
397
  lines.push(specialStories);
376
- return lines.join("\n");
398
+ return STORY_CSS_HEADER + lines.join("\n");
377
399
  }
378
400
 
379
- // Generic variant-based stories
401
+ // Generic variant-based stories (omit children for img/void components)
380
402
  if (!variants.length) {
381
403
  lines.push(`export const Default: Story = {`);
382
404
  lines.push(` args: {`);
383
- lines.push(` children: "${componentName}",`);
405
+ if (!omitChildren) lines.push(` children: "${componentName}",`);
384
406
  lines.push(` },`);
385
407
  lines.push(`};`);
386
408
  } else {
@@ -388,7 +410,7 @@ function buildStoryFileContent(comp) {
388
410
  lines.push(`export const ${capitalize(defaultVariant)}: Story = {`);
389
411
  lines.push(` args: {`);
390
412
  lines.push(` variant: "${defaultVariant}",`);
391
- lines.push(` children: "${componentName}",`);
413
+ if (!omitChildren) lines.push(` children: "${componentName}",`);
392
414
  lines.push(` },`);
393
415
  lines.push(`};`);
394
416
  lines.push("");
@@ -397,14 +419,130 @@ function buildStoryFileContent(comp) {
397
419
  lines.push(`export const ${storyName}: Story = {`);
398
420
  lines.push(` args: {`);
399
421
  lines.push(` variant: "${v}",`);
400
- lines.push(` children: "${storyName}",`);
422
+ if (!omitChildren) lines.push(` children: "${storyName}",`);
401
423
  lines.push(` },`);
402
424
  lines.push(`};`);
403
425
  lines.push("");
404
426
  }
405
427
  }
406
428
 
407
- return lines.join("\n");
429
+ return STORY_CSS_HEADER + lines.join("\n");
430
+ }
431
+
432
+ /** Build color entries from foundations.colors (skip _dark; flatten to { name, hex }). */
433
+ function getColorEntries(colors) {
434
+ if (!colors || typeof colors !== "object") return [];
435
+ const entries = [];
436
+ for (const [name, v] of Object.entries(colors)) {
437
+ if (name === "_dark") continue;
438
+ const hex = v && (v.hex ?? (typeof v.value === "string" && v.value.startsWith("#") ? v.value : null));
439
+ const value = v && v.value;
440
+ if (hex) entries.push({ name, hex });
441
+ else if (value) entries.push({ name, hex: value });
442
+ }
443
+ return entries;
444
+ }
445
+
446
+ function writeFoundationsStories(foundations) {
447
+ const foundationsDir = path.join(STORIES_DIR, "foundations");
448
+ ensureDir(foundationsDir);
449
+
450
+ const colorEntries = getColorEntries(foundations?.colors);
451
+ const colorsContent =
452
+ STORY_CSS_HEADER +
453
+ [
454
+ "import type { Meta, StoryObj } from \"@storybook/react\";",
455
+ "",
456
+ "const meta = { title: \"Foundations/Colors\" } satisfies Meta;",
457
+ "export default meta;",
458
+ "type Story = StoryObj;",
459
+ "",
460
+ `const colors = ${JSON.stringify(colorEntries)};`,
461
+ "",
462
+ "export const Default: Story = {",
463
+ " render: () => (",
464
+ " <div style={{ display: \"grid\", gridTemplateColumns: \"repeat(auto-fill, minmax(140px, 1fr))\", gap: \"1rem\" }}>",
465
+ " {colors.map(({ name, hex }) => (",
466
+ " <div key={name}>",
467
+ " <div style={{ backgroundColor: hex, height: 80, borderRadius: 8, border: \"1px solid #333\" }} />",
468
+ " <p style={{ marginTop: 8, fontSize: 12 }}>{name}</p>",
469
+ " <code style={{ fontSize: 11 }}>{hex}</code>",
470
+ " </div>",
471
+ " ))}",
472
+ " </div>",
473
+ " ),",
474
+ "};",
475
+ ].join("\n");
476
+ fs.writeFileSync(path.join(foundationsDir, "Colors.stories.tsx"), colorsContent, "utf-8");
477
+ console.log("[VDS] Wrote " + path.relative(PROJECT_ROOT, path.join(foundationsDir, "Colors.stories.tsx")));
478
+
479
+ const typo = foundations?.typography;
480
+ if (typo && typeof typo === "object" && Object.keys(typo).length > 0) {
481
+ const typoRows = Object.entries(typo).map(([k, v]) => ({
482
+ token: k,
483
+ value: Array.isArray(v) ? v.join(", ") : String(v),
484
+ }));
485
+ const typoContent =
486
+ STORY_CSS_HEADER +
487
+ [
488
+ "import type { Meta, StoryObj } from \"@storybook/react\";",
489
+ "",
490
+ "const meta = { title: \"Foundations/Typography\" } satisfies Meta;",
491
+ "export default meta;",
492
+ "type Story = StoryObj;",
493
+ "",
494
+ `const typography = ${JSON.stringify(typoRows)};`,
495
+ "",
496
+ "export const Default: Story = {",
497
+ " render: () => (",
498
+ " <div>",
499
+ " <h2 style={{ marginBottom: 16 }}>Font family & scale</h2>",
500
+ " <table style={{ borderCollapse: \"collapse\", width: \"100%\" }}>",
501
+ " <thead><tr><th style={{ textAlign: \"left\", padding: 8, borderBottom: \"1px solid #333\" }}>Token</th><th style={{ textAlign: \"left\", padding: 8, borderBottom: \"1px solid #333\" }}>Value</th></tr></thead>",
502
+ " <tbody>",
503
+ " {typography.map(({ token, value }) => (",
504
+ " <tr key={token}><td style={{ padding: 8, borderBottom: \"1px solid #222\" }}>{token}</td><td style={{ padding: 8, borderBottom: \"1px solid #222\" }}>{value}</td></tr>",
505
+ " ))}",
506
+ " </tbody>",
507
+ " </table>",
508
+ " </div>",
509
+ " ),",
510
+ "};",
511
+ ].join("\n");
512
+ fs.writeFileSync(path.join(foundationsDir, "Typography.stories.tsx"), typoContent, "utf-8");
513
+ console.log("[VDS] Wrote " + path.relative(PROJECT_ROOT, path.join(foundationsDir, "Typography.stories.tsx")));
514
+ }
515
+
516
+ const brandAssets = foundations?.brand?.assets;
517
+ const assets = Array.isArray(brandAssets) ? brandAssets : [];
518
+ const brandContent =
519
+ STORY_CSS_HEADER +
520
+ [
521
+ "import type { Meta, StoryObj } from \"@storybook/react\";",
522
+ "",
523
+ "const meta = { title: \"Foundations/Brand\" } satisfies Meta;",
524
+ "export default meta;",
525
+ "type Story = StoryObj;",
526
+ "",
527
+ `const assets = ${JSON.stringify(assets)};`,
528
+ "",
529
+ "export const Default: Story = {",
530
+ " render: () => (",
531
+ " <div>",
532
+ " <h2 style={{ marginBottom: 16 }}>Logo and favicon paths</h2>",
533
+ " <ul style={{ listStyle: \"none\", padding: 0 }}>",
534
+ " {assets.length === 0 ? <li>No brand assets found.</li> : assets.map((a, i) => (",
535
+ " <li key={i} style={{ padding: \"8px 0\", borderBottom: \"1px solid #222\" }}>",
536
+ " <strong>{a.type || \"asset\"}</strong>: <code>{a.path || a.name || \"\"}</code>",
537
+ " </li>",
538
+ " ))}",
539
+ " </ul>",
540
+ " </div>",
541
+ " ),",
542
+ "};",
543
+ ].join("\n");
544
+ fs.writeFileSync(path.join(foundationsDir, "Brand.stories.tsx"), brandContent, "utf-8");
545
+ console.log("[VDS] Wrote " + path.relative(PROJECT_ROOT, path.join(foundationsDir, "Brand.stories.tsx")));
408
546
  }
409
547
 
410
548
  function main() {
@@ -415,14 +553,29 @@ function main() {
415
553
  const raw = fs.readFileSync(VDS_OUTPUT, "utf-8");
416
554
  const data = JSON.parse(raw);
417
555
  const components = Array.isArray(data.components) ? data.components : [];
556
+ const foundations = data.foundations || null;
418
557
 
419
558
  const onlyName = process.argv[2] || null;
420
559
 
421
560
  ensureDir(STORIES_DIR);
422
- // Clear existing .stories.* files so only VDS-generated stories remain
561
+ ensureDir(path.join(STORIES_DIR, "foundations"));
562
+ writeFoundationsStories(foundations);
563
+ try {
564
+ const fd = path.join(STORIES_DIR, "foundations");
565
+ if (fs.existsSync(fd)) {
566
+ for (const name of fs.readdirSync(fd)) {
567
+ if (name.endsWith(".stories.mdx")) fs.unlinkSync(path.join(fd, name));
568
+ }
569
+ }
570
+ } catch {
571
+ // ignore
572
+ }
573
+
574
+ // Clear existing .stories.* files (except foundations/*.mdx) so only VDS-generated stories remain
423
575
  try {
424
576
  const existing = fs.readdirSync(STORIES_DIR);
425
577
  for (const name of existing) {
578
+ if (name === "foundations") continue;
426
579
  if (
427
580
  name.endsWith(".stories.tsx") ||
428
581
  name.endsWith(".stories.ts") ||
@@ -450,4 +603,3 @@ function main() {
450
603
  }
451
604
 
452
605
  main();
453
-