@ethlete/core 4.31.0 → 5.0.0-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,490 @@
1
+ import { formatFiles, logger, visitNotIgnoredFiles } from '@nx/devkit';
2
+ import { Project, SyntaxKind } from 'ts-morph';
3
+ export default async function generate(tree, schema) {
4
+ logger.log('\nšŸ”„ Starting Tailwind 4 theme generator...\n');
5
+ const themesPath = schema.themesPath || 'src/themes.ts';
6
+ const outputPath = schema.outputPath || 'src/styles/generated-tailwind-themes.css';
7
+ const prefix = schema.prefix || 'et';
8
+ // Step 1: Check if themes file exists
9
+ if (!tree.exists(themesPath)) {
10
+ logger.error(`āŒ Themes file not found at: ${themesPath}`);
11
+ logger.log(`\nPlease specify the correct path using --themesPath option.`);
12
+ logger.log(`Example: nx g @ethlete/core:tailwind-4-theme --themesPath=src/app/themes.ts\n`);
13
+ return;
14
+ }
15
+ logger.log(`šŸ“ Reading themes from: ${themesPath}`);
16
+ // Step 2: Read and parse themes file
17
+ const themesContent = tree.read(themesPath, 'utf-8');
18
+ if (!themesContent) {
19
+ logger.error('āŒ Failed to read themes file');
20
+ return;
21
+ }
22
+ // Step 3: Try to extract themes using TypeScript
23
+ let themes;
24
+ try {
25
+ themes = extractThemesFromContent(themesContent, themesPath);
26
+ logger.log(`āœ… Found ${themes.length} theme(s)`);
27
+ }
28
+ catch (error) {
29
+ logger.error('āŒ Failed to parse themes file');
30
+ logger.error(` ${error instanceof Error ? error.message : String(error)}`);
31
+ logger.log('\nThe themes file must export themes as:');
32
+ logger.log(' export const THEMES = [...] satisfies Theme[];\n');
33
+ return;
34
+ }
35
+ // Step 4: Validate theme configuration
36
+ try {
37
+ validateThemeConfiguration(themes);
38
+ }
39
+ catch (error) {
40
+ logger.error('āŒ Theme configuration error');
41
+ logger.error(` ${error instanceof Error ? error.message : String(error)}`);
42
+ return;
43
+ }
44
+ // Step 5: Generate Tailwind CSS
45
+ logger.log('\nšŸŽØ Generating Tailwind theme CSS...');
46
+ const css = generateTailwindThemeCss(themes, prefix, schema);
47
+ // Step 6: Write the generated CSS file
48
+ const outputDir = outputPath.substring(0, outputPath.lastIndexOf('/'));
49
+ if (outputDir && !tree.exists(outputDir)) {
50
+ logger.log(`šŸ“ Creating directory: ${outputDir}`);
51
+ }
52
+ tree.write(outputPath, css);
53
+ logger.log(`āœ… Generated Tailwind themes at: ${outputPath}`);
54
+ // Step 7: Try to find and update main styles file
55
+ const mainStylesFiles = findMainStylesFile(tree);
56
+ if (mainStylesFiles.length > 0) {
57
+ logger.log('\nšŸ“ Found potential main styles files:');
58
+ mainStylesFiles.forEach((file) => logger.log(` - ${file}`));
59
+ logger.log('\nāš ļø Please manually import the generated themes:');
60
+ logger.log(` @import './${outputPath.replace('src/styles/', '')}';`);
61
+ }
62
+ if (!schema.skipFormat) {
63
+ await formatFiles(tree);
64
+ }
65
+ logger.log('\nāœ… Generation completed successfully!\n');
66
+ }
67
+ //#endregion
68
+ //#region Helper Functions
69
+ function extractThemesFromContent(content, filePath) {
70
+ // Create an in-memory TypeScript project
71
+ const project = new Project({
72
+ useInMemoryFileSystem: true,
73
+ compilerOptions: {
74
+ target: 99, // ESNext
75
+ module: 99, // ESNext
76
+ },
77
+ });
78
+ // Add the source file
79
+ const sourceFile = project.createSourceFile(filePath, content);
80
+ const themes = [];
81
+ // Find all exported const declarations
82
+ const exportedDeclarations = sourceFile.getVariableDeclarations().filter((decl) => {
83
+ const statement = decl.getVariableStatement();
84
+ return statement?.isExported();
85
+ });
86
+ // Look for the THEMES or themes array
87
+ const themesArray = exportedDeclarations.find((decl) => {
88
+ const name = decl.getName();
89
+ return name === 'THEMES' || name === 'themes';
90
+ });
91
+ if (!themesArray) {
92
+ throw new Error('Could not find THEMES or themes export');
93
+ }
94
+ let initializer = themesArray.getInitializer();
95
+ if (!initializer) {
96
+ throw new Error('THEMES export has no initializer');
97
+ }
98
+ // Handle 'satisfies' expression: [array] satisfies Type[]
99
+ if (initializer.isKind(SyntaxKind.SatisfiesExpression)) {
100
+ initializer = initializer.getExpression();
101
+ }
102
+ // Handle 'as const': [array] as const
103
+ if (initializer.isKind(SyntaxKind.AsExpression)) {
104
+ initializer = initializer.getExpression();
105
+ }
106
+ if (!initializer.isKind(SyntaxKind.ArrayLiteralExpression)) {
107
+ throw new Error('THEMES export must be an array literal');
108
+ }
109
+ // Get the array elements (these are references to the theme const declarations)
110
+ const elements = initializer.getElements();
111
+ for (const element of elements) {
112
+ // Resolve the identifier to its declaration
113
+ if (element.isKind(SyntaxKind.Identifier)) {
114
+ const name = element.getText();
115
+ const themeDecl = exportedDeclarations.find((decl) => decl.getName() === name);
116
+ if (!themeDecl) {
117
+ logger.warn(`āš ļø Could not find declaration for theme: ${name}`);
118
+ continue;
119
+ }
120
+ let themeObj = themeDecl.getInitializer();
121
+ if (!themeObj) {
122
+ logger.warn(`āš ļø Theme ${name} has no initializer`);
123
+ continue;
124
+ }
125
+ // Handle 'as const' on individual theme objects
126
+ if (themeObj.isKind(SyntaxKind.AsExpression)) {
127
+ themeObj = themeObj.getExpression();
128
+ }
129
+ if (!themeObj.isKind(SyntaxKind.ObjectLiteralExpression)) {
130
+ logger.warn(`āš ļø Theme ${name} is not an object literal`);
131
+ continue;
132
+ }
133
+ try {
134
+ const theme = parseThemeObject(themeObj, sourceFile);
135
+ themes.push(theme);
136
+ }
137
+ catch (error) {
138
+ logger.warn(`āš ļø Failed to parse theme ${name}: ${error instanceof Error ? error.message : String(error)}`);
139
+ }
140
+ }
141
+ }
142
+ if (themes.length === 0) {
143
+ throw new Error('No valid themes found in THEMES array');
144
+ }
145
+ return themes;
146
+ }
147
+ function parseThemeObject(obj, sourceFile) {
148
+ const properties = obj.getProperties();
149
+ const theme = {};
150
+ for (const prop of properties) {
151
+ if (!prop.isKind(SyntaxKind.PropertyAssignment)) {
152
+ continue;
153
+ }
154
+ const propName = prop.getName();
155
+ const initializer = prop.getInitializer();
156
+ if (!initializer) {
157
+ continue;
158
+ }
159
+ switch (propName) {
160
+ case 'name':
161
+ if (initializer.isKind(SyntaxKind.StringLiteral)) {
162
+ theme.name = initializer.getLiteralValue();
163
+ }
164
+ break;
165
+ case 'isDefault':
166
+ if (initializer.isKind(SyntaxKind.TrueKeyword)) {
167
+ theme.isDefault = true;
168
+ }
169
+ break;
170
+ case 'isDefaultAlt':
171
+ if (initializer.isKind(SyntaxKind.TrueKeyword)) {
172
+ theme.isDefaultAlt = true;
173
+ }
174
+ break;
175
+ case 'primary':
176
+ case 'secondary':
177
+ case 'tertiary':
178
+ if (initializer.isKind(SyntaxKind.ObjectLiteralExpression)) {
179
+ theme[propName] = parseThemeSwatch(initializer, sourceFile);
180
+ }
181
+ break;
182
+ }
183
+ }
184
+ if (!theme.name || !theme.primary) {
185
+ throw new Error('Theme must have name and primary properties');
186
+ }
187
+ return theme;
188
+ }
189
+ function validateThemeConfiguration(themes) {
190
+ const defaultThemes = themes.filter((t) => t.isDefault);
191
+ const defaultAltThemes = themes.filter((t) => t.isDefaultAlt);
192
+ const bothDefaultAndAlt = themes.filter((t) => t.isDefault && t.isDefaultAlt);
193
+ // Error: No default theme
194
+ if (defaultThemes.length === 0) {
195
+ throw new Error('No default theme found. At least one theme must have isDefault: true');
196
+ }
197
+ // Error: Multiple default themes
198
+ if (defaultThemes.length > 1) {
199
+ throw new Error(`Multiple default themes found: ${defaultThemes.map((t) => t.name).join(', ')}. Only one theme can have isDefault: true`);
200
+ }
201
+ // Error: Theme has both isDefault and isDefaultAlt
202
+ if (bothDefaultAndAlt.length > 0) {
203
+ throw new Error(`Theme "${bothDefaultAndAlt[0].name}" has both isDefault and isDefaultAlt set to true. A theme can only be one or the other`);
204
+ }
205
+ // Error: Multiple default alt themes
206
+ if (defaultAltThemes.length > 1) {
207
+ throw new Error(`Multiple default alt themes found: ${defaultAltThemes.map((t) => t.name).join(', ')}. Only one theme can have isDefaultAlt: true`);
208
+ }
209
+ }
210
+ function parseThemeSwatch(obj, sourceFile) {
211
+ const properties = obj.getProperties();
212
+ const swatch = {};
213
+ for (const prop of properties) {
214
+ if (!prop.isKind(SyntaxKind.PropertyAssignment)) {
215
+ continue;
216
+ }
217
+ const propName = prop.getName();
218
+ const initializer = prop.getInitializer();
219
+ if (!initializer) {
220
+ continue;
221
+ }
222
+ if (propName === 'color' || propName === 'onColor') {
223
+ const colorMap = parseColorMap(initializer, sourceFile);
224
+ if (colorMap) {
225
+ swatch[propName] = colorMap;
226
+ }
227
+ }
228
+ }
229
+ if (!swatch.color || !swatch.onColor) {
230
+ throw new Error('ThemeSwatch must have color and onColor properties');
231
+ }
232
+ return swatch;
233
+ }
234
+ function parseColorMap(initializer, sourceFile) {
235
+ // Handle spread expressions by resolving references
236
+ if (initializer.isKind(SyntaxKind.ObjectLiteralExpression)) {
237
+ const colorMap = {};
238
+ const properties = initializer.getProperties();
239
+ for (const prop of properties) {
240
+ if (prop.isKind(SyntaxKind.PropertyAssignment)) {
241
+ const propName = prop.getName();
242
+ const propValue = prop.getInitializer();
243
+ if (propValue?.isKind(SyntaxKind.StringLiteral)) {
244
+ colorMap[propName] = propValue.getLiteralValue();
245
+ }
246
+ }
247
+ else if (prop.isKind(SyntaxKind.SpreadAssignment)) {
248
+ // Handle spread: { ...onColorDark, disabled: '...' }
249
+ const spreadExpr = prop.getExpression();
250
+ if (spreadExpr.isKind(SyntaxKind.Identifier)) {
251
+ const referencedName = spreadExpr.getText();
252
+ const referencedDecl = sourceFile
253
+ .getVariableDeclarations()
254
+ .find((decl) => decl.getName() === referencedName);
255
+ if (referencedDecl) {
256
+ const referencedObj = referencedDecl.getInitializer();
257
+ if (referencedObj?.isKind(SyntaxKind.ObjectLiteralExpression)) {
258
+ const spreadColors = parseColorMap(referencedObj, sourceFile);
259
+ if (spreadColors) {
260
+ Object.assign(colorMap, spreadColors);
261
+ }
262
+ }
263
+ }
264
+ }
265
+ }
266
+ }
267
+ // Apply fallbacks for required fields
268
+ if (colorMap.default) {
269
+ // Check if this is a ThemeColorMap (has hover, active, or disabled explicitly set)
270
+ const isThemeColorMap = colorMap.hover !== undefined || colorMap.active !== undefined || colorMap.disabled !== undefined;
271
+ if (isThemeColorMap) {
272
+ // For ThemeColorMap - apply fallbacks for all required fields
273
+ const defaultColor = colorMap.default;
274
+ const hoverColor = (colorMap.hover || defaultColor);
275
+ const result = {
276
+ default: defaultColor,
277
+ hover: hoverColor,
278
+ focus: colorMap.focus,
279
+ active: (colorMap.active || hoverColor),
280
+ disabled: (colorMap.disabled || defaultColor),
281
+ };
282
+ return result;
283
+ }
284
+ // For OnThemeColorMap - no fallbacks needed, all fields are optional except default
285
+ return colorMap;
286
+ }
287
+ return null;
288
+ }
289
+ return null;
290
+ }
291
+ function createCssThemeName(name) {
292
+ // Convert theme name to CSS-safe format (e.g., "Primary Blue" -> "primary-blue")
293
+ return name
294
+ .toLowerCase()
295
+ .replace(/\s+/g, '-')
296
+ .replace(/[^a-z0-9-]/g, '');
297
+ }
298
+ function generateTailwindThemeCss(themes, prefix, schema) {
299
+ const tailwindVars = [];
300
+ const themeVars = [];
301
+ const themesPath = schema.themesPath || 'src/themes.ts';
302
+ const outputPath = schema.outputPath || 'src/styles/generated-tailwind-themes.css';
303
+ const header = `/*
304
+ * Auto-generated Tailwind 4 theme colors from @ethlete/core
305
+ * DO NOT EDIT THIS FILE MANUALLY
306
+ *
307
+ * Generated from your theme definitions
308
+ * This file can be regenerated by running:
309
+ * nx g @ethlete/core:tailwind-4-theme --themesPath=${themesPath}${schema.outputPath ? ` --outputPath=${outputPath}` : ''}${schema.prefix && schema.prefix !== 'et' ? ` --prefix=${schema.prefix}` : ''}
310
+ */
311
+
312
+ `;
313
+ // Validation is now done separately before this function is called
314
+ const defaultThemes = themes.filter((t) => t.isDefault);
315
+ const defaultAltThemes = themes.filter((t) => t.isDefaultAlt);
316
+ const regularThemes = themes.filter((t) => !t.isDefault && !t.isDefaultAlt);
317
+ // Generate static Tailwind @theme block for each theme
318
+ for (const theme of themes) {
319
+ const name = createCssThemeName(theme.name);
320
+ // Add comment for theme section
321
+ tailwindVars.push(` /* ${theme.name} theme */`);
322
+ // Primary colors for Tailwind utilities
323
+ addTailwindColorVariants(tailwindVars, `${prefix}-${name}`, theme.primary.color);
324
+ tailwindVars.push('');
325
+ // On colors for Tailwind utilities
326
+ addTailwindColorVariants(tailwindVars, `${prefix}-on-${name}`, theme.primary.onColor);
327
+ tailwindVars.push('');
328
+ // Secondary colors if present
329
+ if (theme.secondary) {
330
+ addTailwindColorVariants(tailwindVars, `${prefix}-${name}-secondary`, theme.secondary.color);
331
+ tailwindVars.push('');
332
+ addTailwindColorVariants(tailwindVars, `${prefix}-on-${name}-secondary`, theme.secondary.onColor);
333
+ tailwindVars.push('');
334
+ }
335
+ // Tertiary colors if present
336
+ if (theme.tertiary) {
337
+ addTailwindColorVariants(tailwindVars, `${prefix}-${name}-tertiary`, theme.tertiary.color);
338
+ tailwindVars.push('');
339
+ addTailwindColorVariants(tailwindVars, `${prefix}-on-${name}-tertiary`, theme.tertiary.onColor);
340
+ tailwindVars.push('');
341
+ }
342
+ }
343
+ tailwindVars.push('');
344
+ // Generate dynamic theme variables - check main themes and alt themes separately
345
+ const mainThemes = [...defaultThemes, ...regularThemes];
346
+ const hasSecondary = mainThemes.some((t) => t.secondary);
347
+ const hasTertiary = mainThemes.some((t) => t.tertiary);
348
+ const hasAltSecondary = defaultAltThemes.some((t) => t.secondary);
349
+ const hasAltTertiary = defaultAltThemes.some((t) => t.tertiary);
350
+ // Main theme dynamic colors
351
+ tailwindVars.push(' /* Dynamic theme colors (references runtime CSS variables) */');
352
+ addDynamicThemeColors(tailwindVars, prefix, 'theme', 'primary', true);
353
+ if (hasSecondary) {
354
+ addDynamicThemeColors(tailwindVars, prefix, 'theme-secondary', 'secondary', false);
355
+ }
356
+ if (hasTertiary) {
357
+ addDynamicThemeColors(tailwindVars, prefix, 'theme-tertiary', 'tertiary', false);
358
+ }
359
+ // Alt theme dynamic colors
360
+ tailwindVars.push(' /* Alt theme dynamic colors */');
361
+ addDynamicThemeColors(tailwindVars, prefix, 'alt-theme', 'alt-primary', true);
362
+ if (hasAltSecondary) {
363
+ addDynamicThemeColors(tailwindVars, prefix, 'alt-theme-secondary', 'alt-secondary', false);
364
+ }
365
+ if (hasAltTertiary) {
366
+ addDynamicThemeColors(tailwindVars, prefix, 'alt-theme-tertiary', 'alt-tertiary', false);
367
+ }
368
+ // Generate runtime CSS for ALL themes (both main and alt variants)
369
+ themes.forEach((theme) => {
370
+ const name = createCssThemeName(theme.name);
371
+ // Determine if this is the default or default-alt theme
372
+ const isDefault = theme.isDefault;
373
+ const isDefaultAlt = theme.isDefaultAlt;
374
+ // Generate main theme variant (.et-theme--{name})
375
+ if (isDefault) {
376
+ // Default theme gets :root and .et-theme--default selectors
377
+ const selectors = [':root', `.${prefix}-theme--default`, `.${prefix}-theme--${name}`];
378
+ themeVars.push(`${selectors.join(', ')} {`);
379
+ }
380
+ else if (!isDefaultAlt) {
381
+ // Regular themes just get their own class
382
+ themeVars.push(`.${prefix}-theme--${name} {`);
383
+ }
384
+ if (isDefault || !isDefaultAlt) {
385
+ addThemeColorVariants(themeVars, prefix, '', theme);
386
+ themeVars.push('}\n');
387
+ }
388
+ // Generate alt theme variant (.et-theme-alt--{name})
389
+ if (isDefaultAlt) {
390
+ // Default alt theme gets :root and .et-theme-alt--default selectors
391
+ const selectors = [':root', `.${prefix}-theme-alt--default`, `.${prefix}-theme-alt--${name}`];
392
+ themeVars.push(`${selectors.join(', ')} {`);
393
+ }
394
+ else {
395
+ // All themes (including default and regular) get an alt variant
396
+ themeVars.push(`.${prefix}-theme-alt--${name} {`);
397
+ }
398
+ addThemeColorVariants(themeVars, prefix, 'alt-', theme);
399
+ themeVars.push('}\n');
400
+ });
401
+ return `${header}@theme {
402
+ ${tailwindVars.join('\n')}
403
+ }
404
+
405
+ ${themeVars.join('\n')}`;
406
+ }
407
+ function addDynamicThemeColors(vars, prefix, tailwindName, cssVarName, addSpacingBefore) {
408
+ if (addSpacingBefore && vars.length > 0 && vars[vars.length - 1] !== '') {
409
+ vars.push('');
410
+ }
411
+ // Color variants
412
+ vars.push(` --color-${prefix}-${tailwindName}: rgb(var(--${prefix}-color-${cssVarName}));`);
413
+ vars.push(` --color-${prefix}-${tailwindName}-hover: rgb(var(--${prefix}-color-${cssVarName}-hover));`);
414
+ vars.push(` --color-${prefix}-${tailwindName}-focus: rgb(var(--${prefix}-color-${cssVarName}-focus));`);
415
+ vars.push(` --color-${prefix}-${tailwindName}-active: rgb(var(--${prefix}-color-${cssVarName}-active));`);
416
+ vars.push(` --color-${prefix}-${tailwindName}-disabled: rgb(var(--${prefix}-color-${cssVarName}-disabled));`);
417
+ vars.push('');
418
+ // On-color variants
419
+ vars.push(` --color-${prefix}-on-${tailwindName}: rgb(var(--${prefix}-color-on-${cssVarName}));`);
420
+ vars.push(` --color-${prefix}-on-${tailwindName}-hover: rgb(var(--${prefix}-color-on-${cssVarName}-hover));`);
421
+ vars.push(` --color-${prefix}-on-${tailwindName}-focus: rgb(var(--${prefix}-color-on-${cssVarName}-focus));`);
422
+ vars.push(` --color-${prefix}-on-${tailwindName}-active: rgb(var(--${prefix}-color-on-${cssVarName}-active));`);
423
+ vars.push(` --color-${prefix}-on-${tailwindName}-disabled: rgb(var(--${prefix}-color-on-${cssVarName}-disabled));`);
424
+ vars.push('');
425
+ }
426
+ function addTailwindColorVariants(vars, colorName, colorSet) {
427
+ // Tailwind 4 requires --color-* prefix and rgb() wrapper
428
+ // Always generate all variants with fallbacks
429
+ vars.push(` --color-${colorName}: rgb(${colorSet.default});`);
430
+ // For hover: use hover if exists, otherwise default
431
+ const hoverValue = 'hover' in colorSet && colorSet.hover ? colorSet.hover : colorSet.default;
432
+ vars.push(` --color-${colorName}-hover: rgb(${hoverValue});`);
433
+ // For focus: use focus if exists, otherwise hover, otherwise default
434
+ const focusValue = 'focus' in colorSet && colorSet.focus ? colorSet.focus : hoverValue;
435
+ vars.push(` --color-${colorName}-focus: rgb(${focusValue});`);
436
+ // For active: use active if exists, otherwise hover, otherwise default
437
+ const activeValue = 'active' in colorSet && colorSet.active ? colorSet.active : hoverValue;
438
+ vars.push(` --color-${colorName}-active: rgb(${activeValue});`);
439
+ // For disabled: use disabled if exists, otherwise default
440
+ const disabledValue = 'disabled' in colorSet && colorSet.disabled ? colorSet.disabled : colorSet.default;
441
+ vars.push(` --color-${colorName}-disabled: rgb(${disabledValue});`);
442
+ }
443
+ function addThemeColorVariants(vars, prefix, altPrefix, theme) {
444
+ const addSwatch = (level, swatch) => {
445
+ // Color variants with fallbacks
446
+ const defaultColor = swatch.color.default;
447
+ const hoverColor = swatch.color.hover || defaultColor;
448
+ const focusColor = swatch.color.focus || hoverColor;
449
+ const activeColor = swatch.color.active || hoverColor;
450
+ const disabledColor = swatch.color.disabled || defaultColor;
451
+ vars.push(` --${prefix}-color-${altPrefix}${level}: ${defaultColor};`);
452
+ vars.push(` --${prefix}-color-${altPrefix}${level}-hover: ${hoverColor};`);
453
+ vars.push(` --${prefix}-color-${altPrefix}${level}-focus: ${focusColor};`);
454
+ vars.push(` --${prefix}-color-${altPrefix}${level}-active: ${activeColor};`);
455
+ vars.push(` --${prefix}-color-${altPrefix}${level}-disabled: ${disabledColor};`);
456
+ vars.push('');
457
+ // On color variants with fallbacks
458
+ const onDefaultColor = swatch.onColor.default;
459
+ const onHoverColor = swatch.onColor.hover || onDefaultColor;
460
+ const onFocusColor = swatch.onColor.focus || onHoverColor;
461
+ const onActiveColor = swatch.onColor.active || onDefaultColor;
462
+ const onDisabledColor = swatch.onColor.disabled || onDefaultColor;
463
+ vars.push(` --${prefix}-color-${altPrefix}on-${level}: ${onDefaultColor};`);
464
+ vars.push(` --${prefix}-color-${altPrefix}on-${level}-hover: ${onHoverColor};`);
465
+ vars.push(` --${prefix}-color-${altPrefix}on-${level}-focus: ${onFocusColor};`);
466
+ vars.push(` --${prefix}-color-${altPrefix}on-${level}-active: ${onActiveColor};`);
467
+ vars.push(` --${prefix}-color-${altPrefix}on-${level}-disabled: ${onDisabledColor};`);
468
+ if (theme.secondary || theme.tertiary) {
469
+ vars.push('');
470
+ }
471
+ };
472
+ addSwatch('primary', theme.primary);
473
+ if (theme.secondary) {
474
+ addSwatch('secondary', theme.secondary);
475
+ }
476
+ if (theme.tertiary) {
477
+ addSwatch('tertiary', theme.tertiary);
478
+ }
479
+ }
480
+ function findMainStylesFile(tree) {
481
+ const potentialFiles = [];
482
+ visitNotIgnoredFiles(tree, '', (path) => {
483
+ if (path.match(/styles\.(css|scss)$/) && !path.includes('node_modules') && !path.includes('dist')) {
484
+ potentialFiles.push(path);
485
+ }
486
+ });
487
+ return potentialFiles;
488
+ }
489
+ //#endregion
490
+ //# sourceMappingURL=generator.js.map
@@ -0,0 +1,32 @@
1
+ {
2
+ "$schema": "http://json-schema.org/schema",
3
+ "$id": "tailwind-4-theme",
4
+ "title": "Tailwind 4 Theming Generator",
5
+ "description": "Generates Tailwind 4 compatible CSS from your Ethlete theme definitions",
6
+ "type": "object",
7
+ "properties": {
8
+ "themesPath": {
9
+ "type": "string",
10
+ "description": "Path to your themes file (relative to workspace root)",
11
+ "default": "src/themes.ts",
12
+ "x-prompt": "Where is your themes file located?"
13
+ },
14
+ "outputPath": {
15
+ "type": "string",
16
+ "description": "Where to output the generated Tailwind themes CSS",
17
+ "default": "src/styles/generated-tailwind-themes.css",
18
+ "x-prompt": "Where should the generated CSS be saved?"
19
+ },
20
+ "prefix": {
21
+ "type": "string",
22
+ "description": "Prefix for color variable names (e.g., 'et' creates --color-et-primary)",
23
+ "default": "et"
24
+ },
25
+ "skipFormat": {
26
+ "type": "boolean",
27
+ "description": "Skip formatting files after generation",
28
+ "default": false
29
+ }
30
+ },
31
+ "required": ["themesPath"]
32
+ }
package/package.json CHANGED
@@ -1,27 +1,34 @@
1
1
  {
2
2
  "name": "@ethlete/core",
3
- "version": "4.31.0",
3
+ "version": "5.0.0-next.0",
4
4
  "license": "MIT",
5
5
  "sideEffects": false,
6
6
  "peerDependencies": {
7
- "@angular/cdk": "20.2.8",
8
- "@angular/common": "20.3.4",
9
- "@angular/core": "20.3.4",
10
- "@angular/forms": "20.3.4",
11
- "@angular/platform-browser": "20.3.4",
12
- "@angular/router": "20.3.4",
7
+ "@analogjs/vite-plugin-angular": "2.3.1",
8
+ "@angular/cdk": "21.2.1",
9
+ "@angular/common": "21.2.1",
10
+ "@angular/core": "21.2.1",
11
+ "@angular/forms": "21.2.1",
12
+ "@angular/platform-browser": "21.2.1",
13
+ "@angular/router": "21.2.1",
13
14
  "@ethlete/types": "^1.6.2",
14
- "@floating-ui/dom": "1.7.4",
15
- "rxjs": "7.8.2"
15
+ "@floating-ui/dom": "1.7.6",
16
+ "@nx/devkit": "22.5.4",
17
+ "@nx/vite": "22.5.4",
18
+ "rxjs": "7.8.2",
19
+ "ts-morph": "21.0.1",
20
+ "typescript": "5.9.3",
21
+ "vite": "^7.0.0"
16
22
  },
23
+ "generators": "./generators/generators.json",
17
24
  "module": "fesm2022/ethlete-core.mjs",
18
- "typings": "index.d.ts",
25
+ "typings": "types/ethlete-core.d.ts",
19
26
  "exports": {
20
27
  "./package.json": {
21
28
  "default": "./package.json"
22
29
  },
23
30
  ".": {
24
- "types": "./index.d.ts",
31
+ "types": "./types/ethlete-core.d.ts",
25
32
  "default": "./fesm2022/ethlete-core.mjs"
26
33
  }
27
34
  },