learning_ai_common_plat/packages/design-tokens/scripts/token-coverage.cjs
2026-03-04 19:54:40 -08:00

185 lines
5.3 KiB
JavaScript
Executable File

#!/usr/bin/env node
/**
* Token coverage report — analyzes how well a product uses design tokens
*
* Usage: node scripts/token-coverage.js <product-path>
*/
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 <product-path>');
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();