vibe-design-system 2.8.56 → 2.8.59
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 +20 -10
- package/package.json +1 -1
- package/vds-core-template/story-generator.mjs +125 -7
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, "..");
|
|
@@ -278,8 +278,9 @@ export default config;
|
|
|
278
278
|
// vite (default) & remix
|
|
279
279
|
// For fullstack projects (client/src, frontend/src, etc.), add staticDirs for public assets
|
|
280
280
|
const frontendDir = srcPrefix.split("/")[0]; // e.g. "client" from "client/src"
|
|
281
|
+
// Map public/ to "/" so components can load /images/... /figmaAssets/... etc. without prefix
|
|
281
282
|
const staticDirsLine = srcPrefix !== "src"
|
|
282
|
-
? `\n staticDirs: [
|
|
283
|
+
? `\n staticDirs: ["../${frontendDir}/public"],`
|
|
283
284
|
: "";
|
|
284
285
|
|
|
285
286
|
return `import type { StorybookConfig } from "@storybook/react-vite";
|
|
@@ -610,12 +611,11 @@ function ensureVdsConfig(projectRoot, srcPrefix) {
|
|
|
610
611
|
const configPath = path.join(projectRoot, "vds.config.js");
|
|
611
612
|
if (fs.existsSync(configPath)) return;
|
|
612
613
|
|
|
613
|
-
//
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
: ` // includeGroups: ["shadcn", "UI"], // Uncomment to generate stories for shadcn/ui primitives\n`;
|
|
614
|
+
// includeGroups is intentionally OFF by default.
|
|
615
|
+
// shadcn/ui base components (Button, Avatar, Badge…) are generic library primitives —
|
|
616
|
+
// most projects only want their custom sections and page components in Storybook.
|
|
617
|
+
// To include shadcn/ui components: add includeGroups: ["shadcn", "UI", "ui"] to vds.config.js.
|
|
618
|
+
const includeGroupsLine = ` // includeGroups: ["shadcn", "UI"], // Uncomment to add shadcn/ui primitive stories\n`;
|
|
619
619
|
|
|
620
620
|
const content = `/**
|
|
621
621
|
* VDS Configuration — auto-created by VDS installer
|
|
@@ -715,8 +715,18 @@ runStorybookAdapt(projectRoot);
|
|
|
715
715
|
|
|
716
716
|
// ADIM 8
|
|
717
717
|
console.log("\n✅ VDS kuruldu!");
|
|
718
|
-
console.log("→ npm run storybook — design system'ı aç (localhost:6006)");
|
|
719
|
-
console.log("→ npm run storybook:bg — Storybook'u arka planda başlat (Cursor kapansa bile çalışır)");
|
|
720
718
|
console.log("→ npm run vds:stories — tarama + story + provider (tek komut)");
|
|
721
719
|
console.log("→ npm run vds:watch — otomatik güncelleme (watch modu)");
|
|
722
720
|
console.log("\nNot: Storybook başlarken (Node 24+) DEP0190 uyarısı çıkarsa Storybook kaynaklıdır, güvenle yok sayabilirsiniz.\n");
|
|
721
|
+
|
|
722
|
+
// ADIM 10 — Storybook'u otomatik başlat
|
|
723
|
+
console.log("🚀 Storybook başlatılıyor → http://localhost:6006\n");
|
|
724
|
+
const sbProc = spawn("npm", ["run", "storybook"], {
|
|
725
|
+
stdio: "inherit",
|
|
726
|
+
cwd: projectRoot,
|
|
727
|
+
shell: process.platform === "win32",
|
|
728
|
+
});
|
|
729
|
+
sbProc.on("error", (err) => {
|
|
730
|
+
console.error("[VDS] Storybook başlatılamadı:", err.message);
|
|
731
|
+
console.log("→ Manuel başlatmak için: npm run storybook");
|
|
732
|
+
});
|
package/package.json
CHANGED
|
@@ -246,10 +246,9 @@ const RECIPES = {
|
|
|
246
246
|
)`,
|
|
247
247
|
},
|
|
248
248
|
Avatar: {
|
|
249
|
-
imports: ["
|
|
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
|
)`,
|
|
@@ -881,6 +880,58 @@ const COMPONENT_EXTRA_ARGS = {
|
|
|
881
880
|
...(VDS_CONFIG.componentExtraArgs ?? {}),
|
|
882
881
|
};
|
|
883
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
|
+
|
|
884
935
|
/** Render'da args'a uygulanacak fallback (useState(estimate.toString()) gibi kullanımlar için; args/Controls undefined yapsa bile). */
|
|
885
936
|
const RENDER_ARGS_FALLBACKS = {
|
|
886
937
|
TaskEstimateInput: ", estimate: (args && args.estimate) ?? 0, value: (args && args.value) ?? 0, task: (args && args.task) ?? { id: \"1\", title: \"Example\", estimate: 0 }",
|
|
@@ -1867,6 +1918,28 @@ function buildStoryFileContent(comp) {
|
|
|
1867
1918
|
lines.push(`};`);
|
|
1868
1919
|
}
|
|
1869
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
|
+
|
|
1870
1943
|
return lines.join("\n");
|
|
1871
1944
|
}
|
|
1872
1945
|
|
|
@@ -2292,8 +2365,47 @@ function writeFoundationsStories(foundations) {
|
|
|
2292
2365
|
console.log("[VDS] Wrote " + path.relative(PROJECT_ROOT, path.join(foundationsDir, "Typography.stories.tsx")));
|
|
2293
2366
|
}
|
|
2294
2367
|
|
|
2368
|
+
// Collect brand assets: prefer scan data, fallback to scanning public/ directories
|
|
2295
2369
|
const brandAssets = foundations?.brand?.assets;
|
|
2296
|
-
|
|
2370
|
+
let assets = Array.isArray(brandAssets) ? brandAssets : [];
|
|
2371
|
+
|
|
2372
|
+
// Also scan public/ dirs directly — scan.mjs may miss most project image assets
|
|
2373
|
+
{
|
|
2374
|
+
const publicDirs = [
|
|
2375
|
+
path.join(PROJECT_ROOT, "public"),
|
|
2376
|
+
path.join(PROJECT_ROOT, "client", "public"),
|
|
2377
|
+
path.join(PROJECT_ROOT, "frontend", "public"),
|
|
2378
|
+
path.join(PROJECT_ROOT, "src", "assets"),
|
|
2379
|
+
path.join(PROJECT_ROOT, "client", "src", "assets"),
|
|
2380
|
+
];
|
|
2381
|
+
const IMAGE_EXT = /\.(svg|png|jpg|jpeg|webp|gif)$/i;
|
|
2382
|
+
const existingUrls = new Set(assets.map((a) => a.url || a.path || ""));
|
|
2383
|
+
|
|
2384
|
+
for (const pubDir of publicDirs) {
|
|
2385
|
+
if (!fs.existsSync(pubDir)) continue;
|
|
2386
|
+
const newAssets = [];
|
|
2387
|
+
const scanDir = (dir, baseUrl) => {
|
|
2388
|
+
let entries;
|
|
2389
|
+
try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
|
|
2390
|
+
for (const e of entries) {
|
|
2391
|
+
const urlPath = baseUrl + "/" + e.name;
|
|
2392
|
+
if (e.isDirectory()) {
|
|
2393
|
+
scanDir(path.join(dir, e.name), urlPath);
|
|
2394
|
+
} else if (IMAGE_EXT.test(e.name) && !existingUrls.has(urlPath)) {
|
|
2395
|
+
newAssets.push({ name: e.name.replace(/\.[^.]+$/, ""), url: urlPath });
|
|
2396
|
+
}
|
|
2397
|
+
}
|
|
2398
|
+
};
|
|
2399
|
+
scanDir(pubDir, "");
|
|
2400
|
+
if (newAssets.length > 0) {
|
|
2401
|
+
assets = [...assets, ...newAssets];
|
|
2402
|
+
break; // Use first matching public dir only
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2405
|
+
// Limit to 40 assets to keep story file size reasonable
|
|
2406
|
+
assets = assets.slice(0, 40);
|
|
2407
|
+
}
|
|
2408
|
+
|
|
2297
2409
|
const brandContent =
|
|
2298
2410
|
[
|
|
2299
2411
|
"import type { Meta, StoryObj } from \"@storybook/react\";",
|
|
@@ -2307,10 +2419,10 @@ function writeFoundationsStories(foundations) {
|
|
|
2307
2419
|
"export const Default: Story = {",
|
|
2308
2420
|
" render: () => (",
|
|
2309
2421
|
" <div style={{ display: \"flex\", gap: 24, flexWrap: \"wrap\", padding: 24 }}>",
|
|
2310
|
-
" {assets.length === 0 ? <p>No brand assets found
|
|
2311
|
-
" <div key={i} style={{ textAlign: \"center\" }}>",
|
|
2312
|
-
" <img src={\"/\" + String(a.path || \"\").replace(
|
|
2313
|
-
" <div style={{ fontSize: 11, marginTop: 8, color: \"#666\" }}>{a.name || a.type || \"asset\"}</div>",
|
|
2422
|
+
" {assets.length === 0 ? <p style={{ color: '#888', fontFamily: 'monospace' }}>No brand assets found — add images to public/ or client/public/</p> : assets.map((a, i) => (",
|
|
2423
|
+
" <div key={i} style={{ textAlign: \"center\", background: '#f8f8f8', borderRadius: 8, padding: 12, minWidth: 120 }}>",
|
|
2424
|
+
" <img src={a.url || (\"/\" + String(a.path || \"\").replace(/^public\\//, \"\"))} style={{ maxHeight: 100, maxWidth: 180, objectFit: \"contain\", display: 'block', margin: '0 auto' }} alt={a.name || \"\"} />",
|
|
2425
|
+
" <div style={{ fontSize: 11, marginTop: 8, color: \"#666\", wordBreak: 'break-all' }}>{a.name || a.type || \"asset\"}</div>",
|
|
2314
2426
|
" </div>",
|
|
2315
2427
|
" ))}",
|
|
2316
2428
|
" </div>",
|
|
@@ -3496,6 +3608,12 @@ function main() {
|
|
|
3496
3608
|
if (!onlyName) {
|
|
3497
3609
|
try {
|
|
3498
3610
|
const existing = fs.readdirSync(STORIES_DIR);
|
|
3611
|
+
// Remove macOS copy artifacts: "Button.stories 2.tsx", "Badge.stories 3.tsx", etc.
|
|
3612
|
+
for (const name of existing) {
|
|
3613
|
+
if (/\.stories \d+\.(tsx|ts|jsx|js)$/.test(name)) {
|
|
3614
|
+
try { fs.unlinkSync(path.join(STORIES_DIR, name)); } catch { /* ignore */ }
|
|
3615
|
+
}
|
|
3616
|
+
}
|
|
3499
3617
|
for (const name of existing) {
|
|
3500
3618
|
if (name === "foundations") continue;
|
|
3501
3619
|
if (
|