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 +23 -6
- package/package.json +1 -1
- package/vds-core-template/story-generator.mjs +136 -22
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
|
@@ -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
|
)`,
|
|
@@ -425,13 +424,21 @@ function componentWrapsVoidElement(source) {
|
|
|
425
424
|
}
|
|
426
425
|
|
|
427
426
|
function toSafeComponentName(name, file) {
|
|
428
|
-
|
|
429
|
-
|
|
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
|
|
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
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
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
|
-
|
|
3522
|
-
|
|
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)
|
|
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
|
|