vibe-design-system 1.9.6 → 1.9.9

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibe-design-system",
3
- "version": "1.9.6",
3
+ "version": "1.9.9",
4
4
  "description": "Auto-generate design systems for vibe coding projects",
5
5
  "type": "module",
6
6
  "bin": {
@@ -14,22 +14,6 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
14
14
  const PROJECT_ROOT = path.join(__dirname, "..");
15
15
  const projectRequire = createRequire(path.join(PROJECT_ROOT, "package.json"));
16
16
 
17
- function getVdsPackageVersion() {
18
- try {
19
- const fromNodeModules = path.join(PROJECT_ROOT, "node_modules", "vibe-design-system", "package.json");
20
- if (fs.existsSync(fromNodeModules)) {
21
- const pkg = JSON.parse(fs.readFileSync(fromNodeModules, "utf-8"));
22
- return pkg.version || null;
23
- }
24
- const nextToScript = path.join(__dirname, "..", "package.json");
25
- if (fs.existsSync(nextToScript)) {
26
- const pkg = JSON.parse(fs.readFileSync(nextToScript, "utf-8"));
27
- if (pkg.name === "vibe-design-system") return pkg.version || null;
28
- }
29
- } catch (_) {}
30
- return null;
31
- }
32
-
33
17
  const CLI_LOCALES = {
34
18
  en: {
35
19
  componentsNotFound: "src/components not found. VDS scan skipped.",
@@ -44,6 +28,7 @@ const CLI_LOCALE = (process.env.VDS_LOCALE === "tr" ? "tr" : "en");
44
28
  const cliT = (key, n) => CLI_LOCALES[CLI_LOCALE][key].replace("{n}", String(n));
45
29
  const SRC_DIR = path.join(PROJECT_ROOT, "src");
46
30
  const COMPONENTS_DIR = path.join(PROJECT_ROOT, "src", "components");
31
+ const PAGES_DIR = path.join(PROJECT_ROOT, "src", "pages");
47
32
  const OUTPUT_FILE = path.join(PROJECT_ROOT, "vds-output.json");
48
33
  const PUBLIC_MANIFEST = path.join(PROJECT_ROOT, "public", "vds-output.json");
49
34
  const HISTORY_FILE = path.join(PROJECT_ROOT, "vds-history.json");
@@ -266,7 +251,7 @@ function getAllTsxJsxInDir(dir) {
266
251
  }
267
252
 
268
253
  const BRAND_KEYWORDS = /logo|brand|icon|favicon|emblem|mark/i;
269
- const BRAND_EXTENSIONS = /\.(svg|png|ico|jpg|jpeg|webp|gif)$/i;
254
+ const BRAND_EXTENSIONS = /\.(svg|png|ico)$/i;
270
255
 
271
256
  function getFilesByExtension(dir, extRe, baseDir = dir) {
272
257
  if (!fs.existsSync(dir)) return [];
@@ -283,7 +268,7 @@ function getFilesByExtension(dir, extRe, baseDir = dir) {
283
268
  return files;
284
269
  }
285
270
 
286
- /** Scan public/ and src/assets/ for all images; include logos/brand by keyword, rest as "asset". Preview URL is relative for dashboard /api/vds-asset. */
271
+ /** Scan public/, src/assets/, src/images/ for .svg, .png, .ico; filter by brand keywords. */
287
272
  function extractBrandAssets() {
288
273
  const assets = [];
289
274
  const dirs = [
@@ -292,12 +277,26 @@ function extractBrandAssets() {
292
277
  path.join(PROJECT_ROOT, "src", "images"),
293
278
  ];
294
279
  for (const dir of dirs) {
295
- if (!fs.existsSync(dir)) continue;
280
+ const relDir = path.relative(PROJECT_ROOT, dir).replace(/\\/g, "/");
296
281
  const files = getFilesByExtension(dir, BRAND_EXTENSIONS, PROJECT_ROOT);
297
282
  for (const filePath of files) {
298
283
  const baseName = path.basename(filePath);
299
- const type = /favicon|\.ico$/i.test(baseName) ? "favicon" : BRAND_KEYWORDS.test(baseName) ? (baseName.match(/logo|brand|emblem|mark/i) ? "logo" : "icon") : "asset";
300
- assets.push({ path: filePath, name: baseName, type, previewUrl: `/api/vds-asset?path=${encodeURIComponent(filePath)}` });
284
+ if (!BRAND_KEYWORDS.test(baseName)) continue;
285
+ const type = /favicon|\.ico$/i.test(baseName) ? "favicon" : /logo|brand|emblem|mark/i.test(baseName) ? "logo" : "icon";
286
+ assets.push({ path: filePath, name: baseName, type });
287
+ }
288
+ }
289
+ // Fallback: if no branded assets found by keyword, include all image files from src/assets
290
+ if (assets.length === 0 || assets.every((r) => r.type === "asset")) {
291
+ const assetsDir = path.join(SRC_DIR, "assets");
292
+ if (fs.existsSync(assetsDir)) {
293
+ const imgExtRe = /\.(png|jpg|jpeg|svg|gif|webp|ico)$/i;
294
+ const allImages = fs.readdirSync(assetsDir).filter((f) => imgExtRe.test(f));
295
+ for (const img of allImages) {
296
+ if (!assets.some((r) => r.path === "src/assets/" + img)) {
297
+ assets.push({ type: "asset", path: "src/assets/" + img, name: img });
298
+ }
299
+ }
301
300
  }
302
301
  }
303
302
  return assets;
@@ -353,14 +352,6 @@ function humanizeName(filePath) {
353
352
  return base.replace(/([A-Z])/g, " $1").replace(/^./, (s) => s.toUpperCase()).trim();
354
353
  }
355
354
 
356
- /** Normalize component source for storage: consistent newlines, collapse excess blank lines, trim. */
357
- function cleanRawCode(raw) {
358
- if (typeof raw !== "string") return "";
359
- let s = raw.replace(/\r\n/g, "\n").replace(/\r/g, "\n").trim();
360
- s = s.replace(/\n{3,}/g, "\n\n");
361
- return s;
362
- }
363
-
364
355
  function extractTailwindTokens(content) {
365
356
  const tokens = new Set();
366
357
  const patterns = [
@@ -385,42 +376,6 @@ function extractTailwindTokens(content) {
385
376
  return [...tokens].sort();
386
377
  }
387
378
 
388
- const TAILWIND_CATEGORIES = [
389
- { tag: "Spacing", test: (c) => /^(p|m|gap|space|w|h|min-|max-|size)-|padding|margin/.test(c) || /^inset-|top-|left-|right-|bottom-/.test(c) },
390
- { tag: "Colors", test: (c) => /^(bg|text|border|ring|from|to|via|fill|stroke|decoration|divide|placeholder)-/.test(c) || /^outline-/.test(c) },
391
- { tag: "Layout", test: (c) => /^(flex|grid|block|inline|hidden|visible|overflow|float|clear|object-|aspect-)/.test(c) || /^container|box-/.test(c) },
392
- { tag: "Typography", test: (c) => /^(text|font|leading|tracking|italic|not-italic|antialiased|subpixel)/.test(c) || /^truncate|break-|whitespace-|underline|line-through/.test(c) },
393
- { tag: "Effects", test: (c) => /^(shadow|opacity|mix-blend|bg-blend)/.test(c) },
394
- { tag: "Borders", test: (c) => /^(border|rounded|ring-|outline)/.test(c) },
395
- { tag: "Transitions", test: (c) => /^(transition|duration|ease|animate|delay)/.test(c) },
396
- { tag: "Interactivity", test: (c) => /^(cursor|pointer-events|resize|scroll|snap)/.test(c) || /^hover:|focus:|active:|disabled:/.test(c) },
397
- { tag: "Responsive", test: (c) => /^(sm|md|lg|xl|2xl):/.test(c) },
398
- { tag: "Position", test: (c) => /^(relative|absolute|fixed|sticky)/.test(c) },
399
- ];
400
-
401
- function categorizeTailwindTokens(tokens) {
402
- const byCategory = {};
403
- const tags = new Set();
404
- for (const token of tokens) {
405
- let matched = false;
406
- for (const { tag, test } of TAILWIND_CATEGORIES) {
407
- if (test(token)) {
408
- tags.add(tag);
409
- if (!byCategory[tag]) byCategory[tag] = [];
410
- byCategory[tag].push(token);
411
- matched = true;
412
- break;
413
- }
414
- }
415
- if (!matched) {
416
- tags.add("Other");
417
- if (!byCategory.Other) byCategory.Other = [];
418
- byCategory.Other.push(token);
419
- }
420
- }
421
- return { tags: [...tags].sort(), byCategory };
422
- }
423
-
424
379
  /** Parse HSL string "0 0% 0%" or "hsl(0 0% 0%)" and return hex. */
425
380
  function hslToHex(hslStr) {
426
381
  const match = hslStr.match(/hsl\s*\(\s*([\d.]+)\s*[, ]\s*([\d.]+)%\s*[, ]\s*([\d.]+)%\s*\)/) ||
@@ -466,83 +421,6 @@ function parseCssVarBlock(block) {
466
421
  return out;
467
422
  }
468
423
 
469
- /** Default Tailwind theme values when project has none (no placeholders). */
470
- const DEFAULT_TAILWIND = {
471
- spacing: {
472
- 0: "0", 0.5: "0.125rem", 1: "0.25rem", 1.5: "0.375rem", 2: "0.5rem", 2.5: "0.625rem",
473
- 3: "0.75rem", 3.5: "0.875rem", 4: "1rem", 5: "1.25rem", 6: "1.5rem", 7: "1.75rem",
474
- 8: "2rem", 9: "2.25rem", 10: "2.5rem", 11: "2.75rem", 12: "3rem", 14: "3.5rem",
475
- 16: "4rem", 20: "5rem", 24: "6rem", 28: "7rem", 32: "8rem", 36: "9rem", 40: "10rem",
476
- 44: "11rem", 48: "12rem", 52: "13rem", 56: "14rem", 60: "15rem", 64: "16rem",
477
- 72: "18rem", 80: "20rem", 96: "24rem", px: "1px",
478
- },
479
- borderRadius: {
480
- none: "0", sm: "0.125rem", DEFAULT: "0.25rem", md: "0.375rem", lg: "0.5rem",
481
- xl: "0.75rem", "2xl": "1rem", "3xl": "1.5rem", full: "9999px",
482
- },
483
- boxShadow: {
484
- sm: "0 1px 2px 0 rgb(0 0 0 / 0.05)",
485
- DEFAULT: "0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)",
486
- md: "0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)",
487
- lg: "0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)",
488
- xl: "0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)",
489
- "2xl": "0 25px 50px -12px rgb(0 0 0 / 0.25)",
490
- },
491
- transitionDuration: { DEFAULT: "150ms", 75: "75ms", 100: "100ms", 150: "150ms", 200: "200ms", 300: "300ms", 500: "500ms", 700: "700ms", 1000: "1000ms" },
492
- transitionTimingFunction: { DEFAULT: "cubic-bezier(0.4, 0, 0.2, 1)", linear: "linear", in: "cubic-bezier(0.4, 0, 1, 1)", out: "cubic-bezier(0, 0, 0.2, 1)", "in-out": "cubic-bezier(0.4, 0, 0.2, 1)" },
493
- animation: { DEFAULT: "none" },
494
- };
495
-
496
- /** Extract a single theme block (e.g. borderRadius: { lg: "..." }) from config text. Handles nested braces. */
497
- function extractThemeBlock(content, key) {
498
- const keyRe = new RegExp(`${key}\\s*:\\s*\\{`, "g");
499
- const m = keyRe.exec(content);
500
- if (!m) return null;
501
- let start = m.index + m[0].length;
502
- let depth = 1;
503
- let i = start;
504
- while (i < content.length && depth > 0) {
505
- if (content[i] === "{") depth++;
506
- else if (content[i] === "}") depth--;
507
- i++;
508
- }
509
- const block = content.slice(start, i - 1);
510
- const out = {};
511
- const pairRe = /(\w+):\s*["']([^"']*)["']/g;
512
- let pm;
513
- while ((pm = pairRe.exec(block)) !== null) out[pm[1]] = pm[2].trim();
514
- if (Object.keys(out).length === 0) {
515
- const fallbackRe = /(\w+):\s*([^,}\n]+)/g;
516
- while ((pm = fallbackRe.exec(block)) !== null) out[pm[1]] = pm[2].trim().replace(/^["']|["']$/g, "");
517
- }
518
- return Object.keys(out).length ? out : null;
519
- }
520
-
521
- /** Parse tailwind.config.js/ts/mjs as raw text to extract theme.extend (and theme) for radius, shadows, motion. */
522
- function parseTailwindConfigRaw(content) {
523
- const out = { borderRadius: {}, boxShadow: {}, transitionDuration: {}, transitionTimingFunction: {}, animation: {}, spacing: {}, screens: {} };
524
- const normalized = content.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/[^\n]*/g, "");
525
- const extendMatch = normalized.match(/extend\s*:\s*\{/);
526
- const themeMatch = normalized.match(/theme\s*:\s*\{/);
527
- const searchStart = extendMatch ? normalized.indexOf("extend") : themeMatch ? normalized.indexOf("theme") : 0;
528
- const slice = searchStart ? normalized.slice(searchStart) : normalized;
529
- const br = extractThemeBlock(slice, "borderRadius");
530
- if (br) out.borderRadius = br;
531
- const bs = extractThemeBlock(slice, "boxShadow");
532
- if (bs) out.boxShadow = bs;
533
- const td = extractThemeBlock(slice, "transitionDuration");
534
- if (td) out.transitionDuration = td;
535
- const ttf = extractThemeBlock(slice, "transitionTimingFunction");
536
- if (ttf) out.transitionTimingFunction = ttf;
537
- const anim = extractThemeBlock(slice, "animation");
538
- if (anim) out.animation = anim;
539
- const sp = extractThemeBlock(slice, "spacing");
540
- if (sp) out.spacing = sp;
541
- const scr = extractThemeBlock(slice, "screens");
542
- if (scr) out.screens = scr;
543
- return out;
544
- }
545
-
546
424
  /** Resolve Tailwind theme for boxShadow, spacing, screens, zIndex, motion. Uses resolveConfig when available. */
547
425
  function getTailwindTheme() {
548
426
  const empty = { shadows: {}, spacing: {}, breakpoints: {}, zIndex: {}, transitionDuration: {}, transitionTimingFunction: {}, animation: {} };
@@ -560,14 +438,13 @@ function getTailwindTheme() {
560
438
  }
561
439
  const resolved = resolveConfig(config);
562
440
  const theme = resolved.theme || {};
563
- const extend = theme.extend || {};
564
- const boxShadow = extend.boxShadow ?? theme.boxShadow;
565
- const spacing = extend.spacing ?? theme.spacing;
566
- const screens = extend.screens ?? theme.screens;
567
- const zIndex = extend.zIndex ?? theme.zIndex;
568
- const transitionDuration = extend.transitionDuration ?? theme.transitionDuration;
569
- const transitionTimingFunction = extend.transitionTimingFunction ?? theme.transitionTimingFunction;
570
- const animation = extend.animation ?? theme.animation;
441
+ const boxShadow = theme.boxShadow;
442
+ const spacing = theme.spacing;
443
+ const screens = theme.screens;
444
+ const zIndex = theme.zIndex;
445
+ const transitionDuration = theme.transitionDuration;
446
+ const transitionTimingFunction = theme.transitionTimingFunction;
447
+ const animation = theme.animation;
571
448
  const toObj = (v) => (v && typeof v === "object" && !Array.isArray(v) ? v : {});
572
449
  return {
573
450
  shadows: toObj(boxShadow),
@@ -589,7 +466,6 @@ function extractFoundations() {
589
466
  const typography = {};
590
467
  const cssRadiusVars = {};
591
468
  const borderRadiusScale = {};
592
- const cssByPrefix = { radius: {}, spacing: {}, border: {}, shadows: {}, motion: {} };
593
469
  const cssPath = path.join(PROJECT_ROOT, "src", "index.css");
594
470
  const globalsCss = path.join(PROJECT_ROOT, "src", "globals.css");
595
471
  const appGlobals = path.join(PROJECT_ROOT, "app", "globals.css");
@@ -603,30 +479,10 @@ function extractFoundations() {
603
479
  if (rootMatch) {
604
480
  const rootVars = parseCssVarBlock(rootMatch[1]);
605
481
  for (const [name, value] of Object.entries(rootVars)) {
606
- const norm = name.toLowerCase();
607
- if (norm.startsWith("radius") || norm === "radius") {
608
- cssByPrefix.radius[name] = value;
609
- } else if (norm.startsWith("spacing") || norm.startsWith("space")) {
610
- cssByPrefix.spacing[name] = value;
611
- } else if (norm.startsWith("border")) {
612
- cssByPrefix.border[name] = value;
613
- } else if (norm.startsWith("shadow")) {
614
- cssByPrefix.shadows[name] = value;
615
- } else if (norm.startsWith("font") || norm.startsWith("line-height") || norm.startsWith("font-weight") || norm.startsWith("letter-spacing")) {
616
- typography[name] = value;
617
- } else if (norm.startsWith("color")) {
618
- if (/\d+\s+\d+%/.test(value) || /\d+%/.test(value)) {
619
- const hsl = `hsl(${value})`;
620
- colors[name] = { value: hsl, hex: hslToHex(hsl) || hsl };
621
- } else {
622
- colors[name] = { value, hex: value };
623
- }
624
- } else if (/\d+\s+\d+%/.test(value) || /\d+%/.test(value)) {
482
+ if (/\d+\s+\d+%/.test(value) || /\d+%/.test(value)) {
625
483
  const hsl = `hsl(${value})`;
626
484
  colors[name] = { value: hsl, hex: hslToHex(hsl) || hsl };
627
- } else if (/^\d+(\.\d+)?(ms|s)$/.test(value) || /ease|linear|cubic/.test(value)) {
628
- cssByPrefix.motion[name] = value;
629
- } else if (/rem|px|em|calc/.test(value)) {
485
+ } else if (/rem|px/.test(value)) {
630
486
  cssRadiusVars[name] = value;
631
487
  } else {
632
488
  colors[name] = { value, hex: value };
@@ -646,190 +502,125 @@ function extractFoundations() {
646
502
  }
647
503
  }
648
504
 
649
- const bodyBlock = css.match(/body\s*\{([^}]*)\}/);
650
- if (bodyBlock) {
651
- const bodyProps = bodyBlock[1];
652
- const ff = bodyProps.match(/font-family:\s*([^;]+);/);
653
- if (ff) typography.bodyFontFamily = ff[1].trim();
654
- const lh = bodyProps.match(/line-height:\s*([^;]+);/);
655
- if (lh) typography.bodyLineHeight = lh[1].trim();
656
- const fw = bodyProps.match(/font-weight:\s*([^;]+);/);
657
- if (fw) typography.bodyFontWeight = fw[1].trim();
658
- const ls = bodyProps.match(/letter-spacing:\s*([^;]+);/);
659
- if (ls) typography.bodyLetterSpacing = ls[1].trim();
660
- }
661
505
  const bodyMatch = css.match(/body\s*\{[^}]*font-family:\s*([^;]+);/);
662
- if (bodyMatch) typography.body = typography.body || bodyMatch[1].trim();
506
+ if (bodyMatch) typography.body = bodyMatch[1].trim();
663
507
  const monoMatch = css.match(/code,\s*pre,\s*\.font-mono\s*\{[^}]*font-family:\s*([^;]+);/);
664
508
  if (monoMatch) typography.mono = monoMatch[1].trim();
665
509
  }
666
510
  } catch (_) {}
667
511
 
668
- const twPaths = [
669
- path.join(PROJECT_ROOT, "tailwind.config.ts"),
670
- path.join(PROJECT_ROOT, "tailwind.config.js"),
671
- path.join(PROJECT_ROOT, "tailwind.config.mjs"),
672
- ];
673
- let rawParsed = null;
674
- for (const twFile of twPaths) {
512
+ try {
513
+ const twPath = path.join(PROJECT_ROOT, "tailwind.config.ts");
514
+ const twPathJs = path.join(PROJECT_ROOT, "tailwind.config.js");
515
+ const twFile = fs.existsSync(twPath) ? twPath : twPathJs;
675
516
  if (fs.existsSync(twFile)) {
517
+ const tw = fs.readFileSync(twFile, "utf-8");
518
+ const sansMatch = tw.match(/sans:\s*\[([^\]]+)\]/);
519
+ if (sansMatch) {
520
+ typography.tailwindSans = sansMatch[1]
521
+ .split(",")
522
+ .map((s) => s.trim().replace(/^['"`]|['"`]$/g, ""))
523
+ .filter(Boolean);
524
+ }
525
+ const monoMatch2 = tw.match(/mono:\s*\[([^\]]+)\]/);
526
+ if (monoMatch2) {
527
+ typography.tailwindMono = monoMatch2[1]
528
+ .split(",")
529
+ .map((s) => s.trim().replace(/^['"`]|['"`]$/g, ""))
530
+ .filter(Boolean);
531
+ }
532
+ const brMatch = tw.match(/borderRadius:\s*\{([\s\S]*?)\}/);
533
+ if (brMatch) {
534
+ const body = brMatch[1];
535
+ const brRe = /(\w+):\s*"([^"]+)"/g;
536
+ let m2;
537
+ while ((m2 = brRe.exec(body)) !== null) borderRadiusScale[m2[1]] = m2[2];
538
+ }
539
+ }
540
+ } catch (_) {}
541
+
542
+ // Fallback: if no CSS variable colors found, extract hardcoded colors from component files
543
+ if (Object.keys(colors).length === 0) {
544
+ const hardcodedColors = new Map();
545
+ const srcFiles = getAllTsxJsxInDir(SRC_DIR);
546
+ for (const f of srcFiles) {
676
547
  try {
677
- const tw = fs.readFileSync(twFile, "utf-8");
678
- const sansMatch = tw.match(/sans:\s*\[([^\]]+)\]/);
679
- if (sansMatch) {
680
- typography.tailwindSans = sansMatch[1]
681
- .split(",")
682
- .map((s) => s.trim().replace(/^['"`]|['"`]$/g, ""))
683
- .filter(Boolean);
684
- }
685
- const monoMatch2 = tw.match(/mono:\s*\[([^\]]+)\]/);
686
- if (monoMatch2) {
687
- typography.tailwindMono = monoMatch2[1]
688
- .split(",")
689
- .map((s) => s.trim().replace(/^['"`]|['"`]$/g, ""))
690
- .filter(Boolean);
691
- }
692
- const brMatch = tw.match(/borderRadius:\s*\{([\s\S]*?)\}/);
693
- if (brMatch) {
694
- const body = brMatch[1];
695
- const brRe = /(\w+):\s*["']?([^"',}\n]+)["']?/g;
696
- let m2;
697
- while ((m2 = brRe.exec(body)) !== null) borderRadiusScale[m2[1]] = m2[2].trim();
548
+ const fullPath = path.join(SRC_DIR, f);
549
+ const content = fs.readFileSync(fullPath, "utf-8");
550
+ const matches = content.matchAll(/(bg|text|border|from|to|via|ring|fill|stroke|shadow|outline|accent|divide|decoration|placeholder)-\[#([0-9a-fA-F]{3,8})\]/g);
551
+ for (const m of matches) {
552
+ const hex = "#" + m[2].toLowerCase();
553
+ if (!hardcodedColors.has(hex)) {
554
+ hardcodedColors.set(hex, { usages: new Set(), count: 0 });
555
+ }
556
+ hardcodedColors.get(hex).usages.add(m[1]);
557
+ hardcodedColors.get(hex).count++;
698
558
  }
699
- rawParsed = parseTailwindConfigRaw(tw);
700
- break;
701
559
  } catch (_) {}
702
560
  }
561
+ const sorted = [...hardcodedColors.entries()].sort((a, b) => b[1].count - a[1].count);
562
+ for (const [hex, info] of sorted) {
563
+ const primaryUsage = [...info.usages][0];
564
+ const name = `color-${primaryUsage}-${hex.slice(1)}`;
565
+ colors[name] = { value: hex, hex };
566
+ }
703
567
  }
704
568
 
705
569
  const radius = {};
706
570
  if (cssRadiusVars.radius) radius.base = cssRadiusVars.radius;
707
- for (const [k, v] of Object.entries(cssRadiusVars)) {
708
- if (k !== "_motion" && k !== "radius" && /radius|rounded/.test(k)) radius[k] = v;
709
- }
710
- for (const [k, v] of Object.entries(cssByPrefix.radius)) radius[k] = v;
711
571
  if (Object.keys(borderRadiusScale).length > 0) radius.borderRadius = borderRadiusScale;
712
- if (rawParsed && Object.keys(rawParsed.borderRadius || {}).length > 0) {
713
- radius.borderRadius = { ...radius.borderRadius, ...rawParsed.borderRadius };
714
- }
715
-
716
572
  const foundationsColors = { ...colors };
717
573
  if (Object.keys(colorsDark).length > 0) foundationsColors._dark = colorsDark;
718
574
 
575
+ // Fallback: if no typography tokens from config, extract from Google Fonts @import
576
+ if (Object.keys(typography).length === 0 || (!typography.body && !typography.bodyFontFamily)) {
577
+ for (const cssFile of ["src/index.css", "src/App.css", "src/globals.css", "src/styles/globals.css"]) {
578
+ const cssPath = path.join(PROJECT_ROOT, cssFile);
579
+ if (!fs.existsSync(cssPath)) continue;
580
+ try {
581
+ const cssContent = fs.readFileSync(cssPath, "utf-8");
582
+ const fontImports = cssContent.matchAll(/@import\s+url\(['"]?https:\/\/fonts\.googleapis\.com\/css2\?family=([^&'"]+)/g);
583
+ const families = [];
584
+ for (const m of fontImports) {
585
+ const family = decodeURIComponent(m[1]).replace(/\+/g, " ").split(":")[0];
586
+ families.push(family);
587
+ }
588
+ if (families.length > 0) {
589
+ typography.body = families[0] + ", sans-serif";
590
+ typography.fontFamilies = families;
591
+ if (families.length > 1) typography.headingFont = families.find((f) => f !== families[0]) || families[1];
592
+ break;
593
+ }
594
+ } catch (_) {}
595
+ }
596
+ }
597
+
719
598
  const twTheme = getTailwindTheme();
720
599
  const normalizeThemeObj = (obj) => {
721
600
  if (!obj || typeof obj !== "object") return {};
722
601
  const out = {};
723
602
  for (const [k, v] of Object.entries(obj)) {
724
- if (v !== undefined && v !== null && k !== "_placeholder") out[k] = typeof v === "string" ? v : String(v);
603
+ if (v !== undefined && v !== null) out[k] = typeof v === "string" ? v : String(v);
725
604
  }
726
605
  return out;
727
606
  };
728
-
729
- let shadows = {
730
- ...normalizeThemeObj(rawParsed?.boxShadow),
731
- ...normalizeThemeObj(twTheme.shadows),
732
- ...cssByPrefix.shadows,
733
- };
734
- if (Object.keys(shadows).length === 0) shadows = DEFAULT_TAILWIND.boxShadow;
735
-
736
- const motionFromCss = cssByPrefix.motion || {};
737
- let transitionDuration = {
738
- ...normalizeThemeObj(rawParsed?.transitionDuration),
739
- ...normalizeThemeObj(twTheme.transitionDuration),
740
- ...(Object.keys(motionFromCss).length ? { _cssVars: motionFromCss } : {}),
741
- };
742
- if (Object.keys(transitionDuration).length === 0) transitionDuration = DEFAULT_TAILWIND.transitionDuration;
743
- let transitionTimingFunction = {
744
- ...normalizeThemeObj(rawParsed?.transitionTimingFunction),
745
- ...normalizeThemeObj(twTheme.transitionTimingFunction),
746
- };
747
- if (Object.keys(transitionTimingFunction).length === 0) transitionTimingFunction = DEFAULT_TAILWIND.transitionTimingFunction;
748
- let animation = {
749
- ...normalizeThemeObj(rawParsed?.animation),
750
- ...normalizeThemeObj(twTheme.animation),
751
- };
752
- if (Object.keys(animation).length === 0) animation = DEFAULT_TAILWIND.animation;
753
-
754
- let spacing = Object.keys(normalizeThemeObj(rawParsed?.spacing)).length
755
- ? { ...normalizeThemeObj(rawParsed.spacing), ...normalizeThemeObj(twTheme.spacing) }
756
- : normalizeThemeObj(twTheme.spacing);
757
- if (Object.keys(spacing).length === 0) spacing = DEFAULT_TAILWIND.spacing;
758
- spacing = { ...spacing, ...cssByPrefix.spacing };
759
-
760
- const border = Object.keys(cssByPrefix.border).length ? cssByPrefix.border : null;
761
-
762
607
  return {
763
608
  colors: foundationsColors,
764
609
  typography,
765
610
  radius,
766
- shadows,
767
- spacing,
768
- breakpoints: Object.keys(normalizeThemeObj(rawParsed?.screens)).length
769
- ? { ...normalizeThemeObj(rawParsed.screens), ...normalizeThemeObj(twTheme.breakpoints) }
770
- : normalizeThemeObj(twTheme.breakpoints),
611
+ shadows: normalizeThemeObj(twTheme.shadows),
612
+ spacing: normalizeThemeObj(twTheme.spacing),
613
+ breakpoints: normalizeThemeObj(twTheme.breakpoints),
771
614
  zIndex: normalizeThemeObj(twTheme.zIndex),
772
- ...(border ? { border } : {}),
773
615
  motion: {
774
- transitionDuration,
775
- transitionTimingFunction,
776
- animation,
616
+ transitionDuration: normalizeThemeObj(twTheme.transitionDuration),
617
+ transitionTimingFunction: normalizeThemeObj(twTheme.transitionTimingFunction),
618
+ animation: normalizeThemeObj(twTheme.animation),
777
619
  },
778
620
  };
779
621
  }
780
622
 
781
- /** Resolve a Tailwind utility class to its theme value (e.g. rounded-md → 0.375rem). */
782
- function resolveClassToValue(className, foundations) {
783
- if (!foundations || typeof className !== "string") return null;
784
- const parts = className.split("-");
785
- if (parts.length < 2) return null;
786
- const spacingKeys = ["p", "m", "px", "py", "pt", "pr", "pb", "pl", "gap", "w", "h", "min-w", "min-h", "max-w", "max-h", "top", "right", "bottom", "left", "space-x", "space-y"];
787
- const spacing = foundations.spacing || DEFAULT_TAILWIND.spacing;
788
- const radius = foundations.radius?.borderRadius || foundations.radius || DEFAULT_TAILWIND.borderRadius;
789
- const shadows = foundations.shadows || DEFAULT_TAILWIND.boxShadow;
790
- const motionDuration = foundations.motion?.transitionDuration || DEFAULT_TAILWIND.transitionDuration;
791
- const motionEasing = foundations.motion?.transitionTimingFunction || DEFAULT_TAILWIND.transitionTimingFunction;
792
-
793
- if (parts[0] === "rounded") {
794
- const key = parts.length >= 2 ? parts.slice(1).join("-") : "DEFAULT";
795
- const v = radius[key];
796
- return v != null ? v : null;
797
- }
798
- if (parts[0] === "shadow") {
799
- const key = parts.length === 2 ? "DEFAULT" : parts[1];
800
- const v = shadows[key];
801
- return v != null ? v : null;
802
- }
803
- if (spacingKeys.some((pre) => className === pre || className.startsWith(pre + "-"))) {
804
- const last = parts[parts.length - 1];
805
- const v = spacing[last];
806
- if (v != null) return v;
807
- return null;
808
- }
809
- if (parts[0] === "duration") {
810
- const key = parts[1] || "DEFAULT";
811
- return motionDuration[key] ?? null;
812
- }
813
- if (parts[0] === "ease") {
814
- const key = parts[1] || "DEFAULT";
815
- return motionEasing[key] ?? null;
816
- }
817
- return null;
818
- }
819
-
820
- function buildResolvedSpecs(tokens, foundations) {
821
- if (!Array.isArray(tokens) || !foundations) return [];
822
- const out = [];
823
- for (const t of tokens) {
824
- const value = resolveClassToValue(t, foundations);
825
- if (value != null) out.push({ class: t, value });
826
- }
827
- return out;
828
- }
829
-
830
623
  function scan() {
831
- const pkgVersion = getVdsPackageVersion();
832
- if (pkgVersion) console.log("[VDS] vibe-design-system v" + pkgVersion);
833
624
  if (!fs.existsSync(COMPONENTS_DIR)) {
834
625
  console.error(CLI_LOCALES[CLI_LOCALE].componentsNotFound);
835
626
  process.exit(1);
@@ -854,26 +645,28 @@ function scan() {
854
645
  description = "";
855
646
  }
856
647
  const tokens = extractTailwindTokens(content);
857
- const { tags: tokenTags, byCategory: tokenCategories } = categorizeTailwindTokens(tokens);
858
- results.push({
859
- file: rel,
860
- name,
861
- group,
862
- category,
863
- description,
864
- tokens,
865
- tokenTags,
866
- tokenCategories,
867
- componentRender: true,
868
- rawCode: cleanRawCode(content),
869
- });
648
+ results.push({ file: rel, name, group, category, description, tokens });
649
+ }
650
+ if (fs.existsSync(PAGES_DIR)) {
651
+ const pageFiles = getAllComponentFiles(PAGES_DIR);
652
+ for (const rel of pageFiles) {
653
+ const fullPath = path.join(PAGES_DIR, rel);
654
+ const content = fs.readFileSync(fullPath, "utf-8");
655
+ const name = humanizeName(rel);
656
+ const tokens = extractTailwindTokens(content);
657
+ results.push({
658
+ file: "pages/" + rel,
659
+ name,
660
+ group: "Sections",
661
+ category: "Pages",
662
+ description: "",
663
+ tokens,
664
+ });
665
+ }
870
666
  }
871
667
  const foundations = extractFoundations();
872
668
  foundations.icons = extractLucideIconsUsed(SRC_DIR);
873
669
  foundations.brand = { assets: extractBrandAssets() };
874
- for (const r of results) {
875
- r.resolvedSpecs = buildResolvedSpecs(r.tokens, foundations);
876
- }
877
670
  const output = {
878
671
  branch: getGitBranch(),
879
672
  engineer: getGitEngineer(),
@@ -923,7 +716,7 @@ function loadVdsOutputFromBranch(branch) {
923
716
  }
924
717
 
925
718
  function componentSignature(c) {
926
- return JSON.stringify({ name: c.name, group: c.group, category: c.category, tokenTags: (c.tokenTags || []).slice().sort(), tokens: (c.tokens || []).slice().sort() });
719
+ return JSON.stringify({ name: c.name, group: c.group, category: c.category, tokens: (c.tokens || []).slice().sort() });
927
720
  }
928
721
 
929
722
  function runCompare() {
@@ -1008,8 +801,6 @@ if (isCompare) {
1008
801
  }
1009
802
  scan();
1010
803
  console.log("[VDS] Watch: src/components (.tsx, .jsx). Scan complete.");
1011
- const v = getVdsPackageVersion();
1012
- if (v) console.log("[VDS] vibe-design-system v" + v);
1013
804
  const url = vdsDashboardUrl();
1014
805
  console.log("[VDS] Dashboard: " + formatClickableLink(url) + "\n");
1015
806
  }