Skip to main content
Glama
design.ts11.8 kB
/** * @fileOverview: Design system analyzer for React applications * @module: DesignAnalyzer * @keyFunctions: * - analyzeDesignSystem(): Analyze styling approaches and design system usage * - detectTailwind(): Identify Tailwind CSS usage patterns * - detectShadcnUI(): Identify shadcn/ui component library usage * - detectCSSModules(): Identify CSS Modules and global CSS usage * @context: Detects styling patterns, design systems, and UI library usage */ import { readFile } from 'fs/promises'; import * as path from 'path'; import type { FileInfo } from '../../../../core/compactor/fileDiscovery'; import { logger } from '../../../../utils/logger'; export interface DesignSystemAnalysis { tailwind: boolean; shadcnUI: boolean; radixUI: boolean; lucideIcons: boolean; classVarianceAuthority: boolean; tailwindVariants: boolean; cssModules: boolean; styledComponents: boolean; emotion: boolean; globalCSS: string[]; designLibraries: string[]; } /** * Detect Tailwind CSS usage */ function detectTailwind(files: FileInfo[]): { hasTailwind: boolean; configFiles: string[] } { let hasTailwind = false; const configFiles: string[] = []; // Check for Tailwind config files for (const file of files) { const fileName = path.basename(file.relPath); if (fileName.startsWith('tailwind.config.') || fileName === 'tailwind.config') { hasTailwind = true; configFiles.push(file.relPath); } } // Check for Tailwind class usage in component files if (!hasTailwind) { for (const file of files) { if (file.ext === '.tsx' || file.ext === '.jsx' || file.ext === '.ts' || file.ext === '.js') { try { const content = readFile(file.absPath, 'utf-8').then(content => { // Check for common Tailwind classes const tailwindClasses = [ 'flex', 'grid', 'block', 'inline', 'hidden', 'p-', 'm-', 'w-', 'h-', 'text-', 'bg-', 'border', 'rounded', 'shadow', 'hover:', 'focus:', ]; for (const className of tailwindClasses) { if ( content.includes(`className="${className}`) || content.includes(`className='${className}`) ) { hasTailwind = true; break; } if (content.includes(`className={`) && content.includes(className)) { hasTailwind = true; break; } } }); } catch (error) { // Continue with next file } if (hasTailwind) break; } } } return { hasTailwind, configFiles }; } /** * Detect shadcn/ui usage */ function detectShadcnUI(files: FileInfo[]): { hasShadcnUI: boolean; componentFiles: string[] } { let hasShadcnUI = false; const componentFiles: string[] = []; for (const file of files) { try { const content = readFile(file.absPath, 'utf-8').then(content => { // Check for shadcn/ui imports if (content.includes('@/components/ui/') || content.includes('shadcn/ui')) { hasShadcnUI = true; componentFiles.push(file.relPath); } // Check for common shadcn/ui component usage const shadcnComponents = [ 'Button', 'Input', 'Card', 'Dialog', 'Sheet', 'AlertDialog', 'DropdownMenu', 'Select', 'Checkbox', 'RadioGroup', 'Switch', 'Tabs', 'Accordion', 'Alert', 'Badge', 'Avatar', ]; for (const component of shadcnComponents) { if (content.includes(`<${component}`) || content.includes(`import.*${component}`)) { hasShadcnUI = true; componentFiles.push(file.relPath); break; } } }); } catch (error) { // Continue with next file } } return { hasShadcnUI, componentFiles: [...new Set(componentFiles)] }; } /** * Detect Radix UI usage */ function detectRadixUI(files: FileInfo[]): { hasRadixUI: boolean; components: string[] } { let hasRadixUI = false; const components: string[] = []; for (const file of files) { try { const content = readFile(file.absPath, 'utf-8').then(content => { // Check for Radix UI imports if (content.includes('@radix-ui/')) { hasRadixUI = true; // Extract specific Radix components const radixImports = content.match(/import\s+.*from\s+['"]@radix-ui\/([^'"]+)['"]/g) || []; for (const imp of radixImports) { const component = imp.match(/@radix-ui\/([^'"]+)/)?.[1]; if (component && !components.includes(component)) { components.push(component); } } } }); } catch (error) { // Continue with next file } } return { hasRadixUI, components }; } /** * Detect Lucide icon usage */ function detectLucideIcons(files: FileInfo[]): boolean { for (const file of files) { try { const content = readFile(file.absPath, 'utf-8').then(content => { if ( content.includes('lucide-react') || content.includes('from "lucide-react"') || content.includes("from 'lucide-react'") ) { return true; } // Check for common Lucide icon names const lucideIcons = [ 'ArrowRight', 'ArrowLeft', 'ChevronDown', 'ChevronUp', 'ChevronRight', 'ChevronLeft', 'Search', 'User', 'Settings', 'Home', 'Menu', 'X', 'Check', 'Plus', 'Minus', ]; for (const icon of lucideIcons) { if (content.includes(`<${icon}`) || content.includes(`import.*${icon}`)) { return true; } } }); } catch (error) { // Continue with next file } } return false; } /** * Detect class-variance-authority usage */ function detectClassVarianceAuthority(files: FileInfo[]): boolean { for (const file of files) { try { const content = readFile(file.absPath, 'utf-8').then(content => { if ( content.includes('class-variance-authority') || content.includes('cva(') || content.includes('cx(') ) { return true; } // Check for common cva patterns if (content.includes('import.*cva') || content.includes('import.*cx')) { return true; } }); } catch (error) { // Continue with next file } } return false; } /** * Detect tailwind-variants usage */ function detectTailwindVariants(files: FileInfo[]): boolean { for (const file of files) { try { const content = readFile(file.absPath, 'utf-8').then(content => { if (content.includes('tailwind-variants') || content.includes('tv(')) { return true; } // Check for tv import if (content.includes('import.*tv') || content.includes('from.*tailwind-variants')) { return true; } }); } catch (error) { // Continue with next file } } return false; } /** * Detect CSS Modules usage */ function detectCSSModules(files: FileInfo[]): { hasCSSModules: boolean; moduleFiles: string[] } { let hasCSSModules = false; const moduleFiles: string[] = []; for (const file of files) { if (file.ext === '.module.css' || file.ext === '.module.scss' || file.ext === '.module.sass') { hasCSSModules = true; moduleFiles.push(file.relPath); } // Check for CSS module imports if (file.ext === '.tsx' || file.ext === '.jsx' || file.ext === '.ts' || file.ext === '.js') { try { const content = readFile(file.absPath, 'utf-8').then(content => { if ( content.includes('.module.css') || content.includes('.module.scss') || content.includes('.module.sass') ) { hasCSSModules = true; moduleFiles.push(file.relPath); } }); } catch (error) { // Continue with next file } } } return { hasCSSModules, moduleFiles: [...new Set(moduleFiles)] }; } /** * Detect styled-components usage */ function detectStyledComponents(files: FileInfo[]): boolean { for (const file of files) { try { const content = readFile(file.absPath, 'utf-8').then(content => { if (content.includes('styled-components') || content.includes('styled.')) { return true; } }); } catch (error) { // Continue with next file } } return false; } /** * Detect Emotion usage */ function detectEmotion(files: FileInfo[]): boolean { for (const file of files) { try { const content = readFile(file.absPath, 'utf-8').then(content => { if (content.includes('@emotion/styled') || content.includes('@emotion/react')) { return true; } }); } catch (error) { // Continue with next file } } return false; } /** * Detect global CSS files */ function detectGlobalCSS(files: FileInfo[]): string[] { const globalCSSFiles: string[] = []; for (const file of files) { if (file.ext === '.css' || file.ext === '.scss' || file.ext === '.sass') { // Skip CSS modules if (!file.relPath.includes('.module.')) { globalCSSFiles.push(file.relPath); } } } return globalCSSFiles; } /** * Analyze design system and styling patterns */ export async function analyzeDesignSystem(files: FileInfo[]): Promise<DesignSystemAnalysis> { logger.info(`🎨 Analyzing design system in ${files.length} files`); // Run all detections in parallel const [ tailwindResult, shadcnResult, radixResult, lucideResult, cvaResult, tvResult, cssModulesResult, styledComponentsResult, emotionResult, globalCSSFiles, ] = await Promise.all([ detectTailwind(files), detectShadcnUI(files), detectRadixUI(files), detectLucideIcons(files), detectClassVarianceAuthority(files), detectTailwindVariants(files), detectCSSModules(files), detectStyledComponents(files), detectEmotion(files), detectGlobalCSS(files), ]); // Build design libraries array const designLibraries: string[] = []; if (tailwindResult.hasTailwind) designLibraries.push('Tailwind CSS'); if (shadcnResult.hasShadcnUI) designLibraries.push('shadcn/ui'); if (radixResult.hasRadixUI) designLibraries.push('Radix UI'); if (lucideResult) designLibraries.push('Lucide Icons'); if (cvaResult) designLibraries.push('class-variance-authority'); if (tvResult) designLibraries.push('tailwind-variants'); if (cssModulesResult.hasCSSModules) designLibraries.push('CSS Modules'); if (styledComponentsResult) designLibraries.push('styled-components'); if (emotionResult) designLibraries.push('@emotion/styled'); const analysis: DesignSystemAnalysis = { tailwind: tailwindResult.hasTailwind, shadcnUI: shadcnResult.hasShadcnUI, radixUI: radixResult.hasRadixUI, lucideIcons: lucideResult, classVarianceAuthority: cvaResult, tailwindVariants: tvResult, cssModules: cssModulesResult.hasCSSModules, styledComponents: styledComponentsResult, emotion: emotionResult, globalCSS: globalCSSFiles, designLibraries, }; logger.info(`🎨 Design system analysis complete: ${designLibraries.length} libraries detected`); return analysis; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/sbarron/AmbianceMCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server