@ethlete/core 5.0.0-next.7 → 5.0.0-next.70

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.
Files changed (54) hide show
  1. package/CHANGELOG.md +843 -0
  2. package/README.md +28 -4
  3. package/design/interactive-styles.ts +5 -0
  4. package/design/theme-css.js +392 -0
  5. package/design/theme-css.ts +547 -0
  6. package/fesm2022/ethlete-core.mjs +9338 -5667
  7. package/fesm2022/ethlete-core.mjs.map +1 -1
  8. package/generators/devtools-about/generator.js +122 -0
  9. package/generators/devtools-about/schema.json +25 -0
  10. package/generators/generators.json +41 -5
  11. package/generators/migrate-color-theme-variants/color-theme-variants.js +136 -0
  12. package/generators/migrate-color-theme-variants/migration.js +46 -0
  13. package/generators/migrate-color-theme-variants/schema.json +26 -0
  14. package/generators/migrate-create-destroy/create-destroy.js +317 -0
  15. package/generators/migrate-create-destroy/migration.js +46 -0
  16. package/generators/migrate-create-destroy/schema.json +26 -0
  17. package/generators/migrate-legacy-color-themes/legacy-color-themes.js +123 -0
  18. package/generators/migrate-legacy-color-themes/migration.js +93 -0
  19. package/generators/migrate-legacy-color-themes/schema.json +26 -0
  20. package/generators/migrate-provider-shape/migration-scope.js +35 -0
  21. package/generators/migrate-provider-shape/migration.js +50 -0
  22. package/generators/migrate-provider-shape/provider-shape.js +284 -0
  23. package/generators/migrate-provider-shape/schema.json +26 -0
  24. package/generators/migrate-surface-interaction-swatch/interaction-swatch.js +63 -0
  25. package/generators/migrate-surface-interaction-swatch/migration.js +36 -0
  26. package/generators/migrate-surface-interaction-swatch/schema.json +26 -0
  27. package/generators/migrate-to-v5/apply-replacements.js +14 -0
  28. package/generators/migrate-to-v5/color-naming.js +124 -0
  29. package/generators/migrate-to-v5/create-provider.js +67 -55
  30. package/generators/migrate-to-v5/migration-files.js +13 -0
  31. package/generators/migrate-to-v5/migration.js +33 -19
  32. package/generators/migrate-to-v5/removed-exports.js +161 -0
  33. package/generators/migrate-to-v5/router-state-service.js +125 -330
  34. package/generators/migrate-to-v5/schema.json +22 -0
  35. package/generators/migrate-to-v5/service-classes.js +108 -0
  36. package/generators/migrate-to-v5/sync-observable-consumers.js +80 -0
  37. package/generators/migrate-to-v5/viewport-service.js +226 -491
  38. package/generators/tailwind-4-color-theme/generator.js +411 -0
  39. package/generators/tailwind-4-color-theme/schema.json +44 -0
  40. package/generators/tailwind-4-surface-theme/generator.js +379 -0
  41. package/generators/tailwind-4-surface-theme/schema.json +48 -0
  42. package/migrations/legacy-color-themes.md +38 -0
  43. package/migrations/seo-directive-removed.md +81 -0
  44. package/migrations/surface-theme-regenerate.md +20 -0
  45. package/migrations.json +59 -0
  46. package/package.json +38 -21
  47. package/src/lib/animations/easing-tokens-styles.component.css +155 -0
  48. package/src/lib/providers/visually-hidden-styles.component.css +13 -0
  49. package/src/lib/resize-handles/resize-handles.component.css +137 -0
  50. package/src/lib/theming/color-interactive-styles.component.css +147 -0
  51. package/src/lib/theming/surface-interactive-styles.component.css +42 -0
  52. package/types/ethlete-core.d.ts +3842 -2205
  53. package/generators/tailwind-4-theme/generator.js +0 -514
  54. package/generators/tailwind-4-theme/schema.json +0 -32
@@ -0,0 +1,411 @@
1
+ import { formatFiles, logger, visitNotIgnoredFiles } from '@nx/devkit';
2
+ import { posix } from 'path';
3
+ import { Project, SyntaxKind } from 'ts-morph';
4
+ import { colorThemeCss, cssThemeName } from '../../design/theme-css.js';
5
+ export default async function generate(tree, schema) {
6
+ logger.log('\nšŸ”„ Starting Tailwind 4 theme generator...\n');
7
+ const themesPath = schema.themesPath || 'src/themes.ts';
8
+ const outputPath = schema.outputPath || 'src/styles/generated-tailwind-themes.css';
9
+ const prefix = schema.prefix || 'et';
10
+ const runtimePrefix = schema.runtimePrefix || prefix;
11
+ if (!tree.exists(themesPath)) {
12
+ logger.error(`āŒ Themes file not found at: ${themesPath}`);
13
+ logger.log(`\nPlease specify the correct path using --themesPath option.`);
14
+ logger.log(`Example: nx g @ethlete/core:tailwind-4-color-theme --themesPath=src/app/themes.ts\n`);
15
+ return;
16
+ }
17
+ logger.log(`šŸ“ Reading themes from: ${themesPath}`);
18
+ const themesContent = tree.read(themesPath, 'utf-8');
19
+ if (!themesContent) {
20
+ logger.error('āŒ Failed to read themes file');
21
+ return;
22
+ }
23
+ let themes;
24
+ try {
25
+ themes = extractThemesFromContent(tree, 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
+ // Apply the generation-time default override, if any. The shared theme
36
+ // definitions may mark a default via isDefault, but in a monorepo each app picks its
37
+ // own default at its generation invocation - the option wins over the definitions.
38
+ if (schema.defaultTheme) {
39
+ try {
40
+ const defaultName = applyDefaultThemeOverride(themes, schema.defaultTheme);
41
+ logger.log(`šŸŽÆ Default theme set at generation time: ${defaultName}`);
42
+ }
43
+ catch (error) {
44
+ logger.error('āŒ Invalid --defaultTheme option');
45
+ logger.error(` ${error instanceof Error ? error.message : String(error)}`);
46
+ return;
47
+ }
48
+ }
49
+ try {
50
+ validateThemeConfiguration(themes);
51
+ }
52
+ catch (error) {
53
+ logger.error('āŒ Theme configuration error');
54
+ logger.error(` ${error instanceof Error ? error.message : String(error)}`);
55
+ return;
56
+ }
57
+ logger.log('\nšŸŽØ Generating Tailwind theme CSS...');
58
+ const css = generateTailwindThemeCss(themes, prefix, runtimePrefix, schema);
59
+ const typesOutputPath = schema.typesOutputPath ?? outputPath.replace(/\.css$/, '.d.ts');
60
+ if (typesOutputPath === outputPath) {
61
+ throw new Error('A custom outputPath must end in .css or be paired with a distinct typesOutputPath.');
62
+ }
63
+ const outputDir = outputPath.substring(0, outputPath.lastIndexOf('/'));
64
+ if (outputDir && !tree.exists(outputDir)) {
65
+ logger.log(`šŸ“ Creating directory: ${outputDir}`);
66
+ }
67
+ tree.write(outputPath, css);
68
+ logger.log(`āœ… Generated Tailwind themes at: ${outputPath}`);
69
+ // Generate the `EthleteColorThemeNameRegistry` augmentation, so `etProvideColor`
70
+ // (and anything else that accepts a `RegisteredColorThemeName`) is checked/autocompleted
71
+ // against this app's actual theme names, instead of a plain `string`.
72
+ const typesDts = generateColorThemeNameTypes(themes, schema);
73
+ tree.write(typesOutputPath, typesDts);
74
+ logger.log(`āœ… Generated color theme name types at: ${typesOutputPath}`);
75
+ const mainStylesFiles = findMainStylesFile(tree);
76
+ if (mainStylesFiles.length > 0) {
77
+ logger.log('\nšŸ“ Found potential main styles files:');
78
+ mainStylesFiles.forEach((file) => logger.log(` - ${file}`));
79
+ logger.log('\nāš ļø Please manually import the generated themes:');
80
+ logger.log(` @import './${outputPath.replace('src/styles/', '')}';`);
81
+ }
82
+ if (!schema.skipFormat) {
83
+ await formatFiles(tree);
84
+ }
85
+ logger.log('\nāœ… Generation completed successfully!\n');
86
+ }
87
+ function extractThemesFromContent(tree, content, filePath) {
88
+ const project = new Project({
89
+ useInMemoryFileSystem: true,
90
+ compilerOptions: {
91
+ target: 99,
92
+ module: 99,
93
+ },
94
+ });
95
+ const sourceFile = project.createSourceFile(filePath, content);
96
+ const context = { tree, project };
97
+ const themes = [];
98
+ const exportedDeclarations = sourceFile.getVariableDeclarations().filter((decl) => {
99
+ const statement = decl.getVariableStatement();
100
+ return statement?.isExported();
101
+ });
102
+ const themesArray = exportedDeclarations.find((decl) => {
103
+ const name = decl.getName();
104
+ return name === 'THEMES' || name === 'themes';
105
+ });
106
+ if (!themesArray) {
107
+ throw new Error('Could not find THEMES or themes export');
108
+ }
109
+ let initializer = themesArray.getInitializer();
110
+ if (!initializer) {
111
+ throw new Error('THEMES export has no initializer');
112
+ }
113
+ // Handle 'satisfies X'/'as const' wrappers, in either order: [array] satisfies Type[],
114
+ // [array] as const, or both combined.
115
+ while (initializer.isKind(SyntaxKind.SatisfiesExpression) || initializer.isKind(SyntaxKind.AsExpression)) {
116
+ initializer = initializer.getExpression();
117
+ }
118
+ if (!initializer.isKind(SyntaxKind.ArrayLiteralExpression)) {
119
+ throw new Error('THEMES export must be an array literal');
120
+ }
121
+ const elements = initializer.getElements();
122
+ for (const element of elements) {
123
+ let themeObj;
124
+ let name = '<inline>';
125
+ if (element.isKind(SyntaxKind.Identifier)) {
126
+ name = element.getText();
127
+ const themeDecl = exportedDeclarations.find((decl) => decl.getName() === name);
128
+ if (!themeDecl) {
129
+ logger.warn(`āš ļø Could not find declaration for theme: ${name}`);
130
+ continue;
131
+ }
132
+ themeObj = themeDecl.getInitializer();
133
+ if (!themeObj) {
134
+ logger.warn(`āš ļø Theme ${name} has no initializer`);
135
+ continue;
136
+ }
137
+ }
138
+ else {
139
+ themeObj = element;
140
+ }
141
+ while (themeObj.isKind(SyntaxKind.AsExpression) || themeObj.isKind(SyntaxKind.SatisfiesExpression)) {
142
+ themeObj = themeObj.getExpression();
143
+ }
144
+ if (!themeObj.isKind(SyntaxKind.ObjectLiteralExpression)) {
145
+ logger.warn(`āš ļø Theme ${name} is not an object literal`);
146
+ continue;
147
+ }
148
+ try {
149
+ const theme = parseThemeObject(themeObj, sourceFile, context);
150
+ themes.push(theme);
151
+ }
152
+ catch (error) {
153
+ logger.warn(`āš ļø Failed to parse theme ${name}: ${error instanceof Error ? error.message : String(error)}`);
154
+ }
155
+ }
156
+ if (themes.length === 0) {
157
+ throw new Error('No valid themes found in THEMES array');
158
+ }
159
+ return themes;
160
+ }
161
+ function parseThemeObject(obj, sourceFile, context) {
162
+ const properties = obj.getProperties();
163
+ const theme = {};
164
+ for (const prop of properties) {
165
+ if (!prop.isKind(SyntaxKind.PropertyAssignment)) {
166
+ continue;
167
+ }
168
+ const propName = prop.getName();
169
+ const initializer = prop.getInitializer();
170
+ if (!initializer) {
171
+ continue;
172
+ }
173
+ switch (propName) {
174
+ case 'name':
175
+ if (initializer.isKind(SyntaxKind.StringLiteral)) {
176
+ theme.name = initializer.getLiteralValue();
177
+ }
178
+ break;
179
+ case 'isDefault':
180
+ if (initializer.isKind(SyntaxKind.TrueKeyword)) {
181
+ theme.isDefault = true;
182
+ }
183
+ break;
184
+ case 'type':
185
+ if (initializer.isKind(SyntaxKind.StringLiteral)) {
186
+ theme.type = initializer.getLiteralValue();
187
+ }
188
+ break;
189
+ case 'primary':
190
+ case 'secondary':
191
+ case 'tertiary':
192
+ if (initializer.isKind(SyntaxKind.ObjectLiteralExpression)) {
193
+ theme[propName] = parseThemeSwatch(initializer, sourceFile, context);
194
+ }
195
+ break;
196
+ }
197
+ }
198
+ if (!theme.name || !theme.primary) {
199
+ throw new Error('Theme must have name and primary properties');
200
+ }
201
+ return theme;
202
+ }
203
+ function applyDefaultThemeOverride(themes, defaultTheme) {
204
+ const wanted = cssThemeName(defaultTheme);
205
+ const target = themes.find((t) => t.name === defaultTheme || cssThemeName(t.name) === wanted);
206
+ if (!target) {
207
+ throw new Error(`No theme named "${defaultTheme}" found. Available themes: ${themes.map((t) => t.name).join(', ')}`);
208
+ }
209
+ for (const theme of themes) {
210
+ theme.isDefault = theme === target;
211
+ }
212
+ return target.name;
213
+ }
214
+ function validateThemeConfiguration(themes) {
215
+ const defaultThemes = themes.filter((t) => t.isDefault);
216
+ if (defaultThemes.length === 0) {
217
+ throw new Error('No default theme found. At least one theme must have isDefault: true');
218
+ }
219
+ if (defaultThemes.length > 1) {
220
+ throw new Error(`Multiple default themes found: ${defaultThemes.map((t) => t.name).join(', ')}. Only one theme can have isDefault: true`);
221
+ }
222
+ const typeMap = new Map();
223
+ for (const theme of themes) {
224
+ if (!theme.type) {
225
+ continue;
226
+ }
227
+ const existing = typeMap.get(theme.type) || [];
228
+ existing.push(theme.name);
229
+ typeMap.set(theme.type, existing);
230
+ }
231
+ for (const [type, names] of typeMap) {
232
+ if (names.length > 1) {
233
+ throw new Error(`Multiple themes with type "${type}" found: ${names.join(', ')}. Only one theme can have a given type`);
234
+ }
235
+ }
236
+ }
237
+ function parseThemeSwatch(obj, sourceFile, context) {
238
+ const properties = obj.getProperties();
239
+ const swatch = {};
240
+ for (const prop of properties) {
241
+ if (!prop.isKind(SyntaxKind.PropertyAssignment)) {
242
+ continue;
243
+ }
244
+ const propName = prop.getName();
245
+ const initializer = prop.getInitializer();
246
+ if (!initializer) {
247
+ continue;
248
+ }
249
+ const colorMap = parseColorMap(initializer, sourceFile, context);
250
+ if (!colorMap) {
251
+ continue;
252
+ }
253
+ if (propName === 'color') {
254
+ swatch.color = colorMap;
255
+ }
256
+ else if (propName === 'onColor') {
257
+ swatch.onColor = colorMap;
258
+ }
259
+ else if (propName === 'inkColor') {
260
+ swatch.inkColor = colorMap;
261
+ }
262
+ }
263
+ if (!swatch.color || !swatch.onColor) {
264
+ throw new Error('ThemeSwatch must have color and onColor properties');
265
+ }
266
+ return swatch;
267
+ }
268
+ const COLOR_MAP_KEYS = ['default', 'hover', 'focus', 'active', 'disabled'];
269
+ const isColorMapKey = (name) => COLOR_MAP_KEYS.some((key) => key === name);
270
+ function unwrapTypeExpressions(expression) {
271
+ let current = expression;
272
+ while (current?.isKind(SyntaxKind.AsExpression) || current?.isKind(SyntaxKind.SatisfiesExpression)) {
273
+ current = current.getExpression();
274
+ }
275
+ return current;
276
+ }
277
+ function resolveConstInitializer(name, sourceFile, context) {
278
+ const local = sourceFile.getVariableDeclarations().find((decl) => decl.getName() === name);
279
+ if (local) {
280
+ const initializer = unwrapTypeExpressions(local.getInitializer());
281
+ return initializer ? { initializer, sourceFile } : null;
282
+ }
283
+ const importDecl = sourceFile
284
+ .getImportDeclarations()
285
+ .find((decl) => decl.getNamedImports().some((named) => (named.getAliasNode()?.getText() ?? named.getName()) === name));
286
+ if (!importDecl) {
287
+ return null;
288
+ }
289
+ const specifier = importDecl.getModuleSpecifierValue();
290
+ const filePath = sourceFile.getFilePath();
291
+ const importedName = importDecl
292
+ .getNamedImports()
293
+ .find((named) => (named.getAliasNode()?.getText() ?? named.getName()) === name)
294
+ ?.getName() ?? name;
295
+ const warnUnresolved = (reason) => logger.warn(`āš ļø Could not resolve "${name}" imported from "${specifier}" in ${filePath} (${reason}); it is ignored.`);
296
+ if (!specifier.startsWith('.')) {
297
+ warnUnresolved('package imports are not followed');
298
+ return null;
299
+ }
300
+ const base = posix.join(posix.dirname(filePath), specifier).replace(/^\//, '');
301
+ const candidates = [`${base}.ts`, posix.join(base, 'index.ts'), base];
302
+ const resolvedPath = candidates.find((candidate) => context.tree.isFile(candidate));
303
+ if (!resolvedPath) {
304
+ warnUnresolved('file not found in the workspace');
305
+ return null;
306
+ }
307
+ const importedFile = context.project.getSourceFile(resolvedPath) ??
308
+ context.project.createSourceFile(resolvedPath, context.tree.read(resolvedPath, 'utf-8') ?? '');
309
+ const declaration = importedFile.getVariableDeclarations().find((decl) => decl.getName() === importedName);
310
+ const initializer = unwrapTypeExpressions(declaration?.getInitializer());
311
+ if (!initializer?.isKind(SyntaxKind.ObjectLiteralExpression)) {
312
+ warnUnresolved(`not an object literal in ${resolvedPath}`);
313
+ return null;
314
+ }
315
+ return { initializer, sourceFile: importedFile };
316
+ }
317
+ function parseColorMap(initializer, sourceFile, context) {
318
+ if (initializer.isKind(SyntaxKind.Identifier)) {
319
+ const resolved = resolveConstInitializer(initializer.getText(), sourceFile, context);
320
+ return resolved ? parseColorMap(resolved.initializer, resolved.sourceFile, context) : null;
321
+ }
322
+ if (initializer.isKind(SyntaxKind.ObjectLiteralExpression)) {
323
+ const colorMap = {};
324
+ const properties = initializer.getProperties();
325
+ for (const prop of properties) {
326
+ if (prop.isKind(SyntaxKind.PropertyAssignment)) {
327
+ const propName = prop.getName();
328
+ const propValue = prop.getInitializer();
329
+ if (propValue?.isKind(SyntaxKind.StringLiteral) && isColorMapKey(propName)) {
330
+ colorMap[propName] = propValue.getLiteralValue();
331
+ }
332
+ }
333
+ else if (prop.isKind(SyntaxKind.SpreadAssignment)) {
334
+ const spreadExpr = prop.getExpression();
335
+ if (spreadExpr.isKind(SyntaxKind.Identifier)) {
336
+ const resolved = resolveConstInitializer(spreadExpr.getText(), sourceFile, context);
337
+ if (resolved) {
338
+ Object.assign(colorMap, parseColorMap(resolved.initializer, resolved.sourceFile, context));
339
+ }
340
+ }
341
+ else {
342
+ logger.warn(`āš ļø Unsupported spread "...${spreadExpr.getText()}" in ${sourceFile.getFilePath()}; it is ignored.`);
343
+ }
344
+ }
345
+ }
346
+ const defaultColor = colorMap.default;
347
+ if (defaultColor) {
348
+ const isThemeColorMap = colorMap.hover !== undefined || colorMap.active !== undefined || colorMap.disabled !== undefined;
349
+ if (isThemeColorMap) {
350
+ const hoverColor = colorMap.hover || defaultColor;
351
+ const result = {
352
+ default: defaultColor,
353
+ hover: hoverColor,
354
+ focus: colorMap.focus,
355
+ active: colorMap.active || hoverColor,
356
+ disabled: colorMap.disabled || defaultColor,
357
+ };
358
+ return result;
359
+ }
360
+ return colorMap;
361
+ }
362
+ return null;
363
+ }
364
+ return null;
365
+ }
366
+ function generateColorThemeNameTypes(themes, schema) {
367
+ const themesPath = schema.themesPath || 'src/themes.ts';
368
+ const outputPath = schema.outputPath || 'src/styles/generated-tailwind-themes.css';
369
+ const typesOutputPath = schema.typesOutputPath || outputPath.replace(/\.css$/, '.d.ts');
370
+ const names = themes.map((theme) => `'${theme.name}'`).join(' | ');
371
+ return `/*
372
+ * Auto-generated by @ethlete/core:tailwind-4-color-theme
373
+ * DO NOT EDIT THIS FILE MANUALLY
374
+ *
375
+ * Regenerate by running:
376
+ * nx g @ethlete/core:tailwind-4-color-theme --themesPath=${themesPath}${schema.outputPath ? ` --outputPath=${outputPath}` : ''}${schema.typesOutputPath ? ` --typesOutputPath=${typesOutputPath}` : ''}${schema.prefix && schema.prefix !== 'et' ? ` --prefix=${schema.prefix}` : ''}${schema.runtimePrefix && schema.runtimePrefix !== schema.prefix ? ` --runtimePrefix=${schema.runtimePrefix}` : ''}${schema.defaultTheme ? ` --defaultTheme=${schema.defaultTheme}` : ''}
377
+ */
378
+
379
+ declare module '@ethlete/core' {
380
+ interface EthleteColorThemeNameRegistry {
381
+ name: ${names};
382
+ }
383
+ }
384
+
385
+ export {};
386
+ `;
387
+ }
388
+ function generateTailwindThemeCss(themes, utilityPrefix, runtimePrefix, schema) {
389
+ const themesPath = schema.themesPath || 'src/themes.ts';
390
+ const outputPath = schema.outputPath || 'src/styles/generated-tailwind-themes.css';
391
+ const header = `/*
392
+ * Auto-generated Tailwind 4 theme colors from @ethlete/core
393
+ * DO NOT EDIT THIS FILE MANUALLY
394
+ *
395
+ * Generated from your theme definitions
396
+ * This file can be regenerated by running:
397
+ * nx g @ethlete/core:tailwind-4-color-theme --themesPath=${themesPath}${schema.outputPath ? ` --outputPath=${outputPath}` : ''}${schema.prefix && schema.prefix !== 'et' ? ` --prefix=${schema.prefix}` : ''}${schema.runtimePrefix && schema.runtimePrefix !== utilityPrefix ? ` --runtimePrefix=${schema.runtimePrefix}` : ''}${schema.defaultTheme ? ` --defaultTheme=${schema.defaultTheme}` : ''}
398
+ */
399
+
400
+ `;
401
+ return `${header}${colorThemeCss(themes, { prefix: utilityPrefix, runtimePrefix })}`;
402
+ }
403
+ function findMainStylesFile(tree) {
404
+ const potentialFiles = [];
405
+ visitNotIgnoredFiles(tree, '', (path) => {
406
+ if (path.match(/styles\.(css|scss)$/) && !path.includes('node_modules') && !path.includes('dist')) {
407
+ potentialFiles.push(path);
408
+ }
409
+ });
410
+ return potentialFiles;
411
+ }
@@ -0,0 +1,44 @@
1
+ {
2
+ "$schema": "http://json-schema.org/schema",
3
+ "$id": "tailwind-4-color-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
+ "typesOutputPath": {
21
+ "type": "string",
22
+ "description": "Where to output the generated `EthleteColorThemeNameRegistry` augmentation (a .d.ts that narrows `etProvideColor` and other theme-name APIs to your actual theme names). Defaults to `outputPath` with its extension swapped to `.d.ts`."
23
+ },
24
+ "prefix": {
25
+ "type": "string",
26
+ "description": "Prefix for Tailwind utility color variable names (e.g., 'et' creates --color-et-primary, usable as bg-et-primary)",
27
+ "default": "et"
28
+ },
29
+ "runtimePrefix": {
30
+ "type": "string",
31
+ "description": "Prefix for the runtime theme-swap mechanism: the CSS custom properties (--<runtimePrefix>-color-primary) and selectors (.<runtimePrefix>-color--<name>) that ProvideColorDirective (etProvideColor) toggles between. Defaults to the same value as 'prefix'. Set this separately when your Tailwind utility prefix needs to differ from your runtime theme-switching prefix (e.g. because '--color-primary' is already a documented public API elsewhere in your app)."
32
+ },
33
+ "defaultTheme": {
34
+ "type": "string",
35
+ "description": "Name of the theme to use as the default, overriding any isDefault flags in the theme definitions. Use this when multiple apps share one theme definition set (e.g. in a monorepo) but each app needs a different default."
36
+ },
37
+ "skipFormat": {
38
+ "type": "boolean",
39
+ "description": "Skip formatting files after generation",
40
+ "default": false
41
+ }
42
+ },
43
+ "required": ["themesPath"]
44
+ }