@webmate-studio/builder 0.1.5 → 0.1.7
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.
- package/package.json +3 -2
- package/src/build.js +1 -1
- package/src/tailwind-generator.js +84 -58
- package/src/tailwind-generator-v4.js +0 -343
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webmate-studio/builder",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Webmate Studio Component Builder",
|
|
6
6
|
"keywords": [
|
|
@@ -24,6 +24,8 @@
|
|
|
24
24
|
"access": "public"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
+
"@tailwindcss/cli": "^4.1.0",
|
|
28
|
+
"@webmate-studio/builder": "^0.1.5",
|
|
27
29
|
"@webmate-studio/core": "^0.1.0",
|
|
28
30
|
"@webmate-studio/parser": "^0.1.0",
|
|
29
31
|
"alpinejs": "^3.15.0",
|
|
@@ -38,7 +40,6 @@
|
|
|
38
40
|
"react-dom": "^19.2.0",
|
|
39
41
|
"svelte": "^5.41.2",
|
|
40
42
|
"tailwindcss": "^4.1.0",
|
|
41
|
-
"@tailwindcss/cli": "^4.1.0",
|
|
42
43
|
"vue": "^3.5.22"
|
|
43
44
|
}
|
|
44
45
|
}
|
package/src/build.js
CHANGED
|
@@ -5,7 +5,7 @@ import { parseComponent } from '@webmate-studio/parser';
|
|
|
5
5
|
import { loadConfig, logger } from '@webmate-studio/core';
|
|
6
6
|
import { cleanComponentHTML, extractStyles } from './html-cleaner.js';
|
|
7
7
|
import { generateManifest } from './manifest.js';
|
|
8
|
-
import { generateComponentCSS } from './tailwind-generator
|
|
8
|
+
import { generateComponentCSS } from './tailwind-generator.js';
|
|
9
9
|
import { bundleComponentIslands } from './bundler.js';
|
|
10
10
|
import { parseDocument } from 'htmlparser2';
|
|
11
11
|
import { DomUtils } from 'htmlparser2';
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Tailwind CSS Generator for Components
|
|
3
|
-
* Generates scoped Tailwind CSS
|
|
2
|
+
* Tailwind CSS v4 Generator for Components
|
|
3
|
+
* Generates scoped Tailwind CSS using Tailwind v4's new CSS-based configuration
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { exec } from 'child_process';
|
|
7
7
|
import { promisify } from 'util';
|
|
8
|
-
import { writeFileSync,
|
|
8
|
+
import { writeFileSync, unlinkSync, mkdtempSync } from 'fs';
|
|
9
9
|
import { join, dirname } from 'path';
|
|
10
10
|
import { tmpdir } from 'os';
|
|
11
11
|
import { fileURLToPath } from 'url';
|
|
@@ -21,27 +21,25 @@ import { createRequire } from 'module';
|
|
|
21
21
|
const require = createRequire(import.meta.url);
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
|
-
* Get tailwindcss
|
|
25
|
-
* @returns {string}
|
|
24
|
+
* Get tailwindcss CLI path for execution
|
|
25
|
+
* @returns {string} Path to tailwindcss CLI
|
|
26
26
|
*/
|
|
27
27
|
function getTailwindCommand() {
|
|
28
28
|
try {
|
|
29
|
-
// Try to resolve tailwindcss package
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
// Check if we're on Windows
|
|
34
|
-
const isWindows = process.platform === 'win32';
|
|
35
|
-
const binName = isWindows ? 'tailwindcss.cmd' : 'tailwindcss';
|
|
36
|
-
|
|
37
|
-
// Build path to bin
|
|
38
|
-
const binPath = join(tailwindDir, '..', '.bin', binName);
|
|
39
|
-
|
|
40
|
-
// Return just npx command - will be resolved at runtime
|
|
41
|
-
return 'npx tailwindcss';
|
|
29
|
+
// Try to resolve @tailwindcss/cli package which contains the actual binary
|
|
30
|
+
const cliPath = require.resolve('@tailwindcss/cli');
|
|
31
|
+
return cliPath;
|
|
42
32
|
} catch (e) {
|
|
43
|
-
// Fallback to
|
|
44
|
-
|
|
33
|
+
// Fallback: try to find tailwindcss package and use its CLI
|
|
34
|
+
try {
|
|
35
|
+
const tailwindPackagePath = require.resolve('tailwindcss/package.json');
|
|
36
|
+
const tailwindDir = dirname(tailwindPackagePath);
|
|
37
|
+
// Tailwind v4 uses @tailwindcss/cli
|
|
38
|
+
const cliPath = join(tailwindDir, '..', '@tailwindcss', 'cli', 'dist', 'index.js');
|
|
39
|
+
return cliPath;
|
|
40
|
+
} catch (e2) {
|
|
41
|
+
throw new Error('Could not find @tailwindcss/cli. Please install it: npm install @tailwindcss/cli');
|
|
42
|
+
}
|
|
45
43
|
}
|
|
46
44
|
}
|
|
47
45
|
|
|
@@ -71,7 +69,50 @@ export function extractTailwindClasses(html) {
|
|
|
71
69
|
}
|
|
72
70
|
|
|
73
71
|
/**
|
|
74
|
-
* Generate
|
|
72
|
+
* Generate CSS with custom theme colors and design tokens for Tailwind v4
|
|
73
|
+
* @param {Object} colors - Color definitions
|
|
74
|
+
* @param {Object} designTokens - Complete design tokens (optional)
|
|
75
|
+
* @returns {string} CSS with @theme directive
|
|
76
|
+
*/
|
|
77
|
+
function generateThemeCSS(colors, designTokens = null) {
|
|
78
|
+
let themeCSS = `@theme {\n`;
|
|
79
|
+
|
|
80
|
+
// Helper to flatten nested colors
|
|
81
|
+
function flattenColors(obj, prefix = '') {
|
|
82
|
+
let css = '';
|
|
83
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
84
|
+
const colorKey = prefix ? `${prefix}-${key}` : key;
|
|
85
|
+
|
|
86
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
87
|
+
// Nested color (e.g., primary.light)
|
|
88
|
+
if (value.DEFAULT) {
|
|
89
|
+
css += ` --color-${colorKey}: ${value.DEFAULT};\n`;
|
|
90
|
+
}
|
|
91
|
+
css += flattenColors(value, colorKey);
|
|
92
|
+
} else {
|
|
93
|
+
// Simple color value
|
|
94
|
+
css += ` --color-${colorKey}: ${value};\n`;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return css;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
themeCSS += flattenColors(colors);
|
|
101
|
+
|
|
102
|
+
// Add spacing if provided in design tokens
|
|
103
|
+
if (designTokens?.spacing) {
|
|
104
|
+
if (typeof designTokens.spacing === 'string') {
|
|
105
|
+
themeCSS += ` --spacing: ${designTokens.spacing};\n`;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
themeCSS += `}\n`;
|
|
110
|
+
|
|
111
|
+
return themeCSS;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Generate Tailwind CSS for specific classes (Tailwind v4)
|
|
75
116
|
* @param {Set<string>|Array<string>} classes - Tailwind classes to include
|
|
76
117
|
* @param {Object} options - Generation options
|
|
77
118
|
* @param {Object} options.designTokens - Custom design tokens (optional)
|
|
@@ -92,14 +133,10 @@ export async function generateTailwindCSS(classes, options = {}) {
|
|
|
92
133
|
|
|
93
134
|
// Create temporary directory
|
|
94
135
|
const tempDir = mkdtempSync(join(tmpdir(), 'tailwind-gen-'));
|
|
95
|
-
const configPath = join(tempDir, 'tailwind.config.cjs');
|
|
96
136
|
const inputPath = join(tempDir, 'input.css');
|
|
97
137
|
const htmlPath = join(tempDir, 'content.html');
|
|
98
138
|
|
|
99
139
|
try {
|
|
100
|
-
// Tailwind v4 uses CSS-based configuration
|
|
101
|
-
// Instead of config file, we generate CSS with custom properties and @theme
|
|
102
|
-
|
|
103
140
|
// Default theme colors (complete design tokens)
|
|
104
141
|
const defaultColors = {
|
|
105
142
|
// Brand colors with variants
|
|
@@ -150,13 +187,13 @@ export async function generateTailwindCSS(classes, options = {}) {
|
|
|
150
187
|
900: '#111827'
|
|
151
188
|
},
|
|
152
189
|
|
|
153
|
-
// Semantic text colors
|
|
190
|
+
// Semantic text colors
|
|
154
191
|
'text-body': '#374151',
|
|
155
192
|
'text-heading': '#111827',
|
|
156
193
|
'text-muted': '#6b7280',
|
|
157
194
|
'text-inverse': '#ffffff',
|
|
158
195
|
|
|
159
|
-
// Semantic background colors
|
|
196
|
+
// Semantic background colors
|
|
160
197
|
'bg-default': '#ffffff',
|
|
161
198
|
'bg-subtle': '#f9fafb',
|
|
162
199
|
'bg-elevated': '#ffffff',
|
|
@@ -170,11 +207,9 @@ export async function generateTailwindCSS(classes, options = {}) {
|
|
|
170
207
|
// Merge with provided design tokens
|
|
171
208
|
let themeColors = defaultColors;
|
|
172
209
|
if (designTokens?.colors) {
|
|
173
|
-
// Convert flat design tokens to nested structure
|
|
174
210
|
const tokens = designTokens.colors;
|
|
175
211
|
themeColors = {
|
|
176
212
|
...defaultColors,
|
|
177
|
-
// Override with provided tokens
|
|
178
213
|
primary: {
|
|
179
214
|
DEFAULT: tokens.primary || defaultColors.primary.DEFAULT,
|
|
180
215
|
light: tokens.primaryLight || defaultColors.primary.light,
|
|
@@ -232,54 +267,45 @@ export async function generateTailwindCSS(classes, options = {}) {
|
|
|
232
267
|
};
|
|
233
268
|
}
|
|
234
269
|
|
|
235
|
-
// Create
|
|
236
|
-
const
|
|
237
|
-
content: [htmlPath],
|
|
238
|
-
safelist: classArray,
|
|
239
|
-
theme: {
|
|
240
|
-
extend: {
|
|
241
|
-
colors: themeColors,
|
|
242
|
-
fontFamily: designTokens?.typography?.fontFamily || {},
|
|
243
|
-
fontSize: designTokens?.typography?.fontSize || {},
|
|
244
|
-
spacing: designTokens?.spacing || {},
|
|
245
|
-
borderRadius: designTokens?.borderRadius || {},
|
|
246
|
-
boxShadow: designTokens?.boxShadow || {}
|
|
247
|
-
}
|
|
248
|
-
},
|
|
249
|
-
plugins: []
|
|
250
|
-
};
|
|
251
|
-
|
|
252
|
-
writeFileSync(configPath, `module.exports = ${JSON.stringify(config, null, 2)};`);
|
|
253
|
-
|
|
254
|
-
// Create input CSS with Tailwind directives
|
|
270
|
+
// Create input CSS with Tailwind v4 syntax
|
|
271
|
+
const themeCSS = generateThemeCSS(themeColors);
|
|
255
272
|
const inputCSS = `
|
|
256
|
-
@
|
|
257
|
-
|
|
258
|
-
|
|
273
|
+
@import "tailwindcss";
|
|
274
|
+
|
|
275
|
+
${themeCSS}
|
|
259
276
|
`.trim();
|
|
277
|
+
|
|
260
278
|
writeFileSync(inputPath, inputCSS);
|
|
261
279
|
|
|
262
280
|
// Create dummy HTML file with all classes (for content scanning)
|
|
263
281
|
const dummyHTML = `<div class="${classArray.join(' ')}"></div>`;
|
|
264
282
|
writeFileSync(htmlPath, dummyHTML);
|
|
265
283
|
|
|
266
|
-
// Run Tailwind CLI
|
|
267
|
-
|
|
268
|
-
const
|
|
284
|
+
// Run Tailwind v4 CLI
|
|
285
|
+
// Tailwind v4 auto-detects HTML files in the same directory
|
|
286
|
+
const tailwindCliPath = getTailwindCommand();
|
|
287
|
+
const command = `node "${tailwindCliPath}" -i "${inputPath}" ${minify ? '--minify' : ''}`;
|
|
288
|
+
|
|
289
|
+
// Set NODE_PATH so tailwindcss module can be resolved
|
|
290
|
+
const builderNodeModules = join(__dirname, "..", "node_modules");
|
|
269
291
|
|
|
270
292
|
const { stdout } = await execAsync(command, {
|
|
271
|
-
|
|
293
|
+
cwd: tempDir,
|
|
294
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
295
|
+
env: {
|
|
296
|
+
...process.env,
|
|
297
|
+
NODE_PATH: builderNodeModules
|
|
298
|
+
}
|
|
272
299
|
});
|
|
273
300
|
|
|
274
301
|
return stdout.trim();
|
|
275
302
|
|
|
276
303
|
} catch (error) {
|
|
277
|
-
console.error('[Tailwind Generator] Error:', error);
|
|
304
|
+
console.error('[Tailwind Generator v4] Error:', error);
|
|
278
305
|
throw new Error(`Failed to generate Tailwind CSS: ${error.message}`);
|
|
279
306
|
} finally {
|
|
280
307
|
// Cleanup temp files
|
|
281
308
|
try {
|
|
282
|
-
unlinkSync(configPath);
|
|
283
309
|
unlinkSync(inputPath);
|
|
284
310
|
unlinkSync(htmlPath);
|
|
285
311
|
} catch (e) {
|
|
@@ -1,343 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tailwind CSS v4 Generator for Components
|
|
3
|
-
* Generates scoped Tailwind CSS using Tailwind v4's new CSS-based configuration
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { exec } from 'child_process';
|
|
7
|
-
import { promisify } from 'util';
|
|
8
|
-
import { writeFileSync, unlinkSync, mkdtempSync } from 'fs';
|
|
9
|
-
import { join, dirname } from 'path';
|
|
10
|
-
import { tmpdir } from 'os';
|
|
11
|
-
import { fileURLToPath } from 'url';
|
|
12
|
-
|
|
13
|
-
const execAsync = promisify(exec);
|
|
14
|
-
|
|
15
|
-
// Get the directory where this module is located
|
|
16
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
17
|
-
const __dirname = dirname(__filename);
|
|
18
|
-
|
|
19
|
-
// Import createRequire for resolving packages
|
|
20
|
-
import { createRequire } from 'module';
|
|
21
|
-
const require = createRequire(import.meta.url);
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Get tailwindcss command for execution
|
|
25
|
-
* @returns {string} Command to run tailwindcss
|
|
26
|
-
*/
|
|
27
|
-
function getTailwindCommand() {
|
|
28
|
-
try {
|
|
29
|
-
// Try to resolve tailwindcss package
|
|
30
|
-
const tailwindPackagePath = require.resolve('tailwindcss/package.json');
|
|
31
|
-
const tailwindDir = dirname(tailwindPackagePath);
|
|
32
|
-
|
|
33
|
-
// Check if we're on Windows
|
|
34
|
-
const isWindows = process.platform === 'win32';
|
|
35
|
-
const binName = isWindows ? 'tailwindcss.cmd' : 'tailwindcss';
|
|
36
|
-
|
|
37
|
-
// Build path to bin
|
|
38
|
-
const binPath = join(tailwindDir, '..', '.bin', binName);
|
|
39
|
-
|
|
40
|
-
// Return just npx command - will be resolved at runtime
|
|
41
|
-
return 'npx tailwindcss';
|
|
42
|
-
} catch (e) {
|
|
43
|
-
// Fallback to npx
|
|
44
|
-
return 'npx tailwindcss';
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Extract Tailwind classes from HTML content
|
|
50
|
-
* @param {string} html - HTML content
|
|
51
|
-
* @returns {Set<string>} Set of unique Tailwind classes
|
|
52
|
-
*/
|
|
53
|
-
export function extractTailwindClasses(html) {
|
|
54
|
-
const classes = new Set();
|
|
55
|
-
|
|
56
|
-
// Match class="..." and class='...'
|
|
57
|
-
const classRegex = /class=["']([^"']+)["']/g;
|
|
58
|
-
let match;
|
|
59
|
-
|
|
60
|
-
while ((match = classRegex.exec(html)) !== null) {
|
|
61
|
-
const classString = match[1];
|
|
62
|
-
// Split by whitespace and add each class
|
|
63
|
-
classString.split(/\s+/).forEach(cls => {
|
|
64
|
-
if (cls.trim()) {
|
|
65
|
-
classes.add(cls.trim());
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return classes;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Generate CSS with custom theme colors and design tokens for Tailwind v4
|
|
75
|
-
* @param {Object} colors - Color definitions
|
|
76
|
-
* @param {Object} designTokens - Complete design tokens (optional)
|
|
77
|
-
* @returns {string} CSS with @theme directive
|
|
78
|
-
*/
|
|
79
|
-
function generateThemeCSS(colors, designTokens = null) {
|
|
80
|
-
let themeCSS = `@theme {\n`;
|
|
81
|
-
|
|
82
|
-
// Helper to flatten nested colors
|
|
83
|
-
function flattenColors(obj, prefix = '') {
|
|
84
|
-
let css = '';
|
|
85
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
86
|
-
const colorKey = prefix ? `${prefix}-${key}` : key;
|
|
87
|
-
|
|
88
|
-
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
89
|
-
// Nested color (e.g., primary.light)
|
|
90
|
-
if (value.DEFAULT) {
|
|
91
|
-
css += ` --color-${colorKey}: ${value.DEFAULT};\n`;
|
|
92
|
-
}
|
|
93
|
-
css += flattenColors(value, colorKey);
|
|
94
|
-
} else {
|
|
95
|
-
// Simple color value
|
|
96
|
-
css += ` --color-${colorKey}: ${value};\n`;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
return css;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
themeCSS += flattenColors(colors);
|
|
103
|
-
|
|
104
|
-
// Add spacing if provided in design tokens
|
|
105
|
-
if (designTokens?.spacing) {
|
|
106
|
-
if (typeof designTokens.spacing === 'string') {
|
|
107
|
-
themeCSS += ` --spacing: ${designTokens.spacing};\n`;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
themeCSS += `}\n`;
|
|
112
|
-
|
|
113
|
-
return themeCSS;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Generate Tailwind CSS for specific classes (Tailwind v4)
|
|
118
|
-
* @param {Set<string>|Array<string>} classes - Tailwind classes to include
|
|
119
|
-
* @param {Object} options - Generation options
|
|
120
|
-
* @param {Object} options.designTokens - Custom design tokens (optional)
|
|
121
|
-
* @param {boolean} options.minify - Minify output CSS
|
|
122
|
-
* @returns {Promise<string>} Generated CSS
|
|
123
|
-
*/
|
|
124
|
-
export async function generateTailwindCSS(classes, options = {}) {
|
|
125
|
-
const {
|
|
126
|
-
designTokens = null,
|
|
127
|
-
minify = true
|
|
128
|
-
} = options;
|
|
129
|
-
|
|
130
|
-
const classArray = Array.isArray(classes) ? classes : Array.from(classes);
|
|
131
|
-
|
|
132
|
-
if (classArray.length === 0) {
|
|
133
|
-
return '/* No Tailwind classes found */';
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Create temporary directory
|
|
137
|
-
const tempDir = mkdtempSync(join(tmpdir(), 'tailwind-gen-'));
|
|
138
|
-
const inputPath = join(tempDir, 'input.css');
|
|
139
|
-
const htmlPath = join(tempDir, 'content.html');
|
|
140
|
-
|
|
141
|
-
try {
|
|
142
|
-
// Default theme colors (complete design tokens)
|
|
143
|
-
const defaultColors = {
|
|
144
|
-
// Brand colors with variants
|
|
145
|
-
primary: {
|
|
146
|
-
DEFAULT: '#cf0ce9',
|
|
147
|
-
light: '#fb923c',
|
|
148
|
-
dark: '#c2410c'
|
|
149
|
-
},
|
|
150
|
-
secondary: {
|
|
151
|
-
DEFAULT: '#6366f1',
|
|
152
|
-
light: '#818cf8',
|
|
153
|
-
dark: '#4f46e5'
|
|
154
|
-
},
|
|
155
|
-
|
|
156
|
-
// Feedback colors with variants
|
|
157
|
-
success: {
|
|
158
|
-
DEFAULT: '#10b981',
|
|
159
|
-
light: '#34d399',
|
|
160
|
-
dark: '#059669'
|
|
161
|
-
},
|
|
162
|
-
error: {
|
|
163
|
-
DEFAULT: '#ef4444',
|
|
164
|
-
light: '#f87171',
|
|
165
|
-
dark: '#dc2626'
|
|
166
|
-
},
|
|
167
|
-
warning: {
|
|
168
|
-
DEFAULT: '#f59e0b',
|
|
169
|
-
light: '#fbbf24',
|
|
170
|
-
dark: '#d97706'
|
|
171
|
-
},
|
|
172
|
-
info: {
|
|
173
|
-
DEFAULT: '#3b82f6',
|
|
174
|
-
light: '#60a5fa',
|
|
175
|
-
dark: '#2563eb'
|
|
176
|
-
},
|
|
177
|
-
|
|
178
|
-
// Grays
|
|
179
|
-
gray: {
|
|
180
|
-
50: '#f9fafb',
|
|
181
|
-
100: '#f3f4f6',
|
|
182
|
-
200: '#e5e7eb',
|
|
183
|
-
300: '#d1d5db',
|
|
184
|
-
400: '#9ca3af',
|
|
185
|
-
500: '#6b7280',
|
|
186
|
-
600: '#4b5563',
|
|
187
|
-
700: '#374151',
|
|
188
|
-
800: '#1f2937',
|
|
189
|
-
900: '#111827'
|
|
190
|
-
},
|
|
191
|
-
|
|
192
|
-
// Semantic text colors
|
|
193
|
-
'text-body': '#374151',
|
|
194
|
-
'text-heading': '#111827',
|
|
195
|
-
'text-muted': '#6b7280',
|
|
196
|
-
'text-inverse': '#ffffff',
|
|
197
|
-
|
|
198
|
-
// Semantic background colors
|
|
199
|
-
'bg-default': '#ffffff',
|
|
200
|
-
'bg-subtle': '#f9fafb',
|
|
201
|
-
'bg-elevated': '#ffffff',
|
|
202
|
-
'bg-inverse': '#111827',
|
|
203
|
-
|
|
204
|
-
// Basic colors
|
|
205
|
-
black: '#000000',
|
|
206
|
-
white: '#ffffff'
|
|
207
|
-
};
|
|
208
|
-
|
|
209
|
-
// Merge with provided design tokens
|
|
210
|
-
let themeColors = defaultColors;
|
|
211
|
-
if (designTokens?.colors) {
|
|
212
|
-
const tokens = designTokens.colors;
|
|
213
|
-
themeColors = {
|
|
214
|
-
...defaultColors,
|
|
215
|
-
primary: {
|
|
216
|
-
DEFAULT: tokens.primary || defaultColors.primary.DEFAULT,
|
|
217
|
-
light: tokens.primaryLight || defaultColors.primary.light,
|
|
218
|
-
dark: tokens.primaryDark || defaultColors.primary.dark
|
|
219
|
-
},
|
|
220
|
-
secondary: {
|
|
221
|
-
DEFAULT: tokens.secondary || defaultColors.secondary.DEFAULT,
|
|
222
|
-
light: tokens.secondaryLight || defaultColors.secondary.light,
|
|
223
|
-
dark: tokens.secondaryDark || defaultColors.secondary.dark
|
|
224
|
-
},
|
|
225
|
-
success: {
|
|
226
|
-
DEFAULT: tokens.success || defaultColors.success.DEFAULT,
|
|
227
|
-
light: tokens.successLight || defaultColors.success.light,
|
|
228
|
-
dark: tokens.successDark || defaultColors.success.dark
|
|
229
|
-
},
|
|
230
|
-
error: {
|
|
231
|
-
DEFAULT: tokens.error || defaultColors.error.DEFAULT,
|
|
232
|
-
light: tokens.errorLight || defaultColors.error.light,
|
|
233
|
-
dark: tokens.errorDark || defaultColors.error.dark
|
|
234
|
-
},
|
|
235
|
-
warning: {
|
|
236
|
-
DEFAULT: tokens.warning || defaultColors.warning.DEFAULT,
|
|
237
|
-
light: tokens.warningLight || defaultColors.warning.light,
|
|
238
|
-
dark: tokens.warningDark || defaultColors.warning.dark
|
|
239
|
-
},
|
|
240
|
-
info: {
|
|
241
|
-
DEFAULT: tokens.info || defaultColors.info.DEFAULT,
|
|
242
|
-
light: tokens.infoLight || defaultColors.info.light,
|
|
243
|
-
dark: tokens.infoDark || defaultColors.info.dark
|
|
244
|
-
},
|
|
245
|
-
// Semantic colors
|
|
246
|
-
'text-body': tokens.textBody || defaultColors['text-body'],
|
|
247
|
-
'text-heading': tokens.textHeading || defaultColors['text-heading'],
|
|
248
|
-
'text-muted': tokens.textMuted || defaultColors['text-muted'],
|
|
249
|
-
'text-inverse': tokens.textInverse || defaultColors['text-inverse'],
|
|
250
|
-
'bg-default': tokens.bgDefault || defaultColors['bg-default'],
|
|
251
|
-
'bg-subtle': tokens.bgSubtle || defaultColors['bg-subtle'],
|
|
252
|
-
'bg-elevated': tokens.bgElevated || defaultColors['bg-elevated'],
|
|
253
|
-
'bg-inverse': tokens.bgInverse || defaultColors['bg-inverse'],
|
|
254
|
-
// Grays
|
|
255
|
-
gray: {
|
|
256
|
-
50: tokens.gray50 || defaultColors.gray[50],
|
|
257
|
-
100: tokens.gray100 || defaultColors.gray[100],
|
|
258
|
-
200: tokens.gray200 || defaultColors.gray[200],
|
|
259
|
-
300: tokens.gray300 || defaultColors.gray[300],
|
|
260
|
-
400: tokens.gray400 || defaultColors.gray[400],
|
|
261
|
-
500: tokens.gray500 || defaultColors.gray[500],
|
|
262
|
-
600: tokens.gray600 || defaultColors.gray[600],
|
|
263
|
-
700: tokens.gray700 || defaultColors.gray[700],
|
|
264
|
-
800: tokens.gray800 || defaultColors.gray[800],
|
|
265
|
-
900: tokens.gray900 || defaultColors.gray[900]
|
|
266
|
-
},
|
|
267
|
-
black: tokens.black || defaultColors.black,
|
|
268
|
-
white: tokens.white || defaultColors.white
|
|
269
|
-
};
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
// Create input CSS with Tailwind v4 syntax
|
|
273
|
-
const themeCSS = generateThemeCSS(themeColors);
|
|
274
|
-
const inputCSS = `
|
|
275
|
-
@import "tailwindcss";
|
|
276
|
-
|
|
277
|
-
${themeCSS}
|
|
278
|
-
`.trim();
|
|
279
|
-
|
|
280
|
-
writeFileSync(inputPath, inputCSS);
|
|
281
|
-
|
|
282
|
-
// Create dummy HTML file with all classes (for content scanning)
|
|
283
|
-
const dummyHTML = `<div class="${classArray.join(' ')}"></div>`;
|
|
284
|
-
writeFileSync(htmlPath, dummyHTML);
|
|
285
|
-
|
|
286
|
-
// Run Tailwind v4 CLI
|
|
287
|
-
// Tailwind v4 auto-detects HTML files in the same directory
|
|
288
|
-
const tailwindCmd = getTailwindCommand();
|
|
289
|
-
const command = `${tailwindCmd} -i "${inputPath}" ${minify ? '--minify' : ''}`;
|
|
290
|
-
|
|
291
|
-
// Set NODE_PATH so tailwindcss module can be resolved
|
|
292
|
-
const builderNodeModules = join(__dirname, "..", "node_modules");
|
|
293
|
-
|
|
294
|
-
const { stdout } = await execAsync(command, {
|
|
295
|
-
cwd: tempDir,
|
|
296
|
-
maxBuffer: 10 * 1024 * 1024,
|
|
297
|
-
env: {
|
|
298
|
-
...process.env,
|
|
299
|
-
NODE_PATH: builderNodeModules
|
|
300
|
-
}
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
return stdout.trim();
|
|
304
|
-
|
|
305
|
-
} catch (error) {
|
|
306
|
-
console.error('[Tailwind Generator v4] Error:', error);
|
|
307
|
-
throw new Error(`Failed to generate Tailwind CSS: ${error.message}`);
|
|
308
|
-
} finally {
|
|
309
|
-
// Cleanup temp files
|
|
310
|
-
try {
|
|
311
|
-
unlinkSync(inputPath);
|
|
312
|
-
unlinkSync(htmlPath);
|
|
313
|
-
} catch (e) {
|
|
314
|
-
// Ignore cleanup errors
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
/**
|
|
320
|
-
* Generate Tailwind CSS for a component HTML file
|
|
321
|
-
* @param {string} html - Component HTML content
|
|
322
|
-
* @param {Object} options - Generation options
|
|
323
|
-
* @returns {Promise<{css: string, classes: string[]}>} Generated CSS and extracted classes
|
|
324
|
-
*/
|
|
325
|
-
export async function generateComponentCSS(html, options = {}) {
|
|
326
|
-
// Extract classes from HTML
|
|
327
|
-
const classes = extractTailwindClasses(html);
|
|
328
|
-
|
|
329
|
-
if (classes.size === 0) {
|
|
330
|
-
return {
|
|
331
|
-
css: '/* No Tailwind classes found in component */\n',
|
|
332
|
-
classes: []
|
|
333
|
-
};
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
// Generate CSS
|
|
337
|
-
const css = await generateTailwindCSS(classes, options);
|
|
338
|
-
|
|
339
|
-
return {
|
|
340
|
-
css,
|
|
341
|
-
classes: Array.from(classes)
|
|
342
|
-
};
|
|
343
|
-
}
|