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 +1 -1
- package/vds-core-template/scan.mjs +129 -338
package/package.json
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
300
|
-
|
|
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
|
|
564
|
-
const
|
|
565
|
-
const
|
|
566
|
-
const
|
|
567
|
-
const
|
|
568
|
-
const
|
|
569
|
-
const
|
|
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
|
-
|
|
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 (
|
|
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 =
|
|
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
|
-
|
|
669
|
-
path.join(PROJECT_ROOT, "tailwind.config.ts")
|
|
670
|
-
path.join(PROJECT_ROOT, "tailwind.config.js")
|
|
671
|
-
|
|
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
|
|
678
|
-
const
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
.
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
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
|
|
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:
|
|
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
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
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,
|
|
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
|
}
|