learning_ai_common_plat/packages/design-tokens/scripts/generate-react-native.ts
saravanakumardb1 2a0a59e56c feat(design-tokens): add validation tooling and React Native bridge
- validate-tokens.js: Scan source files for hardcoded colors
- token-coverage.js: Report token adoption percentage per product
- generate-react-native.ts: Generator for Expo/NomGap StyleSheet tokens

All scripts include:
- Cross-platform file detection (.ts, .tsx, .swift, .kt)
- Product-specific token categorization
- Exit codes for CI integration
2026-03-03 21:51:56 -08:00

144 lines
6.2 KiB
TypeScript

/**
* React Native token generator for Expo/NomGap
* Reads bytelyst.tokens.json, outputs RN StyleSheet-compatible tokens
*
* Usage: tsx scripts/generate-react-native.ts
*/
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const tokensPath = resolve(__dirname, '../tokens/bytelyst.tokens.json');
const outDir = resolve(__dirname, '../generated/react-native');
mkdirSync(outDir, { recursive: true });
const tokens = JSON.parse(readFileSync(tokensPath, 'utf-8'));
// ── Helpers ───────────────────────────────────────────────────────────
function generateReactNative(): string {
const lines: string[] = [
'/**',
' * React Native Design Tokens — Auto-generated from bytelyst.tokens.json',
' * Do not edit manually. Run: tsx scripts/generate-react-native.ts',
' */',
'',
'export const tokens = {',
'',
' // ── Semantic Colors (Dark Theme) ──────────────────────────────────',
' colors: {',
];
// Semantic dark colors
for (const [key, value] of Object.entries(tokens.color.semantic.dark)) {
if (typeof value === 'string' && value.startsWith('#')) {
lines.push(` ${key}: '${value}',`);
} else if (typeof value === 'string' && value.startsWith('rgba')) {
// Convert rgba to hex8 for React Native
const match = value.match(/rgba?\((\d+),\s*(\d+),\s*(\d+),\s*([\d.]+)\)/);
if (match) {
const [, r, g, b, a] = match;
const alpha = Math.round(parseFloat(a) * 255)
.toString(16)
.padStart(2, '0');
const hex =
`#${alpha}${parseInt(r).toString(16).padStart(2, '0')}${parseInt(g).toString(16).padStart(2, '0')}${parseInt(b).toString(16).padStart(2, '0')}`.toUpperCase();
lines.push(` ${key}: '${hex}',`);
}
}
}
lines.push(' },', '');
// NomGap-specific colors
lines.push(' // ── NomGap Product Colors ──────────────────────────────────────────');
lines.push(' nomgap: {');
for (const [key, value] of Object.entries(tokens.color.nomgap)) {
lines.push(` ${key}: '${value}',`);
}
lines.push(' },', '');
// Spacing
lines.push(' // ── Spacing (8pt grid) ──────────────────────────────────────────────');
lines.push(' spacing: {');
for (const [key, value] of Object.entries(tokens.spacing)) {
lines.push(` ${key}: ${value},`);
}
lines.push(' },', '');
// Radius
lines.push(' // ── Border Radius ───────────────────────────────────────────────────');
lines.push(' radius: {');
for (const [key, value] of Object.entries(tokens.radius)) {
lines.push(` ${key}: ${value},`);
}
lines.push(' },', '');
// Typography
lines.push(' // ── Typography ─────────────────────────────────────────────────────');
lines.push(' typography: {');
lines.push(' fontFamily: {');
for (const key of Object.keys(tokens.typography.fontFamily)) {
// Use system fonts for React Native
const systemFont = key === 'display' ? 'System' : key === 'mono' ? 'Courier' : 'System';
lines.push(` ${key}: '${systemFont}',`);
}
lines.push(' },');
lines.push(' fontSize: {');
for (const [key, value] of Object.entries(tokens.typography.fontSize)) {
lines.push(` ${key}: ${value},`);
}
lines.push(' },');
lines.push(' fontWeight: {');
for (const [key, value] of Object.entries(tokens.typography.fontWeight)) {
lines.push(` ${key}: '${value}',`);
}
lines.push(' },');
lines.push(' },', '');
// Icon sizes
lines.push(' // ── Icon Sizes ──────────────────────────────────────────────────────');
lines.push(' icon: {');
for (const [key, value] of Object.entries(tokens.icon)) {
lines.push(` ${key}: ${value},`);
}
lines.push(' },', '');
// Z-index (for RN zIndex style prop)
lines.push(' // ── Z-Index Layers ───────────────────────────────────────────────────');
lines.push(' zIndex: {');
for (const [key, value] of Object.entries(tokens.zIndex)) {
lines.push(` ${key}: ${value},`);
}
lines.push(' },', '');
// Opacity
lines.push(' // ── Opacity ─────────────────────────────────────────────────────────');
lines.push(' opacity: {');
for (const [key, value] of Object.entries(tokens.opacity)) {
lines.push(` ${key}: ${value},`);
}
lines.push(' },', '');
// Motion (duration in ms)
lines.push(' // ── Motion ────────────────────────────────────────────────────────────');
lines.push(' motion: {');
for (const [key, value] of Object.entries(tokens.motion.duration)) {
lines.push(` ${key}: ${value},`);
}
lines.push(' },', '');
lines.push('} as const;', '');
lines.push('');
lines.push('export type Tokens = typeof tokens;', '');
return lines.join('\n');
}
// ── Write ──────────────────────────────────────────────────────────
writeFileSync(resolve(outDir, 'tokens.ts'), generateReactNative());
// eslint-disable-next-line no-console
console.log('Generated React Native tokens in generated/react-native/');