#!/usr/bin/env node /** * Token coverage report — analyzes how well a product uses design tokens * * Usage: node scripts/token-coverage.js */ const { readFileSync, readdirSync, statSync } = require('fs'); const { join, resolve } = require('path'); const EXCLUDED_DIRS = ['node_modules', 'dist', 'build', '.git', 'generated', '__mocks__']; const INCLUDED_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx', '.swift', '.kt']; const TOKEN_PATTERNS = { web: { cssVar: /--ml-[a-z-]+/g, tokensImport: /@bytelyst\/design-tokens/, }, ios: { mindLystColors: /MindLystColors\./g, colorExtension: /Color\(hex:/g, }, kmp: { mindLystTokens: /MindLystTokens\./g, }, }; function findFiles(dir, files = []) { try { const items = readdirSync(dir); for (const item of items) { const fullPath = join(dir, item); if (EXCLUDED_DIRS.some(ex => fullPath.includes(ex))) continue; const stat = statSync(fullPath); if (stat.isDirectory()) { findFiles(fullPath, files); } else if (INCLUDED_EXTENSIONS.some(ext => item.endsWith(ext))) { files.push(fullPath); } } } catch (e) { // Directory might not exist } return files; } function detectPlatform(files) { const hasSwift = files.some(f => f.endsWith('.swift')); const hasKotlin = files.some(f => f.endsWith('.kt')); const hasTSX = files.some(f => f.endsWith('.tsx')); if (hasSwift) return 'ios'; if (hasKotlin) return 'kmp'; if (hasTSX) return 'web'; return 'unknown'; } function analyzeCoverage(files, platform) { let tokenUsages = 0; let hardcodedColors = 0; let filesUsingTokens = 0; let filesWithHardcoded = 0; const patterns = TOKEN_PATTERNS[platform] || {}; for (const file of files) { const content = readFileSync(file, 'utf-8'); let hasTokens = false; let hasHardcoded = false; // Check token usage if (patterns.cssVar) { const matches = content.match(patterns.cssVar); if (matches) { tokenUsages += matches.length; hasTokens = true; } } if (patterns.mindLystColors) { const matches = content.match(patterns.mindLystColors); if (matches) { tokenUsages += matches.length; hasTokens = true; } } if (patterns.mindLystTokens) { const matches = content.match(patterns.mindLystTokens); if (matches) { tokenUsages += matches.length; hasTokens = true; } } // Check hardcoded colors const colorMatches = content.match(/#[0-9A-Fa-f]{6}\b/g); if (colorMatches) { // Filter out likely non-color hex (like #FFFFFF in different contexts) const likelyColors = colorMatches.filter(c => { const hex = c.slice(1); // Skip pure grays that might be intentional if (hex[0] === hex[2] && hex[2] === hex[4]) return false; return true; }); if (likelyColors.length > 0) { hardcodedColors += likelyColors.length; hasHardcoded = true; } } if (hasTokens) filesUsingTokens++; if (hasHardcoded) filesWithHardcoded++; } return { tokenUsages, hardcodedColors, filesUsingTokens, filesWithHardcoded, totalFiles: files.length, }; } function main() { const targetPath = process.argv[2]; if (!targetPath) { console.log('Usage: node scripts/token-coverage.js '); console.log(''); console.log('Examples:'); console.log(' node scripts/token-coverage.js ../../mindlyst-native/iosApp'); console.log(' node scripts/token-coverage.js ../../learning_ai_clock/web/src'); process.exit(1); } const absolutePath = resolve(targetPath); console.log(`šŸ“Š Analyzing token coverage for ${absolutePath}\n`); const files = findFiles(absolutePath); const platform = detectPlatform(files); console.log(`Detected platform: ${platform}`); console.log(`Total files: ${files.length}\n`); if (files.length === 0) { console.log('āŒ No source files found'); process.exit(1); } const coverage = analyzeCoverage(files, platform); console.log(`${'='.repeat(50)}`); console.log('šŸ“ˆ Coverage Report'); console.log(`${'='.repeat(50)}`); console.log( `Files using tokens: ${coverage.filesUsingTokens}/${coverage.totalFiles} (${((coverage.filesUsingTokens / coverage.totalFiles) * 100).toFixed(1)}%)` ); console.log( `Files with hardcoded: ${coverage.filesWithHardcoded}/${coverage.totalFiles} (${((coverage.filesWithHardcoded / coverage.totalFiles) * 100).toFixed(1)}%)` ); console.log(`Token usages: ${coverage.tokenUsages}`); console.log(`Hardcoded colors: ${coverage.hardcodedColors}`); console.log(`${'='.repeat(50)}`); const tokenRatio = coverage.tokenUsages + coverage.hardcodedColors; const tokenPercentage = tokenRatio > 0 ? ((coverage.tokenUsages / tokenRatio) * 100).toFixed(1) : 'N/A'; console.log(`\nšŸŽÆ Token Adoption: ${tokenPercentage}%`); if (coverage.hardcodedColors > 0) { console.log(`\nāš ļø Found ${coverage.hardcodedColors} hardcoded colors.`); console.log(' Run validate-tokens.js for details.'); } if (coverage.filesUsingTokens === 0) { console.log('\nāŒ No token usage detected. Product needs token integration.'); process.exit(1); } console.log('\nāœ… Coverage analysis complete.'); } main();