vibe-design-system 2.5.25 → 2.5.26

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
@@ -205,9 +205,13 @@ function ensureStorybook(projectRoot) {
205
205
  fs.writeFileSync(mainPath, STORYBOOK_MAIN_TS, "utf-8");
206
206
  console.log("📝 .storybook/main.ts yazıldı.");
207
207
 
208
- const previewPath = path.join(storybookDir, "preview.ts");
209
- fs.writeFileSync(previewPath, STORYBOOK_PREVIEW_TS, "utf-8");
210
- console.log("📝 .storybook/preview.ts yazıldı.");
208
+ const hasPreview = ["preview.tsx", "preview.ts", "preview.jsx", "preview.js"].some((name) =>
209
+ fs.existsSync(path.join(storybookDir, name))
210
+ );
211
+ if (!hasPreview) {
212
+ fs.writeFileSync(path.join(storybookDir, "preview.ts"), STORYBOOK_PREVIEW_TS, "utf-8");
213
+ console.log("📝 .storybook/preview.ts yazıldı.");
214
+ }
211
215
  }
212
216
 
213
217
  // ADIM 3 — vds-core/ kopyala
@@ -324,6 +328,19 @@ function runStoryGenerator(projectRoot) {
324
328
  if (r.status !== 0) process.exitCode = r.status ?? 1;
325
329
  }
326
330
 
331
+ // ADIM 7b — Provider detection & Storybook decorator setup
332
+ function runSetupStorybookProviders(projectRoot) {
333
+ const setupPath = path.join(projectRoot, "vds-core", "setup-storybook-providers.mjs");
334
+ if (!fs.existsSync(setupPath)) return;
335
+ console.log("🔌 Storybook provider decorators ayarlanıyor...");
336
+ const r = spawnSync("node", [setupPath], {
337
+ cwd: projectRoot,
338
+ stdio: "inherit",
339
+ shell: false,
340
+ });
341
+ if (r.status !== 0) process.exitCode = r.status ?? 1;
342
+ }
343
+
327
344
  // ADIM 9 — Storybook örnek dosyalarını sil
328
345
  function removeStorybookExamples(projectRoot) {
329
346
  const storiesDir = path.join(projectRoot, "src", "stories");
@@ -377,6 +394,7 @@ removeStorybookExamples(projectRoot);
377
394
  // ADIM 7
378
395
  runScan(projectRoot);
379
396
  runStoryGenerator(projectRoot);
397
+ runSetupStorybookProviders(projectRoot);
380
398
 
381
399
  // ADIM 8
382
400
  console.log("\n✅ VDS kuruldu!");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibe-design-system",
3
- "version": "2.5.25",
3
+ "version": "2.5.26",
4
4
  "description": "Auto-generate design systems for vibe coding projects",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,204 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * VDS — Storybook provider detection and decorator setup.
4
+ * Run from project root: node vds-core/setup-storybook-providers.mjs
5
+ *
6
+ * 1. Scans src/ for context hooks (useTimer, useCircles, useSidebar, useAuth).
7
+ * 2. Finds which Provider each hook needs; locates export in project.
8
+ * 3. Injects/merges provider decorators into .storybook/preview.ts or .storybook/preview.tsx.
9
+ * 4. Adds warning comment to story files for components that use a hook whose provider was not found.
10
+ */
11
+ import fs from "fs";
12
+ import path from "path";
13
+ import { fileURLToPath } from "url";
14
+
15
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
16
+ const PROJECT_ROOT = path.join(__dirname, "..");
17
+ const SRC_DIR = path.join(PROJECT_ROOT, "src");
18
+ const STORYBOOK_DIR = path.join(PROJECT_ROOT, ".storybook");
19
+
20
+ const HOOK_TO_PROVIDER = {
21
+ useTimer: "TimerProvider",
22
+ useCircles: "CirclesProvider",
23
+ useSidebar: "SidebarProvider",
24
+ useAuth: "AuthProvider",
25
+ };
26
+
27
+ const IGNORE_DIRS = new Set(["node_modules", "dist", ".next", "build", ".storybook", "stories"]);
28
+
29
+ function getAllSourceFiles(dir, baseDir = dir) {
30
+ if (!fs.existsSync(dir)) return [];
31
+ const out = [];
32
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
33
+ for (const e of entries) {
34
+ const full = path.join(dir, e.name);
35
+ const rel = path.relative(baseDir, full).replace(/\\/g, "/");
36
+ if (e.isDirectory()) {
37
+ if (IGNORE_DIRS.has(e.name)) continue;
38
+ out.push(...getAllSourceFiles(full, baseDir));
39
+ } else if (/\.(tsx?|jsx?)$/i.test(e.name)) {
40
+ out.push(rel);
41
+ }
42
+ }
43
+ return out;
44
+ }
45
+
46
+ function detectHooksInFile(content) {
47
+ const found = new Set();
48
+ for (const hook of Object.keys(HOOK_TO_PROVIDER)) {
49
+ if (new RegExp("\\b" + hook + "\\b").test(content)) found.add(hook);
50
+ }
51
+ return found;
52
+ }
53
+
54
+ /** Map: relative path (src/...) -> Set of hook names */
55
+ function detectHooksUsedInProject(projectRoot) {
56
+ const srcDir = path.join(projectRoot, "src");
57
+ const files = getAllSourceFiles(srcDir, srcDir).map((r) => path.join("src", r));
58
+ const byFile = new Map();
59
+ for (const rel of files) {
60
+ const full = path.join(projectRoot, rel);
61
+ try {
62
+ const content = fs.readFileSync(full, "utf-8");
63
+ const hooks = detectHooksInFile(content);
64
+ if (hooks.size) byFile.set(rel, hooks);
65
+ } catch (_) {}
66
+ }
67
+ return byFile;
68
+ }
69
+
70
+ /** Find file that exports providerName (e.g. TimerProvider). Returns import path from project root for @/ alias: "contexts/TimerContext" */
71
+ function findProviderExportPath(projectRoot, providerName) {
72
+ const srcDir = path.join(projectRoot, "src");
73
+ const files = getAllSourceFiles(srcDir, srcDir).map((r) => path.join("src", r));
74
+ const exportRe = new RegExp(
75
+ "export\\s+(?:default\\s+)?(?:const|function|class)\\s+" + providerName + "\\b|export\\s*\\{[^}]*\\b" + providerName + "\\b[^}]*\\}"
76
+ );
77
+ for (const rel of files) {
78
+ const full = path.join(projectRoot, rel);
79
+ try {
80
+ const content = fs.readFileSync(full, "utf-8");
81
+ if (exportRe.test(content)) {
82
+ const withoutExt = rel.replace(/\.(tsx?|jsx?)$/i, "").replace(/^src\/?/, "");
83
+ return "@/" + withoutExt;
84
+ }
85
+ } catch (_) {}
86
+ }
87
+ return null;
88
+ }
89
+
90
+ function getPreviewPath(projectRoot) {
91
+ const pTsx = path.join(projectRoot, ".storybook", "preview.tsx");
92
+ const pTs = path.join(projectRoot, ".storybook", "preview.ts");
93
+ const pJsx = path.join(projectRoot, ".storybook", "preview.jsx");
94
+ const pJs = path.join(projectRoot, ".storybook", "preview.js");
95
+ if (fs.existsSync(pTsx)) return pTsx;
96
+ if (fs.existsSync(pTs)) return pTs;
97
+ if (fs.existsSync(pJsx)) return pJsx;
98
+ if (fs.existsSync(pJs)) return pJs;
99
+ return null;
100
+ }
101
+
102
+ /** Collect all hooks used in project; return { providersToAdd: [{ name, importPath }], hooksWithoutProvider: Set, hooksByFile: Map } */
103
+ function collectProvidersAndWarnings(projectRoot) {
104
+ const hooksByFile = detectHooksUsedInProject(projectRoot);
105
+ const allHooksUsed = new Set();
106
+ for (const hooks of hooksByFile.values()) {
107
+ for (const h of hooks) allHooksUsed.add(h);
108
+ }
109
+ const providersToAdd = [];
110
+ const hooksWithoutProvider = new Set();
111
+ for (const hook of allHooksUsed) {
112
+ const providerName = HOOK_TO_PROVIDER[hook];
113
+ const importPath = findProviderExportPath(projectRoot, providerName);
114
+ if (importPath) {
115
+ if (!providersToAdd.some((p) => p.name === providerName)) {
116
+ providersToAdd.push({ name: providerName, importPath });
117
+ }
118
+ } else {
119
+ hooksWithoutProvider.add(hook);
120
+ }
121
+ }
122
+ return { providersToAdd, hooksWithoutProvider, hooksByFile };
123
+ }
124
+
125
+ /** Build withProviders decorator using React.createElement (no JSX so preview can stay .ts) */
126
+ function buildWithProvidersCode(providers) {
127
+ if (providers.length === 0) return null;
128
+ let inner = "React.createElement(Story)";
129
+ for (let i = providers.length - 1; i >= 0; i--) {
130
+ inner = `React.createElement(${providers[i].name}, null, ${inner})`;
131
+ }
132
+ return `const withProviders = (Story: any) => ${inner};`;
133
+ }
134
+
135
+ function injectProviderDecorators(projectRoot) {
136
+ const previewPath = getPreviewPath(projectRoot);
137
+ if (!previewPath) {
138
+ console.log("[VDS] .storybook/preview not found; skip provider setup.");
139
+ return;
140
+ }
141
+
142
+ const { providersToAdd, hooksWithoutProvider, hooksByFile } = collectProvidersAndWarnings(projectRoot);
143
+ if (providersToAdd.length === 0 && hooksWithoutProvider.size === 0) return;
144
+
145
+ if (providersToAdd.length > 0) {
146
+ let content = fs.readFileSync(previewPath, "utf-8");
147
+ const alreadyHas = providersToAdd.every((p) => content.includes(p.name));
148
+ if (!alreadyHas) {
149
+ const importLines = [...new Map(providersToAdd.map((p) => [p.name, p])).values()]
150
+ .map((p) => `import { ${p.name} } from "${p.importPath}";`)
151
+ .join("\n");
152
+ const lastImportIdx = content.search(/\nimport\s+.+?;\s*$/m);
153
+ const insertAt = lastImportIdx >= 0 ? content.indexOf("\n", lastImportIdx) + 1 : content.indexOf("\n") + 1;
154
+ content = content.slice(0, insertAt) + importLines + "\n" + content.slice(insertAt);
155
+ }
156
+ if (!content.includes("withProviders")) {
157
+ if (!content.includes("import React")) {
158
+ const firstImport = content.match(/^import\s+/m);
159
+ const insertAt = firstImport ? content.indexOf(firstImport[0]) : 0;
160
+ content = content.slice(0, insertAt) + "import React from \"react\";\n" + content.slice(insertAt);
161
+ }
162
+ const withProvidersCode = buildWithProvidersCode(providersToAdd);
163
+ content = content.replace(/(\s*)(export default preview;?)/, withProvidersCode + "\n\n$1$2");
164
+ if (content.includes("decorators:")) {
165
+ content = content.replace(/decorators:\s*\[/, "decorators: [withProviders, ");
166
+ } else {
167
+ content = content.replace(/(const preview\s*[^=]*=\s*\{\s*)/, "$1\n decorators: [withProviders],\n ");
168
+ }
169
+ }
170
+ fs.writeFileSync(previewPath, content, "utf-8");
171
+ console.log("[VDS] Storybook preview: added " + providersToAdd.map((p) => p.name).join(", "));
172
+ }
173
+
174
+ if (hooksWithoutProvider.size > 0) {
175
+ const componentNameFromPath = (rel) => path.basename(rel, path.extname(rel));
176
+ const storiesDir = path.join(projectRoot, "src", "stories");
177
+ const storyRels = fs.existsSync(storiesDir) ? getAllSourceFiles(storiesDir, storiesDir) : [];
178
+ const storyByComponent = new Map();
179
+ for (const r of storyRels) {
180
+ if (!r.includes(".stories.")) continue;
181
+ const name = path.basename(r).replace(/\.stories\.(tsx?|jsx?)$/i, "");
182
+ storyByComponent.set(name, path.join(storiesDir, r));
183
+ }
184
+ for (const [fileRel, hooks] of hooksByFile) {
185
+ const missing = [...hooks].filter((h) => hooksWithoutProvider.has(h));
186
+ if (missing.length === 0) continue;
187
+ const compName = componentNameFromPath(fileRel);
188
+ const storyPath = storyByComponent.get(compName);
189
+ if (!storyPath || !fs.existsSync(storyPath)) continue;
190
+ let storyContent = fs.readFileSync(storyPath, "utf-8");
191
+ const comment = "// ⚠️ This component uses " + missing.join(", ") + " but provider not found in project\n";
192
+ if (storyContent.startsWith("// ⚠️")) continue;
193
+ storyContent = comment + storyContent;
194
+ fs.writeFileSync(storyPath, storyContent, "utf-8");
195
+ }
196
+ }
197
+ }
198
+
199
+ const isMain = process.argv[1] && path.resolve(process.argv[1]) === path.resolve(fileURLToPath(import.meta.url));
200
+ if (isMain) {
201
+ injectProviderDecorators(PROJECT_ROOT);
202
+ }
203
+
204
+ export { collectProvidersAndWarnings, injectProviderDecorators, getPreviewPath };