// Generates static TypeScript files with pre-computed theme constants. // These values are string literals with no runtime imports, so wyw-in-js // can evaluate them in its restricted sandbox without triggering the // twenty-ui dist bundle's dependency chain (safe-regex-test / get-intrinsic). // // Usage (from workspace root): // npx tsx packages/twenty-ui/scripts/generateThemeConstants.ts // // Prerequisites: twenty-ui must be built first (npx nx build twenty-ui). import { createRequire } from 'node:module'; import { writeFileSync, mkdirSync } from 'node:fs'; import { dirname, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; const scriptDir = dirname(fileURLToPath(import.meta.url)); const require = createRequire(import.meta.url); const { MOBILE_VIEWPORT, THEME_LIGHT, THEME_DARK, prepareThemeForRootCssVariableInjection, } = require('../dist/theme.cjs'); const { themeCssVariables: existingThemeCssVariables } = require('../dist/theme-constants.cjs'); const themeCssVariables = existingThemeCssVariables ?? (() => { const { buildThemeReferencingRootCssVariables } = require('../dist/theme.cjs'); return buildThemeReferencingRootCssVariables({ themeNode: THEME_LIGHT, prefix: 't', }); })(); const HEADER = `\ // Auto-generated by scripts/generateThemeConstants.ts — do not edit manually. // Regenerate: npx tsx packages/twenty-ui/scripts/generateThemeConstants.ts `; const serializeObject = ( obj: Record, indent = 2, ): string => { const spaces = ' '.repeat(indent); const entries = Object.entries(obj).map(([key, value]) => { const safeKey = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) ? key : JSON.stringify(key); if (typeof value === 'object' && value !== null) { return `${spaces}${safeKey}: ${serializeObject(value as Record, indent + 2)},`; } return `${spaces}${safeKey}: ${JSON.stringify(value)},`; }); return `{\n${entries.join('\n')}\n${' '.repeat(indent - 2)}}`; }; const serializeTupleArray = (entries: [string, string][]): string => { const lines = entries.map( ([name, value]) => ` [${JSON.stringify(name)}, ${JSON.stringify(value)}],`, ); return `[\n${lines.join('\n')}\n]`; }; const outputDir = resolve(scriptDir, '../src/theme-constants/generated'); mkdirSync(outputDir, { recursive: true }); // --- themeCssVariables.ts --- writeFileSync( resolve(outputDir, 'themeCssVariables.ts'), `${HEADER} import type { ThemeType } from '@ui/theme/types/ThemeType'; type DeepCSSVariableRefs = { [K in keyof T]: T[K] extends (...args: never[]) => unknown ? Record : T[K] extends Record ? DeepCSSVariableRefs : string; }; export const themeCssVariables = ${serializeObject(themeCssVariables)} as DeepCSSVariableRefs; `, 'utf-8', ); console.log('Generated themeCssVariables.ts'); // --- themeLightCssVariableEntries.ts --- const lightEntries = prepareThemeForRootCssVariableInjection({ themeNode: THEME_LIGHT, prefix: 't', }) as [string, string][]; writeFileSync( resolve(outputDir, 'themeLightCssVariableEntries.ts'), `${HEADER} // CSS custom properties don't work in media queries, so MOBILE_VIEWPORT // must be a static number rather than a var(--...) reference. export const MOBILE_VIEWPORT = ${MOBILE_VIEWPORT}; export const THEME_LIGHT_CSS_VARIABLE_ENTRIES: [string, string][] = ${serializeTupleArray(lightEntries)}; `, 'utf-8', ); console.log('Generated themeLightCssVariableEntries.ts'); // --- themeDarkCssVariableEntries.ts --- const darkEntries = prepareThemeForRootCssVariableInjection({ themeNode: THEME_DARK, prefix: 't', }) as [string, string][]; writeFileSync( resolve(outputDir, 'themeDarkCssVariableEntries.ts'), `${HEADER} export const THEME_DARK_CSS_VARIABLE_ENTRIES: [string, string][] = ${serializeTupleArray(darkEntries)}; `, 'utf-8', ); console.log('Generated themeDarkCssVariableEntries.ts');