@udixio/tailwind 1.3.0 → 1.4.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,340 @@
1
+ import * as fs from 'fs';
2
+
3
+ import * as console from 'node:console';
4
+ import { dirname, join, normalize, resolve } from 'pathe';
5
+ import chalk from 'chalk';
6
+
7
+ // Fonction utilitaire universelle de normalisation des chemins
8
+ const normalizePath = async (filePath: string): Promise<string> => {
9
+ const { fileURLToPath } = await import('url');
10
+
11
+ try {
12
+ if (filePath.startsWith('file://')) {
13
+ return normalize(fileURLToPath(filePath));
14
+ }
15
+ return normalize(filePath);
16
+ } catch (error) {
17
+ console.warn(
18
+ chalk.yellow(
19
+ `⚠️ Could not process path ${filePath}, treating as regular path`,
20
+ ),
21
+ );
22
+ return normalize(filePath);
23
+ }
24
+ };
25
+
26
+ // Wrapper sécurisé pour fs.existsSync
27
+ const safeExistsSync = async (filePath: string): Promise<boolean> => {
28
+ return fs.existsSync(await normalizePath(filePath));
29
+ };
30
+
31
+ // Wrapper sécurisé pour fs.readFileSync
32
+ const safeReadFileSync = async (
33
+ filePath: string,
34
+ encoding: BufferEncoding = 'utf8',
35
+ ): Promise<string> => {
36
+ return fs.readFileSync(await normalizePath(filePath), encoding);
37
+ };
38
+
39
+ // Wrapper sécurisé pour fs.writeFileSync
40
+ const safeWriteFileSync = async (
41
+ filePath: string,
42
+ data: string,
43
+ ): Promise<void> => {
44
+ const normalizedPath = await normalizePath(filePath);
45
+ const dirPath = dirname(normalizedPath);
46
+
47
+ if (!fs.existsSync(dirPath)) {
48
+ fs.mkdirSync(dirPath, { recursive: true });
49
+ }
50
+
51
+ fs.writeFileSync(normalizedPath, data);
52
+ };
53
+
54
+ export const createOrUpdateFile = async (
55
+ filePath: string,
56
+ content: string,
57
+ ): Promise<void> => {
58
+ try {
59
+ const normalizedPath = normalizePath(filePath);
60
+
61
+ if (!(await safeExistsSync(filePath))) {
62
+ await safeWriteFileSync(filePath, content);
63
+ console.log(
64
+ chalk.green(`📄 Created`) +
65
+ chalk.gray(` • `) +
66
+ chalk.cyan(normalizedPath),
67
+ );
68
+ } else {
69
+ console.log(
70
+ chalk.blue(`📝 Exists`) +
71
+ chalk.gray(` • `) +
72
+ chalk.cyan(normalizedPath),
73
+ );
74
+ await replaceFileContent(filePath, /[\s\S]*/, content);
75
+ }
76
+ } catch (error) {
77
+ console.error(
78
+ chalk.red(`🚨 Failed to create file`) +
79
+ chalk.gray(` • `) +
80
+ chalk.cyan(filePath),
81
+ );
82
+ console.error(
83
+ chalk.gray(` `) +
84
+ chalk.red(error instanceof Error ? error.message : error),
85
+ );
86
+ }
87
+ };
88
+
89
+ export const getFileContent = async (
90
+ filePath: string,
91
+ searchPattern?: RegExp | string,
92
+ ): Promise<string | false | null> => {
93
+ try {
94
+ const normalizedPath = normalizePath(filePath);
95
+
96
+ // Vérifier si le fichier existe
97
+ if (!(await safeExistsSync(filePath))) {
98
+ console.error(
99
+ chalk.red(`❌ File not found`) +
100
+ chalk.gray(` • `) +
101
+ chalk.cyan(normalizedPath),
102
+ );
103
+ return null;
104
+ }
105
+
106
+ // Lire le contenu du fichier entier
107
+ const fileContent = await safeReadFileSync(filePath);
108
+
109
+ // Si un motif est fourni, chercher le texte correspondant
110
+ if (searchPattern) {
111
+ if (typeof searchPattern === 'string') {
112
+ const found = fileContent.includes(searchPattern)
113
+ ? searchPattern
114
+ : false;
115
+ if (found) {
116
+ console.log(
117
+ chalk.green(`🔍 Found`) +
118
+ chalk.gray(` • `) +
119
+ chalk.yellow(`"${searchPattern}"`),
120
+ );
121
+ } else {
122
+ console.log(
123
+ chalk.yellow(`🔍 Missing`) +
124
+ chalk.gray(` • `) +
125
+ chalk.yellow(`"${searchPattern}"`),
126
+ );
127
+ }
128
+ return found;
129
+ } else {
130
+ const match = fileContent.match(searchPattern);
131
+ if (match) {
132
+ console.log(
133
+ chalk.green(`🎯 Match`) +
134
+ chalk.gray(` • `) +
135
+ chalk.yellow(`"${match[0]}"`),
136
+ );
137
+ return match[0];
138
+ } else {
139
+ console.log(
140
+ chalk.yellow(`🎯 No match`) +
141
+ chalk.gray(` • `) +
142
+ chalk.magenta(searchPattern.toString()),
143
+ );
144
+ return false;
145
+ }
146
+ }
147
+ }
148
+
149
+ console.log(
150
+ chalk.blue(`📖 Read`) + chalk.gray(` • `) + chalk.cyan(normalizedPath),
151
+ );
152
+ return fileContent;
153
+ } catch (error) {
154
+ console.error(
155
+ chalk.red(`🚨 Read failed`) + chalk.gray(` • `) + chalk.cyan(filePath),
156
+ );
157
+ console.error(
158
+ chalk.gray(` `) +
159
+ chalk.red(error instanceof Error ? error.message : error),
160
+ );
161
+ return null;
162
+ }
163
+ };
164
+
165
+ export const replaceFileContent = async (
166
+ filePath: string,
167
+ searchPattern: RegExp | string,
168
+ replacement: string,
169
+ ): Promise<void> => {
170
+ try {
171
+ const { replaceInFileSync } = await import('replace-in-file');
172
+
173
+ const normalizedPath = await normalizePath(filePath);
174
+
175
+ const results = replaceInFileSync({
176
+ files: normalizedPath,
177
+ from: searchPattern,
178
+ to: replacement,
179
+ });
180
+
181
+ if (results.length > 0 && results[0].hasChanged) {
182
+ console.log(
183
+ chalk.green(`✏️ Updated`) +
184
+ chalk.gray(` • `) +
185
+ chalk.cyan(normalizedPath),
186
+ );
187
+ } else {
188
+ console.log(
189
+ chalk.yellow(`⏭️ Skipped`) +
190
+ chalk.gray(` • `) +
191
+ chalk.cyan(normalizedPath) +
192
+ chalk.gray(` (no changes needed)`),
193
+ );
194
+ }
195
+ } catch (error) {
196
+ console.error(
197
+ chalk.red(`🚨 Update failed`) + chalk.gray(` • `) + chalk.cyan(filePath),
198
+ );
199
+ console.error(
200
+ chalk.gray(` `) +
201
+ chalk.red(error instanceof Error ? error.message : error),
202
+ );
203
+ }
204
+ };
205
+
206
+ export const findTailwindCssFile = async (
207
+ startDir: string,
208
+ searchPattern: RegExp | string,
209
+ ): Promise<string | never> => {
210
+ const normalizedStartDir = await normalizePath(startDir);
211
+ console.log(chalk.blue(`🔎 Searching for CSS file...`));
212
+ console.log(
213
+ chalk.gray(` Starting from: `) + chalk.cyan(normalizedStartDir),
214
+ );
215
+
216
+ const stack = [normalizedStartDir];
217
+ let filesScanned = 0;
218
+
219
+ while (stack.length > 0) {
220
+ const currentDir = stack.pop()!;
221
+
222
+ let files: string[];
223
+ try {
224
+ files = fs.readdirSync(currentDir);
225
+ } catch (error) {
226
+ console.error(
227
+ chalk.gray(` `) +
228
+ chalk.red(`❌ Cannot read directory: `) +
229
+ chalk.cyan(currentDir),
230
+ );
231
+ continue;
232
+ }
233
+
234
+ for (const file of files) {
235
+ const filePath = join(currentDir, file);
236
+
237
+ let stats: fs.Stats;
238
+ try {
239
+ stats = fs.statSync(filePath);
240
+ } catch (error) {
241
+ console.error(
242
+ chalk.gray(` `) +
243
+ chalk.red(`❌ Cannot access: `) +
244
+ chalk.cyan(filePath),
245
+ );
246
+ continue;
247
+ }
248
+
249
+ if (stats.isDirectory()) {
250
+ if (file !== 'node_modules' && !file.startsWith('.')) {
251
+ stack.push(filePath);
252
+ }
253
+ } else if (
254
+ stats.isFile() &&
255
+ (file.endsWith('.css') ||
256
+ file.endsWith('.scss') ||
257
+ file.endsWith('.sass'))
258
+ ) {
259
+ try {
260
+ filesScanned++;
261
+ process.stdout.write(
262
+ chalk.gray(` 📂 Scanning: `) + chalk.yellow(file) + `\r`,
263
+ );
264
+
265
+ const content = await safeReadFileSync(filePath);
266
+
267
+ const hasMatch =
268
+ typeof searchPattern === 'string'
269
+ ? content.includes(searchPattern)
270
+ : searchPattern.test(content);
271
+
272
+ if (hasMatch) {
273
+ console.log(chalk.green(`\n🎯 Found target file!`));
274
+ console.log(chalk.gray(` 📍 Location: `) + chalk.cyan(filePath));
275
+ return filePath;
276
+ }
277
+ } catch (readError) {
278
+ console.error(
279
+ chalk.gray(`\n `) +
280
+ chalk.red(`❌ Cannot read: `) +
281
+ chalk.cyan(filePath),
282
+ );
283
+ }
284
+ }
285
+ }
286
+ }
287
+
288
+ console.log(
289
+ chalk.blue(`\n📊 Scanned `) +
290
+ chalk.white.bold(filesScanned.toString()) +
291
+ chalk.blue(` CSS files`),
292
+ );
293
+
294
+ const errorMsg =
295
+ chalk.red(`❌ No file found containing `) +
296
+ chalk.yellow(`"${searchPattern}"`) +
297
+ chalk.red(` in `) +
298
+ chalk.cyan(`"${normalizedStartDir}"`);
299
+
300
+ throw new Error(errorMsg);
301
+ };
302
+
303
+ export async function findProjectRoot(startPath: string): Promise<string> {
304
+ const normalizedStartPath = await normalizePath(startPath);
305
+ let currentPath = resolve(normalizedStartPath);
306
+ let levels = 0;
307
+
308
+ console.log(chalk.blue(`🏠 Finding project root...`));
309
+ console.log(
310
+ chalk.gray(` Starting from: `) + chalk.cyan(normalizedStartPath),
311
+ );
312
+
313
+ while (!fs.existsSync(join(currentPath, 'package.json'))) {
314
+ const parentPath = dirname(currentPath);
315
+ if (currentPath === parentPath) {
316
+ console.error(
317
+ chalk.red(`❌ Project root not found after checking `) +
318
+ chalk.white.bold(levels.toString()) +
319
+ chalk.red(` levels`),
320
+ );
321
+ throw new Error(
322
+ chalk.red('Unable to locate project root (no package.json found)'),
323
+ );
324
+ }
325
+ currentPath = parentPath;
326
+ levels++;
327
+
328
+ if (levels > 10) {
329
+ console.error(
330
+ chalk.red(`❌ Stopped after `) +
331
+ chalk.white.bold(levels.toString()) +
332
+ chalk.red(` levels (too deep)`),
333
+ );
334
+ throw new Error(chalk.red('Project root search exceeded maximum depth'));
335
+ }
336
+ }
337
+
338
+ console.log(chalk.green(`📁 Project root: `) + chalk.cyan(currentPath));
339
+ return currentPath;
340
+ }
@@ -0,0 +1,2 @@
1
+ export * from './tailwind.plugin';
2
+ export * from './file';
@@ -1,20 +1,8 @@
1
- import {
2
- createOrUpdateFile,
3
- findProjectRoot,
4
- findTailwindCssFile,
5
- getFileContent,
6
- replaceFileContent,
7
- } from './file';
8
- import path from 'path';
9
- import { FontPlugin, PluginAbstract, PluginImplAbstract } from '@udixio/theme';
10
- import { ConfigCss } from './main';
1
+ import { FontPlugin, PluginAbstract } from '@udixio/theme';
11
2
 
12
- interface TailwindPluginOptions {
13
- // darkMode?: 'class' | 'media';
14
- responsiveBreakPoints?: Record<string, number>;
15
- styleFilePath?: string;
16
- // subThemes?: Record<string, string>;
17
- }
3
+ import { TailwindImplPluginBrowser, TailwindPluginOptions } from '../browser/tailwind.plugin';
4
+
5
+ import { ConfigCss } from '../main';
18
6
 
19
7
  export class TailwindPlugin extends PluginAbstract<
20
8
  TailwindImplPlugin,
@@ -25,46 +13,59 @@ export class TailwindPlugin extends PluginAbstract<
25
13
  pluginClass = TailwindImplPlugin;
26
14
  }
27
15
 
28
- class TailwindImplPlugin extends PluginImplAbstract<TailwindPluginOptions> {
29
- onInit() {
30
- this.options.responsiveBreakPoints ??= {
31
- lg: 1.125,
32
- };
16
+ class TailwindImplPlugin extends TailwindImplPluginBrowser {
17
+ private isNodeJs(): boolean {
18
+ return (
19
+ typeof process !== 'undefined' &&
20
+ process.versions != null &&
21
+ process.versions.node != null
22
+ );
33
23
  }
34
24
 
35
- onLoad() {
36
- let udixioCssPath = this.options.styleFilePath;
37
-
38
- const projectRoot = findProjectRoot(path.resolve());
39
-
40
- if (!udixioCssPath) {
41
- const searchPattern = /@import ["']tailwindcss["'];/;
42
- const replacement = `@import 'tailwindcss';\n@import "./udixio.css";`;
43
-
44
- const tailwindCssPath = findTailwindCssFile(projectRoot, searchPattern);
45
- udixioCssPath = path.join(tailwindCssPath, '../udixio.css');
46
-
47
- if (!getFileContent(tailwindCssPath, /@import\s+"\.\/udixio\.css";/)) {
48
- replaceFileContent(tailwindCssPath, searchPattern, replacement);
49
- }
25
+ override async onLoad() {
26
+ if (!this.isNodeJs()) {
27
+ await super.onLoad();
28
+ return;
50
29
  }
30
+ const { join, resolve } = await import('pathe');
51
31
 
52
- const colors: Record<
53
- string,
54
- {
55
- light: string;
56
- dark: string;
57
- }
58
- > = {};
59
-
32
+ const {
33
+ createOrUpdateFile,
34
+ findProjectRoot,
35
+ findTailwindCssFile,
36
+ getFileContent,
37
+ replaceFileContent,
38
+ } = await import('./file');
39
+ this.colors = {};
60
40
  for (const isDark of [false, true]) {
61
41
  this.api.themes.update({ isDark: isDark });
62
42
  for (const [key, value] of this.api.colors.getColors().entries()) {
63
43
  const newKey = key
64
44
  .replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1-$2')
65
45
  .toLowerCase();
66
- colors[newKey] ??= { light: '', dark: '' };
67
- colors[newKey][isDark ? 'dark' : 'light'] = value.getHex();
46
+ this.colors[newKey] ??= { light: '', dark: '' };
47
+ this.colors[newKey][isDark ? 'dark' : 'light'] = value.getHex();
48
+ }
49
+ }
50
+
51
+ let udixioCssPath = this.options.styleFilePath;
52
+
53
+ const projectRoot = await findProjectRoot(resolve());
54
+
55
+ if (!udixioCssPath) {
56
+ const searchPattern = /@import ["']tailwindcss["'];/;
57
+ const replacement = `@import 'tailwindcss';\n@import "./udixio.css";`;
58
+
59
+ const tailwindCssPath = await findTailwindCssFile(
60
+ projectRoot,
61
+ searchPattern,
62
+ );
63
+ udixioCssPath = join(tailwindCssPath, '../udixio.css');
64
+
65
+ if (
66
+ !(await getFileContent(tailwindCssPath, /@import\s+"\.\/udixio\.css";/))
67
+ ) {
68
+ await replaceFileContent(tailwindCssPath, searchPattern, replacement);
68
69
  }
69
70
  }
70
71
 
@@ -74,7 +75,7 @@ class TailwindImplPlugin extends PluginImplAbstract<TailwindPluginOptions> {
74
75
  .getFonts();
75
76
 
76
77
  const configCss: ConfigCss = {
77
- colorKeys: Object.keys(colors).join(', '),
78
+ colorKeys: Object.keys(this.colors).join(', ') as any,
78
79
  fontStyles: Object.entries(fontStyles)
79
80
  .map(([fontRole, fontStyle]) =>
80
81
  Object.entries(fontStyle)
@@ -86,36 +87,21 @@ class TailwindImplPlugin extends PluginImplAbstract<TailwindPluginOptions> {
86
87
  )
87
88
  .join(', '),
88
89
  )
89
- .join(', '),
90
+ .join(', ') as any,
90
91
  responsiveBreakPoints: Object.entries(
91
92
  this.options.responsiveBreakPoints ?? {},
92
93
  )
93
94
  .map(([key, value]) => `${key} ${value}`)
94
- .join(', '),
95
+ .join(', ') as any,
95
96
  };
96
97
 
97
- createOrUpdateFile(
98
- udixioCssPath,
99
- `
100
- @plugin "@udixio/tailwind" {
98
+ this.outputCss += `@plugin "@udixio/tailwind" {
101
99
  colorKeys: ${configCss.colorKeys};
102
100
  fontStyles: ${configCss.fontStyles};
103
101
  responsiveBreakPoints: ${configCss.responsiveBreakPoints};
104
- }
105
- @custom-variant dark (&:where(.dark, .dark *));
106
- @theme {
107
- --color-*: initial;
108
- ${Object.entries(colors)
109
- .map(([key, value]) => `--color-${key}: ${value.light};`)
110
- .join('\n ')}
111
- }
112
- @layer theme {
113
- .dark {
114
- ${Object.entries(colors)
115
- .map(([key, value]) => `--color-${key}: ${value.dark};`)
116
- .join('\n ')}
117
- }
118
- }
102
+ }`;
103
+ this.loadColor();
104
+ this.outputCss += `
119
105
  @theme {
120
106
  ${Object.entries(fontFamily)
121
107
  .map(
@@ -123,8 +109,8 @@ class TailwindImplPlugin extends PluginImplAbstract<TailwindPluginOptions> {
123
109
  `--font-${key}: ${values.map((value) => `"${value}"`).join(', ')};`,
124
110
  )
125
111
  .join('\n ')}
126
- }
127
- `,
128
- );
112
+ }`;
113
+
114
+ await createOrUpdateFile(udixioCssPath, this.outputCss);
129
115
  }
130
116
  }
package/vite.config.ts CHANGED
@@ -2,6 +2,7 @@
2
2
  import { defineConfig } from 'vite';
3
3
  import dts from 'vite-plugin-dts';
4
4
  import * as path from 'path';
5
+ import { visualizer } from 'rollup-plugin-visualizer';
5
6
 
6
7
  export default defineConfig(() => ({
7
8
  root: __dirname,
@@ -11,6 +12,12 @@ export default defineConfig(() => ({
11
12
  entryRoot: 'src',
12
13
  tsconfigPath: path.join(__dirname, 'tsconfig.lib.json'),
13
14
  }),
15
+ visualizer({
16
+ filename: '../../stats/tailwind.html',
17
+ open: false,
18
+ gzipSize: true,
19
+ brotliSize: true,
20
+ }),
14
21
  ],
15
22
  // Uncomment this if you are using workers.
16
23
  // worker: {
@@ -19,25 +26,35 @@ export default defineConfig(() => ({
19
26
  // Configuration for building your library.
20
27
  // See: https://vitejs.dev/guide/build.html#library-mode
21
28
  build: {
29
+ ssr: true,
22
30
  outDir: './dist',
23
31
  emptyOutDir: true,
24
32
  reportCompressedSize: true,
25
- ssr: true,
26
33
  commonjsOptions: {
27
34
  transformMixedEsModules: true,
28
35
  },
29
36
  lib: {
30
37
  // Could also be a dictionary or array of multiple entry points.
31
- entry: 'src/index.ts',
38
+ entry: {
39
+ node: 'src/index.node.ts',
40
+ browser: 'src/index.browser.ts',
41
+ },
32
42
  name: '@udixio/tailwind',
33
- fileName: 'index',
43
+ fileName: (format, entryName) =>
44
+ `${entryName}.${format === 'es' ? 'js' : 'cjs'}`,
34
45
  // Change this to the formats you want to support.
35
46
  // Don't forget to update your package.json as well.
36
47
  formats: ['es' as const, 'cjs' as const],
37
48
  },
38
49
  rollupOptions: {
39
50
  // External packages that should not be bundled into your library.
40
- external: ['tailwindcss', '@udixio/theme', 'pathe'],
51
+ external: [
52
+ 'tailwindcss',
53
+ '@udixio/theme',
54
+ 'pathe',
55
+ 'replace-in-file',
56
+ 'chalk',
57
+ ],
41
58
  },
42
59
  },
43
60
  test: {
package/dist/file.d.ts DELETED
@@ -1,6 +0,0 @@
1
- export declare const createOrUpdateFile: (filePath: string, content: string) => void;
2
- export declare const getFileContent: (filePath: string, searchPattern?: RegExp | string) => string | false | null;
3
- export declare const replaceFileContent: (filePath: string, searchPattern: RegExp | string, replacement: string) => void;
4
- export declare const findTailwindCssFile: (startDir: string, searchPattern: RegExp | string) => string | never;
5
- export declare function findProjectRoot(startPath: string): string;
6
- //# sourceMappingURL=file.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"file.d.ts","sourceRoot":"","sources":["../src/file.ts"],"names":[],"mappings":"AA8CA,eAAO,MAAM,kBAAkB,GAAI,UAAU,MAAM,EAAE,SAAS,MAAM,KAAG,IActE,CAAC;AAEF,eAAO,MAAM,cAAc,GACzB,UAAU,MAAM,EAChB,gBAAgB,MAAM,GAAG,MAAM,KAC9B,MAAM,GAAG,KAAK,GAAG,IA8CnB,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAC7B,UAAU,MAAM,EAChB,eAAe,MAAM,GAAG,MAAM,EAC9B,aAAa,MAAM,KAClB,IAsBF,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAC9B,UAAU,MAAM,EAChB,eAAe,MAAM,GAAG,MAAM,KAC7B,MAAM,GAAG,KAkEX,CAAC;AAEF,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAczD"}