@csszyx/cli 0.9.9 → 0.10.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.
package/dist/index.mjs CHANGED
@@ -1,4831 +1,9 @@
1
- #!/usr/bin/env node
2
- import fs$1, { existsSync, writeFileSync, readFileSync } from 'node:fs';
3
- import cac from 'cac';
4
- import * as path from 'node:path';
5
- import path__default, { resolve, dirname } from 'node:path';
6
- import fs from 'fs-extra';
7
- import ora from 'ora';
8
- import pc from 'picocolors';
9
- import { mkdir } from 'node:fs/promises';
10
- import { pathToFileURL } from 'node:url';
11
- import resolveConfig from 'tailwindcss/resolveConfig.js';
12
- import { execa } from 'execa';
13
- import prompts from 'prompts';
14
- import readline from 'node:readline';
15
- import fg from 'fast-glob';
16
- import { parse } from '@babel/parser';
17
- import * as t from '@babel/types';
18
- import { runNextPrebuild } from '@csszyx/unplugin/next-prebuild';
19
- import { NextSafelistWatcher } from '@csszyx/unplugin/next-watcher';
20
- import { watch } from 'chokidar';
21
- import { Minimatch } from 'minimatch';
22
-
23
- const colors = {
24
- success: pc.green,
25
- error: pc.red,
26
- warn: pc.yellow,
27
- info: pc.cyan,
28
- dim: pc.dim,
29
- bold: pc.bold
30
- };
31
- const icons = {
32
- success: "\u2713",
33
- error: "\u2717",
34
- warn: "\u26A0",
35
- info: "\u2139"
36
- };
37
- function printHeader(title) {
38
- const width = 48;
39
- const padding = Math.max(0, width - title.length - 4);
40
- console.log(pc.cyan(`\u250C${"\u2500".repeat(width - 2)}\u2510`));
41
- console.log(`${pc.cyan("\u2502")} ${pc.bold(title)}${" ".repeat(padding)}${pc.cyan("\u2502")}`);
42
- console.log(pc.cyan(`\u2514${"\u2500".repeat(width - 2)}\u2518`));
43
- console.log();
44
- }
45
- function printSection(title) {
46
- console.log();
47
- console.log(pc.bold(title));
48
- console.log(pc.dim("\u2501".repeat(48)));
49
- }
50
- function printSuccess(message) {
51
- console.log(colors.success(`${icons.success} ${message}`));
52
- }
53
- function printError(message) {
54
- console.log(colors.error(`${icons.error} ${message}`));
55
- }
56
- function printWarn(message) {
57
- console.log(colors.warn(`${icons.warn} ${message}`));
58
- }
59
- function printInfo(message) {
60
- console.log(colors.info(`${icons.info} ${message}`));
61
- }
62
- const spinner = {
63
- start(text) {
64
- return ora(text).start();
65
- },
66
- succeed(spinner2, text) {
67
- spinner2.succeed(colors.success(text));
68
- },
69
- fail(spinner2, text) {
70
- spinner2.fail(colors.error(text));
71
- },
72
- warn(spinner2, text) {
73
- spinner2.warn(colors.warn(text));
74
- }
75
- };
76
- function printBar(values, max, width = 20) {
77
- const filled = Math.round(values.reduce((a, b) => a + b, 0) / max * width);
78
- return "\u25A0".repeat(filled) + "\u25A1".repeat(width - filled);
79
- }
80
-
81
- async function audit(options = {}) {
82
- const cwd = options.cwd || process.cwd();
83
- const stats = await collectStats(cwd);
84
- if (options.json) {
85
- console.log(JSON.stringify(stats, null, 2));
86
- return;
87
- }
88
- printHeader("csszyx Audit Report");
89
- printSection("\u{1F4CA} Mangle Statistics");
90
- if (stats.totalClasses === 0) {
91
- console.log(" Tier distribution not yet available.");
92
- console.log(" Run a production build first, then re-run csszyx audit.");
93
- } else {
94
- console.log(` Total Classes: ${stats.totalClasses}`);
95
- console.log(` Mangled Classes: ${stats.totalClasses} (100%)`);
96
- console.log(" Unmangled Classes: 0");
97
- console.log();
98
- console.log(" Tier Distribution:");
99
- const tierNames = [
100
- "Tier 1 (a-Z)",
101
- "Tier 2 (a0-Z9)",
102
- "Tier 3 (aa-ZZ)",
103
- "Tier 4 (a00-Z99)",
104
- "Tier 5 (aaa+)"
105
- ];
106
- for (let i = 1; i <= 5; i++) {
107
- const count = stats.tierDistribution[i] || 0;
108
- const percent = stats.totalClasses ? Math.round(count / stats.totalClasses * 100) : 0;
109
- const bar = printBar([count], stats.totalClasses, 20);
110
- console.log(
111
- ` \u2022 ${tierNames[i - 1].padEnd(18)} ${String(count).padStart(3)} (${String(percent).padStart(2)}%) ${colors.dim(bar)}`
112
- );
113
- }
114
- }
115
- printSection("\u{1F4BE} Bundle Size Impact");
116
- if (stats.bundleSavings.originalHTML > 0) {
117
- const htmlSavings = stats.bundleSavings.originalHTML - stats.bundleSavings.mangledHTML;
118
- const htmlPercent = Math.round(htmlSavings / stats.bundleSavings.originalHTML * 100);
119
- console.log(` Original HTML: ${formatBytes(stats.bundleSavings.originalHTML)}`);
120
- console.log(
121
- ` Mangled HTML: ${formatBytes(stats.bundleSavings.mangledHTML)} \u2193 ${htmlPercent}% (-${formatBytes(htmlSavings)})`
122
- );
123
- console.log();
124
- }
125
- if (stats.bundleSavings.originalCSS > 0) {
126
- const cssSavings = stats.bundleSavings.originalCSS - stats.bundleSavings.mangledCSS;
127
- const cssPercent = Math.round(cssSavings / stats.bundleSavings.originalCSS * 100);
128
- console.log(` Original CSS: ${formatBytes(stats.bundleSavings.originalCSS)}`);
129
- console.log(
130
- ` Mangled CSS: ${formatBytes(stats.bundleSavings.mangledCSS)} \u2193 ${cssPercent}% (-${formatBytes(cssSavings)})`
131
- );
132
- }
133
- console.log();
134
- printInfo("\u{1F4A1} Tip: Enable runtime lite bundle for -1.1KB");
135
- console.log(" \u2192 import { _sz } from 'csszyx/lite'");
136
- }
137
- async function collectStats(cwd) {
138
- const stats = {
139
- totalClasses: 0,
140
- tierDistribution: {},
141
- bundleSavings: {
142
- originalHTML: 0,
143
- mangledHTML: 0,
144
- originalCSS: 0,
145
- mangledCSS: 0
146
- }
147
- };
148
- const distDir = path__default.join(cwd, "dist");
149
- if (!fs.existsSync(distDir)) {
150
- return stats;
151
- }
152
- const htmlFiles = fs.readdirSync(distDir, { recursive: true }).filter((f) => String(f).endsWith(".html"));
153
- const cssFiles = fs.readdirSync(distDir, { recursive: true }).filter((f) => String(f).endsWith(".css"));
154
- if (htmlFiles.length > 0) {
155
- const htmlContent = fs.readFileSync(path__default.join(distDir, String(htmlFiles[0])), "utf-8");
156
- stats.bundleSavings.mangledHTML = Buffer.byteLength(htmlContent);
157
- stats.bundleSavings.originalHTML = Math.round(stats.bundleSavings.mangledHTML * 1.67);
158
- }
159
- if (cssFiles.length > 0) {
160
- const cssContent = fs.readFileSync(path__default.join(distDir, String(cssFiles[0])), "utf-8");
161
- stats.bundleSavings.mangledCSS = Buffer.byteLength(cssContent);
162
- stats.bundleSavings.originalCSS = Math.round(stats.bundleSavings.mangledCSS * 1.71);
163
- }
164
- return stats;
165
- }
166
- function formatBytes(bytes) {
167
- if (bytes === 0) {
168
- return "0 B";
169
- }
170
- if (bytes < 1024) {
171
- return `${bytes} B`;
172
- }
173
- if (bytes < 1024 * 1024) {
174
- return `${(bytes / 1024).toFixed(1)} KB`;
175
- }
176
- return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
177
- }
178
-
179
- function detectFramework(cwd) {
180
- try {
181
- const pkgPath = path__default.join(cwd, "package.json");
182
- if (!fs.existsSync(pkgPath)) {
183
- return "unknown";
184
- }
185
- const pkg = fs.readJSONSync(pkgPath);
186
- const deps = { ...pkg.dependencies, ...pkg.devDependencies };
187
- if (deps.next) {
188
- const hasAppDir = fs.existsSync(path__default.join(cwd, "app"));
189
- return hasAppDir ? "nextjs-app" : "nextjs-pages";
190
- }
191
- if (deps.nuxt) {
192
- return "nuxt";
193
- }
194
- if (deps["@sveltejs/kit"]) {
195
- return "sveltekit";
196
- }
197
- if (deps.astro) {
198
- return "astro";
199
- }
200
- if (deps.vite) {
201
- if (deps.react || deps["react-dom"]) {
202
- return "vite-react";
203
- }
204
- if (deps.vue) {
205
- return "vite-vue";
206
- }
207
- if (deps.svelte) {
208
- return "vite-svelte";
209
- }
210
- }
211
- return "unknown";
212
- } catch {
213
- return "unknown";
214
- }
215
- }
216
- function detectPackageManager(cwd) {
217
- if (fs.existsSync(path__default.join(cwd, "pnpm-lock.yaml"))) {
218
- return "pnpm";
219
- }
220
- if (fs.existsSync(path__default.join(cwd, "yarn.lock"))) {
221
- return "yarn";
222
- }
223
- if (fs.existsSync(path__default.join(cwd, "bun.lockb"))) {
224
- return "bun";
225
- }
226
- return "npm";
227
- }
228
- function hasTailwindInstalled(cwd) {
229
- try {
230
- const pkgPath = path__default.join(cwd, "package.json");
231
- if (!fs.existsSync(pkgPath)) {
232
- return false;
233
- }
234
- const pkg = fs.readJSONSync(pkgPath);
235
- const deps = { ...pkg.dependencies, ...pkg.devDependencies };
236
- return !!deps.tailwindcss;
237
- } catch {
238
- return false;
239
- }
240
- }
241
- function hasTypeScript(cwd) {
242
- return fs.existsSync(path__default.join(cwd, "tsconfig.json")) || fs.existsSync(path__default.join(cwd, "jsconfig.json"));
243
- }
244
- function getProjectInfo(cwd = process.cwd()) {
245
- return {
246
- framework: detectFramework(cwd),
247
- packageManager: detectPackageManager(cwd),
248
- hasTailwind: hasTailwindInstalled(cwd),
249
- hasTypeScript: hasTypeScript(cwd),
250
- rootDir: cwd
251
- };
252
- }
253
- function getFrameworkName(framework) {
254
- const names = {
255
- "vite-react": "Vite + React",
256
- "vite-vue": "Vite + Vue",
257
- "vite-svelte": "Vite + Svelte",
258
- "nextjs-app": "Next.js (App Router)",
259
- "nextjs-pages": "Next.js (Pages Router)",
260
- nuxt: "Nuxt 3",
261
- sveltekit: "SvelteKit",
262
- astro: "Astro",
263
- unknown: "Unknown"
264
- };
265
- return names[framework];
266
- }
267
-
268
- async function doctor(options = {}) {
269
- const cwd = options.cwd || process.cwd();
270
- const projectInfo = getProjectInfo(cwd);
271
- printHeader("csszyx Doctor");
272
- let issueCount = 0;
273
- printSection("\u{1F4CB} Configuration Health");
274
- const hasConfig = fs.existsSync(path__default.join(cwd, "csszyx.config.ts")) || fs.existsSync(path__default.join(cwd, "csszyx.config.js"));
275
- if (hasConfig) {
276
- printSuccess("csszyx configuration found");
277
- } else {
278
- printWarn("No csszyx.config found - using defaults");
279
- }
280
- if (projectInfo.hasTailwind) {
281
- printSuccess("Tailwind CSS installed");
282
- } else {
283
- printError("Tailwind CSS not found");
284
- issueCount++;
285
- if (options.verbose) {
286
- console.log(" \u2192 Run: npm install -D tailwindcss");
287
- }
288
- }
289
- printSection("\u{1F4E6} Package Versions");
290
- try {
291
- const pkgPath = path__default.join(cwd, "package.json");
292
- const pkg = fs.readJSONSync(pkgPath);
293
- const deps = { ...pkg.dependencies, ...pkg.devDependencies };
294
- if (deps.csszyx) {
295
- printSuccess(`csszyx: ${deps.csszyx}`);
296
- } else {
297
- printError("csszyx package not installed");
298
- issueCount++;
299
- }
300
- } catch {
301
- printError("Failed to read package.json");
302
- issueCount++;
303
- }
304
- printSection("\u{1F528} Build Output");
305
- const distDir = path__default.join(cwd, "dist");
306
- if (fs.existsSync(distDir)) {
307
- const htmlFiles = fs.readdirSync(distDir, { recursive: true }).filter((f) => String(f).endsWith(".html"));
308
- if (htmlFiles.length > 0) {
309
- printSuccess(`Found ${htmlFiles.length} HTML file(s)`);
310
- const htmlContent = fs.readFileSync(path__default.join(distDir, String(htmlFiles[0])), "utf-8");
311
- if (htmlContent.includes("data-sz-checksum")) {
312
- printSuccess("Checksum injection working");
313
- } else {
314
- printWarn("Checksum not found in HTML");
315
- if (options.verbose) {
316
- console.log(" \u2192 Enable injectChecksum in production config");
317
- }
318
- }
319
- }
320
- } else {
321
- printWarn("No build output found - run build first");
322
- }
323
- console.log();
324
- if (issueCount === 0) {
325
- printSuccess("\u2728 No issues found! Your setup looks good.");
326
- } else {
327
- printWarn(`Found ${issueCount} issue(s)`);
328
- }
329
- }
330
-
331
- const CONFIG_FILES = [
332
- "tailwind.config.ts",
333
- "tailwind.config.js",
334
- "tailwind.config.cjs",
335
- "tailwind.config.mjs"
336
- ];
337
- function findConfigFile(cwd) {
338
- for (const fileName of CONFIG_FILES) {
339
- const configPath = resolve(cwd, fileName);
340
- if (existsSync(configPath)) {
341
- return configPath;
342
- }
343
- }
344
- return null;
345
- }
346
- async function scanTailwindConfig(configPath) {
347
- const absolutePath = resolve(configPath);
348
- if (!existsSync(absolutePath)) {
349
- throw new Error(`Tailwind config not found: ${absolutePath}`);
350
- }
351
- let userConfig;
352
- try {
353
- const fileUrl = pathToFileURL(absolutePath).href;
354
- const module = await import(fileUrl);
355
- userConfig = module.default || module;
356
- } catch (error) {
357
- throw new Error(
358
- `Failed to load Tailwind config: ${error instanceof Error ? error.message : String(error)}`
359
- );
360
- }
361
- const resolvedConfig = resolveConfig(userConfig);
362
- const theme = resolvedConfig.theme;
363
- const hasCustomColors = Boolean(userConfig.theme?.colors || userConfig.theme?.extend?.colors);
364
- const hasCustomSpacing = Boolean(
365
- userConfig.theme?.spacing || userConfig.theme?.extend?.spacing
366
- );
367
- return {
368
- theme,
369
- configPath: absolutePath,
370
- hasCustomColors,
371
- hasCustomSpacing
372
- };
373
- }
374
- function flattenColors(colors) {
375
- const result = [];
376
- for (const [key, value] of Object.entries(colors)) {
377
- if (key === "inherit" || key === "current" || key === "transparent") {
378
- result.push(key);
379
- continue;
380
- }
381
- if (typeof value === "string") {
382
- result.push(key);
383
- } else if (typeof value === "object" && value !== null) {
384
- for (const shade of Object.keys(value)) {
385
- if (shade === "DEFAULT") {
386
- result.push(key);
387
- } else {
388
- result.push(`${key}-${shade}`);
389
- }
390
- }
391
- }
392
- }
393
- return result.sort();
394
- }
395
- function extractSpacingKeys(spacing) {
396
- return Object.keys(spacing).sort((a, b) => {
397
- const aNum = parseFloat(a);
398
- const bNum = parseFloat(b);
399
- if (!Number.isNaN(aNum) && !Number.isNaN(bNum)) {
400
- return aNum - bNum;
401
- }
402
- if (!Number.isNaN(aNum)) {
403
- return -1;
404
- }
405
- if (!Number.isNaN(bNum)) {
406
- return 1;
407
- }
408
- return a.localeCompare(b);
409
- });
410
- }
411
- function extractScreenKeys(screens) {
412
- return Object.keys(screens);
413
- }
414
-
415
- const PROPERTY_MAPPINGS = [
416
- // Layout
417
- {
418
- prop: "display",
419
- prefix: "",
420
- valueType: "display",
421
- description: "CSS display property"
422
- },
423
- {
424
- prop: "position",
425
- prefix: "",
426
- valueType: "position",
427
- description: "CSS position property"
428
- },
429
- {
430
- prop: "overflow",
431
- prefix: "overflow",
432
- valueType: "overflow",
433
- description: "CSS overflow property"
434
- },
435
- {
436
- prop: "z",
437
- prefix: "z",
438
- valueType: "zIndex",
439
- description: "CSS z-index property"
440
- },
441
- // Flexbox & Grid
442
- {
443
- prop: "flex",
444
- prefix: "flex",
445
- valueType: "flex",
446
- description: "Flex shorthand"
447
- },
448
- {
449
- prop: "flexDir",
450
- prefix: "flex",
451
- valueType: "flexDirection",
452
- description: "Flex direction"
453
- },
454
- {
455
- prop: "justify",
456
- prefix: "justify",
457
- valueType: "justify",
458
- description: "Justify content"
459
- },
460
- {
461
- prop: "items",
462
- prefix: "items",
463
- valueType: "items",
464
- description: "Align items"
465
- },
466
- {
467
- prop: "gap",
468
- prefix: "gap",
469
- valueType: "spacing",
470
- description: "Gap between flex/grid items"
471
- },
472
- {
473
- prop: "grid",
474
- prefix: "grid",
475
- valueType: "grid",
476
- description: "Grid shorthand"
477
- },
478
- {
479
- prop: "gridCols",
480
- prefix: "grid-cols",
481
- valueType: "gridCols",
482
- description: "Grid template columns"
483
- },
484
- {
485
- prop: "gridRows",
486
- prefix: "grid-rows",
487
- valueType: "gridRows",
488
- description: "Grid template rows"
489
- },
490
- {
491
- prop: "col",
492
- prefix: "col",
493
- valueType: "col",
494
- description: "Grid column span"
495
- },
496
- {
497
- prop: "row",
498
- prefix: "row",
499
- valueType: "row",
500
- description: "Grid row span"
501
- },
502
- // Spacing
503
- {
504
- prop: "p",
505
- prefix: "p",
506
- valueType: "spacing",
507
- responsive: true,
508
- description: "Padding (all sides)"
509
- },
510
- {
511
- prop: "px",
512
- prefix: "px",
513
- valueType: "spacing",
514
- responsive: true,
515
- description: "Padding horizontal"
516
- },
517
- {
518
- prop: "py",
519
- prefix: "py",
520
- valueType: "spacing",
521
- responsive: true,
522
- description: "Padding vertical"
523
- },
524
- {
525
- prop: "pt",
526
- prefix: "pt",
527
- valueType: "spacing",
528
- responsive: true,
529
- description: "Padding top"
530
- },
531
- {
532
- prop: "pr",
533
- prefix: "pr",
534
- valueType: "spacing",
535
- responsive: true,
536
- description: "Padding right"
537
- },
538
- {
539
- prop: "pb",
540
- prefix: "pb",
541
- valueType: "spacing",
542
- responsive: true,
543
- description: "Padding bottom"
544
- },
545
- {
546
- prop: "pl",
547
- prefix: "pl",
548
- valueType: "spacing",
549
- responsive: true,
550
- description: "Padding left"
551
- },
552
- {
553
- prop: "m",
554
- prefix: "m",
555
- valueType: "spacing",
556
- responsive: true,
557
- description: "Margin (all sides)"
558
- },
559
- {
560
- prop: "mx",
561
- prefix: "mx",
562
- valueType: "spacing",
563
- responsive: true,
564
- description: "Margin horizontal"
565
- },
566
- {
567
- prop: "my",
568
- prefix: "my",
569
- valueType: "spacing",
570
- responsive: true,
571
- description: "Margin vertical"
572
- },
573
- {
574
- prop: "mt",
575
- prefix: "mt",
576
- valueType: "spacing",
577
- responsive: true,
578
- description: "Margin top"
579
- },
580
- {
581
- prop: "mr",
582
- prefix: "mr",
583
- valueType: "spacing",
584
- responsive: true,
585
- description: "Margin right"
586
- },
587
- {
588
- prop: "mb",
589
- prefix: "mb",
590
- valueType: "spacing",
591
- responsive: true,
592
- description: "Margin bottom"
593
- },
594
- {
595
- prop: "ml",
596
- prefix: "ml",
597
- valueType: "spacing",
598
- responsive: true,
599
- description: "Margin left"
600
- },
601
- // Sizing
602
- {
603
- prop: "w",
604
- prefix: "w",
605
- valueType: "sizing",
606
- responsive: true,
607
- description: "Width"
608
- },
609
- {
610
- prop: "h",
611
- prefix: "h",
612
- valueType: "sizing",
613
- responsive: true,
614
- description: "Height"
615
- },
616
- {
617
- prop: "minW",
618
- prefix: "min-w",
619
- valueType: "minWidth",
620
- description: "Minimum width"
621
- },
622
- {
623
- prop: "maxW",
624
- prefix: "max-w",
625
- valueType: "maxWidth",
626
- description: "Maximum width"
627
- },
628
- {
629
- prop: "minH",
630
- prefix: "min-h",
631
- valueType: "minHeight",
632
- description: "Minimum height"
633
- },
634
- {
635
- prop: "maxH",
636
- prefix: "max-h",
637
- valueType: "maxHeight",
638
- description: "Maximum height"
639
- },
640
- // Colors
641
- {
642
- prop: "bg",
643
- prefix: "bg",
644
- valueType: "colors",
645
- stateful: true,
646
- description: "Background color"
647
- },
648
- {
649
- prop: "text",
650
- prefix: "text",
651
- valueType: "colors",
652
- stateful: true,
653
- description: "Text color"
654
- },
655
- {
656
- prop: "border",
657
- prefix: "border",
658
- valueType: "colors",
659
- stateful: true,
660
- description: "Border color"
661
- },
662
- {
663
- prop: "ring",
664
- prefix: "ring",
665
- valueType: "colors",
666
- stateful: true,
667
- description: "Ring color"
668
- },
669
- {
670
- prop: "fill",
671
- prefix: "fill",
672
- valueType: "colors",
673
- description: "SVG fill color"
674
- },
675
- {
676
- prop: "stroke",
677
- prefix: "stroke",
678
- valueType: "colors",
679
- description: "SVG stroke color"
680
- },
681
- // Typography
682
- {
683
- prop: "font",
684
- prefix: "font",
685
- valueType: "fontWeight",
686
- description: "Font weight"
687
- },
688
- {
689
- prop: "fontFamily",
690
- prefix: "font",
691
- valueType: "fontFamily",
692
- description: "Font family"
693
- },
694
- {
695
- prop: "fontSize",
696
- prefix: "text",
697
- valueType: "fontSize",
698
- description: "Font size"
699
- },
700
- {
701
- prop: "leading",
702
- prefix: "leading",
703
- valueType: "lineHeight",
704
- description: "Line height"
705
- },
706
- {
707
- prop: "tracking",
708
- prefix: "tracking",
709
- valueType: "letterSpacing",
710
- description: "Letter spacing"
711
- },
712
- {
713
- prop: "textAlign",
714
- prefix: "text",
715
- valueType: "textAlign",
716
- description: "Text alignment"
717
- },
718
- // Borders
719
- {
720
- prop: "rounded",
721
- prefix: "rounded",
722
- valueType: "borderRadius",
723
- description: "Border radius"
724
- },
725
- {
726
- prop: "borderW",
727
- prefix: "border",
728
- valueType: "borderWidth",
729
- description: "Border width"
730
- },
731
- // Effects
732
- {
733
- prop: "shadow",
734
- prefix: "shadow",
735
- valueType: "boxShadow",
736
- description: "Box shadow"
737
- },
738
- {
739
- prop: "opacity",
740
- prefix: "opacity",
741
- valueType: "opacity",
742
- description: "Opacity"
743
- },
744
- // Transforms
745
- {
746
- prop: "scale",
747
- prefix: "scale",
748
- valueType: "scale",
749
- description: "Scale transform"
750
- },
751
- {
752
- prop: "rotate",
753
- prefix: "rotate",
754
- valueType: "rotate",
755
- description: "Rotate transform"
756
- },
757
- {
758
- prop: "translate",
759
- prefix: "translate",
760
- valueType: "translate",
761
- description: "Translate transform"
762
- },
763
- // Transitions
764
- {
765
- prop: "transition",
766
- prefix: "transition",
767
- valueType: "transition",
768
- description: "Transition property"
769
- },
770
- {
771
- prop: "duration",
772
- prefix: "duration",
773
- valueType: "duration",
774
- description: "Transition duration"
775
- },
776
- {
777
- prop: "ease",
778
- prefix: "ease",
779
- valueType: "ease",
780
- description: "Transition timing function"
781
- }
782
- ];
783
- const STATIC_VALUE_TYPES = {
784
- display: [
785
- "block",
786
- "inline-block",
787
- "inline",
788
- "flex",
789
- "inline-flex",
790
- "grid",
791
- "inline-grid",
792
- "hidden",
793
- "contents",
794
- "flow-root"
795
- ],
796
- position: ["static", "fixed", "absolute", "relative", "sticky"],
797
- overflow: [
798
- "auto",
799
- "hidden",
800
- "clip",
801
- "visible",
802
- "scroll",
803
- "x-auto",
804
- "y-auto",
805
- "x-hidden",
806
- "y-hidden",
807
- "x-clip",
808
- "y-clip",
809
- "x-visible",
810
- "y-visible",
811
- "x-scroll",
812
- "y-scroll"
813
- ],
814
- flexDirection: ["row", "row-reverse", "col", "col-reverse"],
815
- justify: ["normal", "start", "end", "center", "between", "around", "evenly", "stretch"],
816
- items: ["start", "end", "center", "baseline", "stretch"],
817
- flex: ["1", "auto", "initial", "none"],
818
- grid: ["flow-row", "flow-col", "flow-dense", "flow-row-dense", "flow-col-dense"],
819
- gridCols: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "none", "subgrid"],
820
- gridRows: ["1", "2", "3", "4", "5", "6", "none", "subgrid"],
821
- col: [
822
- "auto",
823
- "span-1",
824
- "span-2",
825
- "span-3",
826
- "span-4",
827
- "span-5",
828
- "span-6",
829
- "span-7",
830
- "span-8",
831
- "span-9",
832
- "span-10",
833
- "span-11",
834
- "span-12",
835
- "span-full",
836
- "start-1",
837
- "start-2",
838
- "start-3",
839
- "start-4",
840
- "start-5",
841
- "start-6",
842
- "start-7",
843
- "start-8",
844
- "start-9",
845
- "start-10",
846
- "start-11",
847
- "start-12",
848
- "start-13",
849
- "start-auto",
850
- "end-1",
851
- "end-2",
852
- "end-3",
853
- "end-4",
854
- "end-5",
855
- "end-6",
856
- "end-7",
857
- "end-8",
858
- "end-9",
859
- "end-10",
860
- "end-11",
861
- "end-12",
862
- "end-13",
863
- "end-auto"
864
- ],
865
- row: [
866
- "auto",
867
- "span-1",
868
- "span-2",
869
- "span-3",
870
- "span-4",
871
- "span-5",
872
- "span-6",
873
- "span-full",
874
- "start-1",
875
- "start-2",
876
- "start-3",
877
- "start-4",
878
- "start-5",
879
- "start-6",
880
- "start-7",
881
- "start-auto",
882
- "end-1",
883
- "end-2",
884
- "end-3",
885
- "end-4",
886
- "end-5",
887
- "end-6",
888
- "end-7",
889
- "end-auto"
890
- ],
891
- textAlign: ["left", "center", "right", "justify", "start", "end"],
892
- fontWeight: [
893
- "thin",
894
- "extralight",
895
- "light",
896
- "normal",
897
- "medium",
898
- "semibold",
899
- "bold",
900
- "extrabold",
901
- "black"
902
- ],
903
- transition: ["none", "all", "colors", "opacity", "shadow", "transform"],
904
- ease: ["linear", "in", "out", "in-out"]
905
- };
906
- function generateUnionType(values) {
907
- if (values.length === 0) {
908
- return "string";
909
- }
910
- return values.map((v) => `'${v}'`).join(" | ");
911
- }
912
- function generateTypeDeclarations(theme, options = {}) {
913
- const { includeComments = true } = options;
914
- const colors = flattenColors(theme.colors || {});
915
- const spacing = extractSpacingKeys(theme.spacing || {});
916
- const screens = extractScreenKeys(theme.screens || {});
917
- const sizing = [
918
- ...spacing,
919
- "auto",
920
- "full",
921
- "screen",
922
- "svw",
923
- "lvw",
924
- "dvw",
925
- "min",
926
- "max",
927
- "fit",
928
- "1/2",
929
- "1/3",
930
- "2/3",
931
- "1/4",
932
- "2/4",
933
- "3/4",
934
- "1/5",
935
- "2/5",
936
- "3/5",
937
- "4/5",
938
- "1/6",
939
- "5/6"
940
- ];
941
- const valueTypeMap = {
942
- colors,
943
- spacing,
944
- sizing,
945
- screens,
946
- ...STATIC_VALUE_TYPES,
947
- // Extract from theme if available
948
- zIndex: theme.zIndex ? Object.keys(theme.zIndex) : ["0", "10", "20", "30", "40", "50", "auto"],
949
- borderRadius: theme.borderRadius ? Object.keys(theme.borderRadius) : ["none", "sm", "md", "lg", "xl", "2xl", "3xl", "full"],
950
- boxShadow: theme.boxShadow ? Object.keys(theme.boxShadow) : ["sm", "md", "lg", "xl", "2xl", "inner", "none"],
951
- opacity: theme.opacity ? Object.keys(theme.opacity) : [
952
- "0",
953
- "5",
954
- "10",
955
- "20",
956
- "25",
957
- "30",
958
- "40",
959
- "50",
960
- "60",
961
- "70",
962
- "75",
963
- "80",
964
- "90",
965
- "95",
966
- "100"
967
- ],
968
- fontSize: theme.fontSize ? Object.keys(theme.fontSize) : [
969
- "xs",
970
- "sm",
971
- "base",
972
- "lg",
973
- "xl",
974
- "2xl",
975
- "3xl",
976
- "4xl",
977
- "5xl",
978
- "6xl",
979
- "7xl",
980
- "8xl",
981
- "9xl"
982
- ],
983
- fontFamily: theme.fontFamily ? Object.keys(theme.fontFamily) : ["sans", "serif", "mono"],
984
- lineHeight: [
985
- "none",
986
- "tight",
987
- "snug",
988
- "normal",
989
- "relaxed",
990
- "loose",
991
- "3",
992
- "4",
993
- "5",
994
- "6",
995
- "7",
996
- "8",
997
- "9",
998
- "10"
999
- ],
1000
- letterSpacing: ["tighter", "tight", "normal", "wide", "wider", "widest"],
1001
- borderWidth: ["0", "2", "4", "8", ""],
1002
- minWidth: ["0", "full", "min", "max", "fit"],
1003
- maxWidth: [
1004
- "0",
1005
- "none",
1006
- "xs",
1007
- "sm",
1008
- "md",
1009
- "lg",
1010
- "xl",
1011
- "2xl",
1012
- "3xl",
1013
- "4xl",
1014
- "5xl",
1015
- "6xl",
1016
- "7xl",
1017
- "full",
1018
- "min",
1019
- "max",
1020
- "fit",
1021
- "prose",
1022
- "screen-sm",
1023
- "screen-md",
1024
- "screen-lg",
1025
- "screen-xl",
1026
- "screen-2xl"
1027
- ],
1028
- minHeight: ["0", "full", "screen", "svh", "lvh", "dvh", "min", "max", "fit"],
1029
- maxHeight: ["0", "none", "full", "screen", "svh", "lvh", "dvh", "min", "max", "fit"],
1030
- scale: ["0", "50", "75", "90", "95", "100", "105", "110", "125", "150"],
1031
- rotate: ["0", "1", "2", "3", "6", "12", "45", "90", "180"],
1032
- translate: spacing,
1033
- duration: ["0", "75", "100", "150", "200", "300", "500", "700", "1000"]
1034
- };
1035
- const properties = [];
1036
- for (const mapping of PROPERTY_MAPPINGS) {
1037
- const values = valueTypeMap[mapping.valueType] || [];
1038
- const unionType = generateUnionType(values);
1039
- let propDef = "";
1040
- if (includeComments && mapping.description) {
1041
- propDef += ` /** ${mapping.description} */
1042
- `;
1043
- }
1044
- propDef += ` ${mapping.prop}?: ${unionType} | (string & {}) | number;`;
1045
- properties.push(propDef);
1046
- }
1047
- const variants = [
1048
- "hover",
1049
- "focus",
1050
- "active",
1051
- "disabled",
1052
- "visited",
1053
- "first",
1054
- "last",
1055
- "odd",
1056
- "even",
1057
- "group-hover",
1058
- "dark"
1059
- ];
1060
- const variantProps = [];
1061
- for (const variant of variants) {
1062
- if (includeComments) {
1063
- variantProps.push(` /** Styles applied on ${variant} state */`);
1064
- }
1065
- variantProps.push(` ${variant}?: SzVariantObject;`);
1066
- }
1067
- const responsiveProps = [];
1068
- for (const screen of screens) {
1069
- if (includeComments) {
1070
- responsiveProps.push(` /** Styles applied at ${screen} breakpoint and above */`);
1071
- }
1072
- responsiveProps.push(` ${screen}?: SzVariantObject;`);
1073
- }
1074
- const output = `/**
1075
- * Auto-generated TypeScript declarations for csszyx.
1076
- *
1077
- * This file provides strict typing for the sz prop based on your
1078
- * Tailwind CSS configuration.
1079
- *
1080
- * @generated by @csszyx/cli
1081
- * @see https://github.com/nguyennhutien/csszyx
1082
- */
1083
-
1084
- import '@csszyx/types';
1085
-
1086
- /**
1087
- * Variant object type (used for hover, focus, responsive breakpoints, etc.)
1088
- */
1089
- type SzVariantObject = Partial<StrictSzObject>;
1090
-
1091
- /**
1092
- * Strict sz object interface with typed properties.
1093
- */
1094
- interface StrictSzObject {
1095
- ${properties.join("\n")}
1096
-
1097
- // State variants
1098
- ${variantProps.join("\n")}
1099
-
1100
- // Responsive variants
1101
- ${responsiveProps.join("\n")}
1102
-
1103
- // Allow arbitrary properties for escape hatch
1104
- [key: string]: string | number | boolean | SzVariantObject | undefined;
1105
- }
1106
-
1107
- declare module '@csszyx/types' {
1108
- /**
1109
- * Override the base SzObject with strict typing.
1110
- */
1111
- interface SzObject extends StrictSzObject {}
1112
- }
1113
-
1114
- declare module 'react' {
1115
- interface HTMLAttributes<T> {
1116
- /**
1117
- * csszyx object syntax for Tailwind CSS classes.
1118
- * Provides IntelliSense for all Tailwind utilities.
1119
- */
1120
- sz?: StrictSzObject;
1121
- }
1122
-
1123
- interface SVGAttributes<T> {
1124
- /**
1125
- * csszyx object syntax for Tailwind CSS classes.
1126
- */
1127
- sz?: StrictSzObject;
1128
- }
1129
- }
1130
-
1131
- export {};
1132
- `;
1133
- return output;
1134
- }
1135
- async function writeDeclarationFile(content, outputPath) {
1136
- const absolutePath = resolve(outputPath);
1137
- const dir = dirname(absolutePath);
1138
- await mkdir(dir, { recursive: true });
1139
- writeFileSync(absolutePath, content, "utf-8");
1140
- }
1141
- async function generateAndWriteTypes(theme, options = {}) {
1142
- const outputPath = options.output || "./csszyx.d.ts";
1143
- const content = generateTypeDeclarations(theme, options);
1144
- await writeDeclarationFile(content, outputPath);
1145
- return resolve(outputPath);
1146
- }
1147
-
1148
- async function generateTypes(options = {}) {
1149
- const cwd = options.cwd || process.cwd();
1150
- const log = options.silent ? () => {
1151
- } : console.log;
1152
- const error = options.silent ? () => {
1153
- } : console.error;
1154
- let configPath;
1155
- let scanResult;
1156
- if (options.config) {
1157
- configPath = resolve(cwd, options.config);
1158
- } else {
1159
- log("\u{1F50D} Searching for tailwind.config...");
1160
- const foundConfig = findConfigFile(cwd);
1161
- if (!foundConfig) {
1162
- error("\u274C Could not find tailwind.config.js in current directory");
1163
- error(" Please specify the path with --config flag");
1164
- process.exit(1);
1165
- }
1166
- configPath = foundConfig;
1167
- }
1168
- log(`\u{1F4D6} Reading config from: ${configPath}`);
1169
- try {
1170
- scanResult = await scanTailwindConfig(configPath);
1171
- } catch (err) {
1172
- error(
1173
- `\u274C Failed to read Tailwind config: ${err instanceof Error ? err.message : String(err)}`
1174
- );
1175
- process.exit(1);
1176
- }
1177
- log("\u2705 Config loaded successfully");
1178
- if (scanResult.hasCustomColors) {
1179
- log(" \u2022 Custom colors detected");
1180
- }
1181
- if (scanResult.hasCustomSpacing) {
1182
- log(" \u2022 Custom spacing detected");
1183
- }
1184
- const outputPath = options.output || "./csszyx.d.ts";
1185
- const generatorOptions = {
1186
- output: resolve(cwd, outputPath),
1187
- includeComments: true
1188
- };
1189
- log("\n\u{1F4DD} Generating TypeScript declarations...");
1190
- try {
1191
- const writtenPath = await generateAndWriteTypes(scanResult.theme, generatorOptions);
1192
- log("\n\u2728 Types generated successfully!");
1193
- log(` Output: ${writtenPath}`);
1194
- log('\n\u{1F4A1} Add this to your tsconfig.json "include" array:');
1195
- log(' "include": ["src", "csszyx.d.ts"]');
1196
- } catch (err) {
1197
- error(`\u274C Failed to generate types: ${err instanceof Error ? err.message : String(err)}`);
1198
- process.exit(1);
1199
- }
1200
- }
1201
-
1202
- const VITE_FRAMEWORKS = /* @__PURE__ */ new Set(["vite-react", "vite-vue", "vite-svelte"]);
1203
- async function readFileOrNull(filePath) {
1204
- try {
1205
- return await fs.readFile(filePath, "utf8");
1206
- } catch (err) {
1207
- if (err?.code === "ENOENT") {
1208
- return null;
1209
- }
1210
- throw err;
1211
- }
1212
- }
1213
- const NEXTJS_FRAMEWORKS = /* @__PURE__ */ new Set(["nextjs-app", "nextjs-pages"]);
1214
- const CSS_ENTRY_CANDIDATES = [
1215
- "src/index.css",
1216
- "src/app.css",
1217
- "src/globals.css",
1218
- "app/globals.css",
1219
- "src/styles/index.css",
1220
- "styles/globals.css"
1221
- ];
1222
- async function init(options = {}) {
1223
- const cwd = options.cwd || process.cwd();
1224
- const projectInfo = getProjectInfo(cwd);
1225
- printHeader("csszyx Setup Wizard");
1226
- if (projectInfo.framework !== "unknown") {
1227
- printSuccess(`Detected: ${getFrameworkName(projectInfo.framework)}`);
1228
- printInfo(`Package Manager: ${projectInfo.packageManager}`);
1229
- }
1230
- let config = {
1231
- enableSSR: true,
1232
- enableRecovery: true,
1233
- installTailwind: !projectInfo.hasTailwind,
1234
- setupGitignore: true,
1235
- setupTsconfig: !!projectInfo.hasTypeScript
1236
- };
1237
- if (!options.yes) {
1238
- const answers = await prompts([
1239
- {
1240
- type: projectInfo.hasTailwind ? null : "confirm",
1241
- name: "installTailwind",
1242
- message: "Install Tailwind CSS v4?",
1243
- initial: true
1244
- },
1245
- {
1246
- type: "confirm",
1247
- name: "enableSSR",
1248
- message: "Enable SSR Hydration Guard?",
1249
- initial: true
1250
- },
1251
- {
1252
- type: "confirm",
1253
- name: "enableRecovery",
1254
- message: "Enable development mode recovery?",
1255
- initial: true
1256
- },
1257
- {
1258
- type: "confirm",
1259
- name: "setupGitignore",
1260
- message: "Add .csszyx to .gitignore?",
1261
- initial: true
1262
- },
1263
- {
1264
- type: projectInfo.hasTypeScript ? "confirm" : null,
1265
- name: "setupTsconfig",
1266
- message: "Add .csszyx/theme.d.ts to tsconfig.json (Theme Auto-Scan)?",
1267
- initial: true
1268
- }
1269
- ]);
1270
- config = { ...config, ...answers };
1271
- }
1272
- const spin = spinner.start("Installing csszyx...");
1273
- try {
1274
- await execa(projectInfo.packageManager, ["add", "csszyx", "@csszyx/runtime"], { cwd });
1275
- if (projectInfo.hasTypeScript) {
1276
- await execa(projectInfo.packageManager, ["add", "-D", "@csszyx/types"], { cwd });
1277
- }
1278
- if (config.installTailwind) {
1279
- const twPackage = VITE_FRAMEWORKS.has(projectInfo.framework) ? "@tailwindcss/vite" : "@tailwindcss/postcss";
1280
- await execa(projectInfo.packageManager, ["add", "-D", "tailwindcss", twPackage], {
1281
- cwd
1282
- });
1283
- }
1284
- spinner.succeed(spin, "Installed csszyx");
1285
- } catch (error) {
1286
- spinner.fail(spin, "Failed to install packages");
1287
- printError(String(error));
1288
- return;
1289
- }
1290
- const spin2 = spinner.start("Creating config files...");
1291
- try {
1292
- const configContent = generateConfigFile(config);
1293
- const configPath = path__default.join(
1294
- cwd,
1295
- projectInfo.hasTypeScript ? "csszyx.config.ts" : "csszyx.config.js"
1296
- );
1297
- await fs.writeFile(configPath, configContent);
1298
- if (config.installTailwind) {
1299
- await setupTailwindCss(cwd, projectInfo.framework);
1300
- }
1301
- await injectPlugin(cwd, projectInfo.framework);
1302
- if (config.setupGitignore) {
1303
- const gitignorePath = path__default.join(cwd, ".gitignore");
1304
- const ignoreEntry = "\n# csszyx generated theme types\n.csszyx\n";
1305
- const existing = await readFileOrNull(gitignorePath);
1306
- if (existing !== null) {
1307
- if (!existing.includes(".csszyx")) {
1308
- await fs.appendFile(gitignorePath, ignoreEntry);
1309
- }
1310
- } else {
1311
- await fs.writeFile(gitignorePath, "node_modules\n.csszyx\n");
1312
- }
1313
- }
1314
- if (config.setupTsconfig) {
1315
- await setupTsconfig(cwd);
1316
- }
1317
- if (projectInfo.hasTypeScript) {
1318
- await setupSzTypes(cwd);
1319
- }
1320
- spinner.succeed(spin2, "Created configuration files");
1321
- } catch (error) {
1322
- spinner.fail(spin2, "Failed to create config files");
1323
- printError(String(error));
1324
- return;
1325
- }
1326
- console.log();
1327
- printSuccess("\u{1F389} All done!");
1328
- console.log();
1329
- printInfo("Next steps:");
1330
- console.log(` \u2022 Run '${projectInfo.packageManager} run dev' to start`);
1331
- if (projectInfo.hasTypeScript) {
1332
- console.log(" \u2022 The `sz` prop is typed via csszyx-env.d.ts");
1333
- }
1334
- console.log(" \u2022 Check the docs at https://csszyx.dev");
1335
- }
1336
- async function setupTailwindCss(cwd, framework) {
1337
- let cssPath;
1338
- let content = null;
1339
- for (const candidate of CSS_ENTRY_CANDIDATES) {
1340
- const full = path__default.join(cwd, candidate);
1341
- const existing = await readFileOrNull(full);
1342
- if (existing !== null) {
1343
- cssPath = full;
1344
- content = existing;
1345
- break;
1346
- }
1347
- }
1348
- if (!cssPath || content === null) {
1349
- cssPath = path__default.join(cwd, "src/index.css");
1350
- await fs.ensureDir(path__default.dirname(cssPath));
1351
- await fs.writeFile(cssPath, '@import "tailwindcss";\n');
1352
- printInfo(`Created ${path__default.relative(cwd, cssPath)} with Tailwind v4 import`);
1353
- return;
1354
- }
1355
- if (!content.includes('@import "tailwindcss"') && !content.includes("@import 'tailwindcss'")) {
1356
- await fs.writeFile(cssPath, `@import "tailwindcss";
1357
-
1358
- ${content}`);
1359
- printInfo(`Added Tailwind v4 import to ${path__default.relative(cwd, cssPath)}`);
1360
- }
1361
- if (NEXTJS_FRAMEWORKS.has(framework)) {
1362
- const postcssMjs = path__default.join(cwd, "postcss.config.mjs");
1363
- const postcssJs = path__default.join(cwd, "postcss.config.js");
1364
- const postcssTs = path__default.join(cwd, "postcss.config.ts");
1365
- const hasExisting = await readFileOrNull(postcssMjs) !== null || await readFileOrNull(postcssJs) !== null || await readFileOrNull(postcssTs) !== null;
1366
- if (!hasExisting) {
1367
- await fs.writeFile(postcssMjs, generatePostcssConfig());
1368
- printInfo("Created postcss.config.mjs for Tailwind v4");
1369
- }
1370
- }
1371
- }
1372
- async function injectPlugin(cwd, framework) {
1373
- if (VITE_FRAMEWORKS.has(framework)) {
1374
- const injected = await injectVitePlugin(cwd);
1375
- if (!injected) {
1376
- printWarn("Could not auto-inject csszyx plugin. Add manually to vite.config.ts:");
1377
- console.log(generateVitePluginInstructions());
1378
- }
1379
- } else if (NEXTJS_FRAMEWORKS.has(framework)) {
1380
- const injected = await injectNextPlugin(cwd);
1381
- if (!injected) {
1382
- printWarn("Could not auto-inject csszyx plugin. Add manually to next.config.js:");
1383
- console.log(generateNextPluginInstructions());
1384
- }
1385
- } else {
1386
- printInfo("Add csszyx plugin to your bundler config:");
1387
- console.log(generateVitePluginInstructions());
1388
- }
1389
- }
1390
- async function injectVitePlugin(cwd) {
1391
- const candidates = [
1392
- path__default.join(cwd, "vite.config.ts"),
1393
- path__default.join(cwd, "vite.config.js"),
1394
- path__default.join(cwd, "vite.config.mts"),
1395
- path__default.join(cwd, "vite.config.mjs")
1396
- ];
1397
- let configPath;
1398
- let content = null;
1399
- for (const c of candidates) {
1400
- const existing = await readFileOrNull(c);
1401
- if (existing !== null) {
1402
- configPath = c;
1403
- content = existing;
1404
- break;
1405
- }
1406
- }
1407
- if (!configPath || content === null) {
1408
- return false;
1409
- }
1410
- if (content.includes("csszyx")) {
1411
- return true;
1412
- }
1413
- const importBlock = [
1414
- "import csszyx from 'csszyx/vite';",
1415
- content.includes("@tailwindcss/vite") ? null : "import tailwindcss from '@tailwindcss/vite';"
1416
- ].filter(Boolean).join("\n");
1417
- const lastImportMatch = [...content.matchAll(/^import .+$/gm)].pop();
1418
- if (!lastImportMatch || lastImportMatch.index === void 0) {
1419
- return false;
1420
- }
1421
- const insertAt = lastImportMatch.index + lastImportMatch[0].length;
1422
- content = `${content.slice(0, insertAt)}
1423
- ${importBlock}${content.slice(insertAt)}`;
1424
- const pluginsMatch = content.match(/plugins\s*:\s*\[/);
1425
- if (!pluginsMatch || pluginsMatch.index === void 0) {
1426
- return false;
1427
- }
1428
- const pluginsInsertAt = pluginsMatch.index + pluginsMatch[0].length;
1429
- const twEntry = content.includes("tailwindcss()") ? "" : "\n tailwindcss(),";
1430
- content = content.slice(0, pluginsInsertAt) + `
1431
- ...csszyx(), // csszyx MUST come before tailwindcss${twEntry}` + content.slice(pluginsInsertAt);
1432
- await fs.writeFile(configPath, content);
1433
- printInfo(`Injected csszyx plugin into ${path__default.basename(configPath)}`);
1434
- return true;
1435
- }
1436
- async function injectNextPlugin(cwd) {
1437
- const candidates = [
1438
- path__default.join(cwd, "next.config.ts"),
1439
- path__default.join(cwd, "next.config.mjs"),
1440
- path__default.join(cwd, "next.config.js")
1441
- ];
1442
- let configPath;
1443
- let content = null;
1444
- for (const c of candidates) {
1445
- const existing = await readFileOrNull(c);
1446
- if (existing !== null) {
1447
- configPath = c;
1448
- content = existing;
1449
- break;
1450
- }
1451
- }
1452
- if (!configPath) {
1453
- configPath = path__default.join(cwd, "next.config.js");
1454
- await fs.writeFile(configPath, generateNextConfig());
1455
- printInfo("Created next.config.js with csszyx plugin");
1456
- return true;
1457
- }
1458
- if (content?.includes("csszyx")) {
1459
- return true;
1460
- }
1461
- return false;
1462
- }
1463
- async function setupTsconfig(cwd) {
1464
- const primary = path__default.join(cwd, "tsconfig.json");
1465
- const viteTsConfig = path__default.join(cwd, "tsconfig.app.json");
1466
- let tsconfigPath = primary;
1467
- let content = await readFileOrNull(tsconfigPath);
1468
- if (content === null) {
1469
- tsconfigPath = viteTsConfig;
1470
- content = await readFileOrNull(tsconfigPath);
1471
- }
1472
- if (content === null) {
1473
- return;
1474
- }
1475
- if (content.includes(".csszyx")) {
1476
- return;
1477
- }
1478
- const includeMatch = content.match(/"include"\s*:\s*\[/);
1479
- if (includeMatch && includeMatch.index !== void 0) {
1480
- const insertPos = includeMatch.index + includeMatch[0].length;
1481
- content = content.slice(0, insertPos) + '\n "./.csszyx/theme.d.ts",\n "./.csszyx",' + content.slice(insertPos);
1482
- await fs.writeFile(tsconfigPath, content);
1483
- }
1484
- }
1485
- async function setupSzTypes(cwd) {
1486
- const envPath = path__default.join(cwd, "csszyx-env.d.ts");
1487
- const reference = '/// <reference types="@csszyx/types/jsx" />';
1488
- const existing = await readFileOrNull(envPath);
1489
- if (existing === null) {
1490
- await fs.writeFile(
1491
- envPath,
1492
- `// Generated by csszyx. Enables the \`sz\` JSX prop types.
1493
- ${reference}
1494
- `
1495
- );
1496
- printInfo("Created csszyx-env.d.ts (sz prop types)");
1497
- } else if (!existing.includes("@csszyx/types/jsx")) {
1498
- await fs.writeFile(envPath, `${existing.trimEnd()}
1499
- ${reference}
1500
- `);
1501
- printInfo("Added the sz type reference to csszyx-env.d.ts");
1502
- }
1503
- await ensureTsconfigInclude(cwd, "csszyx-env.d.ts");
1504
- }
1505
- async function ensureTsconfigInclude(cwd, entry) {
1506
- for (const name of ["tsconfig.json", "tsconfig.app.json"]) {
1507
- const tsconfigPath = path__default.join(cwd, name);
1508
- const content = await readFileOrNull(tsconfigPath);
1509
- if (content === null) {
1510
- continue;
1511
- }
1512
- if (content.includes(entry)) {
1513
- return;
1514
- }
1515
- const includeMatch = content.match(/"include"\s*:\s*\[/);
1516
- if (includeMatch && includeMatch.index !== void 0) {
1517
- const insertPos = includeMatch.index + includeMatch[0].length;
1518
- await fs.writeFile(
1519
- tsconfigPath,
1520
- `${content.slice(0, insertPos)}
1521
- "${entry}",${content.slice(insertPos)}`
1522
- );
1523
- }
1524
- return;
1525
- }
1526
- }
1527
- function generateConfigFile(config) {
1528
- return `import type { CsszyxConfig } from 'csszyx';
1529
-
1530
- const config: CsszyxConfig = {
1531
- development: {
1532
- debug: true,
1533
- },
1534
- production: {
1535
- injectChecksum: ${config.enableSSR},
1536
- },
1537
- };
1538
-
1539
- export default config;
1540
- `;
1541
- }
1542
- function generatePostcssConfig() {
1543
- return `export default {
1544
- plugins: {
1545
- '@tailwindcss/postcss': {},
1546
- },
1547
- };
1548
- `;
1549
- }
1550
- function generateNextConfig() {
1551
- return `const csszyxWebpack = require('@csszyx/unplugin/webpack').default;
1552
-
1553
- /** @type {import('next').NextConfig} */
1554
- const nextConfig = {
1555
- webpack(config) {
1556
- config.plugins.unshift(...csszyxWebpack());
1557
- return config;
1558
- },
1559
- };
1560
-
1561
- module.exports = nextConfig;
1562
- `;
1563
- }
1564
- function generateVitePluginInstructions() {
1565
- return `
1566
- import csszyx from 'csszyx/vite';
1567
- import tailwindcss from '@tailwindcss/vite';
1568
-
1569
- export default defineConfig({
1570
- plugins: [
1571
- ...csszyx(), // csszyx MUST come before tailwindcss
1572
- tailwindcss(),
1573
- // ...your other plugins
1574
- ],
1575
- });
1576
- `;
1577
- }
1578
- function generateNextPluginInstructions() {
1579
- return `
1580
- const csszyxWebpack = require('@csszyx/unplugin/webpack').default;
1581
-
1582
- module.exports = {
1583
- webpack(config) {
1584
- config.plugins.unshift(...csszyxWebpack());
1585
- return config;
1586
- },
1587
- };
1588
- `;
1589
- }
1590
-
1591
- function generateSzExpression(obj) {
1592
- return `{${objectToString(obj)}}`;
1593
- }
1594
- function generateSzHtmlValue(obj, braces = false) {
1595
- const s = objectToString(obj);
1596
- if (braces) {
1597
- return s;
1598
- }
1599
- if (s.startsWith("{ ") && s.endsWith(" }")) {
1600
- return s.slice(2, -2);
1601
- }
1602
- if (s.startsWith("{") && s.endsWith("}")) {
1603
- return s.slice(1, -1).trim();
1604
- }
1605
- return s;
1606
- }
1607
- function generateSzObjectLiteral(obj) {
1608
- return objectToString(obj);
1609
- }
1610
- function objectToString(obj, indent = 0) {
1611
- const entries = Object.entries(obj);
1612
- if (entries.length === 0) {
1613
- return "{}";
1614
- }
1615
- const spaces = " ".repeat(indent);
1616
- const innerSpaces = " ".repeat(indent + 2);
1617
- if (entries.length <= 2 && !hasDeepNesting(obj)) {
1618
- const parts = entries.map(([k, v]) => `${formatKey(k)}: ${formatValue(v, indent)}`);
1619
- return `{ ${parts.join(", ")} }`;
1620
- }
1621
- const lines = entries.map(
1622
- ([k, v]) => `${innerSpaces}${formatKey(k)}: ${formatValue(v, indent + 2)},`
1623
- );
1624
- return `{
1625
- ${lines.join("\n")}
1626
- ${spaces}}`;
1627
- }
1628
- function hasDeepNesting(obj) {
1629
- return Object.values(obj).some(
1630
- (v) => typeof v === "object" && v !== null && !isColorOpacityObj(v) && !isGradientObj(v)
1631
- );
1632
- }
1633
- function isColorOpacityObj(v) {
1634
- return typeof v === "object" && v !== null && "color" in v && "op" in v;
1635
- }
1636
- function isGradientObj(v) {
1637
- return typeof v === "object" && v !== null && "gradient" in v;
1638
- }
1639
- function formatKey(key) {
1640
- if (/^[a-z_$][\w$]*$/i.test(key)) {
1641
- return key;
1642
- }
1643
- return `'${key}'`;
1644
- }
1645
- function formatValue(value, indent) {
1646
- if (value === true) {
1647
- return "true";
1648
- }
1649
- if (value === false) {
1650
- return "false";
1651
- }
1652
- if (value === null) {
1653
- return "null";
1654
- }
1655
- if (typeof value === "number") {
1656
- return String(value);
1657
- }
1658
- if (typeof value === "string") {
1659
- return `'${escapeString(value)}'`;
1660
- }
1661
- if (Array.isArray(value)) {
1662
- const items = value.map((v) => formatValue(v, indent));
1663
- return `[${items.join(", ")}]`;
1664
- }
1665
- if (typeof value === "object") {
1666
- if (isColorOpacityObj(value)) {
1667
- const parts = [`color: '${escapeString(String(value.color))}'`];
1668
- if (typeof value.op === "number") {
1669
- parts.push(`op: ${value.op}`);
1670
- } else {
1671
- parts.push(`op: '${escapeString(String(value.op))}'`);
1672
- }
1673
- return `{ ${parts.join(", ")} }`;
1674
- }
1675
- if (isGradientObj(value)) {
1676
- const grad = value;
1677
- const parts = [`gradient: '${grad.gradient}'`];
1678
- if ("dir" in grad) {
1679
- if (typeof grad.dir === "number") {
1680
- parts.push(`dir: ${grad.dir}`);
1681
- } else {
1682
- parts.push(`dir: '${escapeString(String(grad.dir))}'`);
1683
- }
1684
- }
1685
- if ("in" in grad) {
1686
- parts.push(`in: '${escapeString(String(grad.in))}'`);
1687
- }
1688
- return `{ ${parts.join(", ")} }`;
1689
- }
1690
- return objectToString(value, indent);
1691
- }
1692
- return String(value);
1693
- }
1694
- function escapeString(s) {
1695
- return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
1696
- }
1697
-
1698
- const REVERSE_PROPERTY_MAP = {
1699
- // Background (ambiguous — disambiguated in class-parser)
1700
- bg: "bg",
1701
- "bg-clip": "bgClip",
1702
- "bg-origin": "bgOrigin",
1703
- // Border Radius
1704
- rounded: "rounded",
1705
- "rounded-t": "roundedT",
1706
- "rounded-r": "roundedR",
1707
- "rounded-b": "roundedB",
1708
- "rounded-l": "roundedL",
1709
- "rounded-tl": "roundedTl",
1710
- "rounded-tr": "roundedTr",
1711
- "rounded-bl": "roundedBl",
1712
- "rounded-br": "roundedBr",
1713
- "rounded-s": "roundedS",
1714
- "rounded-e": "roundedE",
1715
- "rounded-ss": "roundedSs",
1716
- "rounded-se": "roundedSe",
1717
- "rounded-es": "roundedEs",
1718
- "rounded-ee": "roundedEe",
1719
- // Border (ambiguous — disambiguated)
1720
- border: "border",
1721
- "border-t": "borderT",
1722
- "border-r": "borderR",
1723
- "border-b": "borderB",
1724
- "border-l": "borderL",
1725
- "border-x": "borderX",
1726
- "border-y": "borderY",
1727
- "border-s": "borderS",
1728
- "border-e": "borderE",
1729
- // Divide
1730
- "divide-x": "divideX",
1731
- "divide-y": "divideY",
1732
- divide: "divideColor",
1733
- // Outline (ambiguous)
1734
- outline: "outline",
1735
- "outline-offset": "outlineOffset",
1736
- // Ring (v4: outset ring + inset ring)
1737
- ring: "ring",
1738
- "ring-offset": "ringOffset",
1739
- "inset-ring": "insetRing",
1740
- // Spacing
1741
- p: "p",
1742
- pt: "pt",
1743
- pr: "pr",
1744
- pb: "pb",
1745
- pl: "pl",
1746
- px: "px",
1747
- py: "py",
1748
- ps: "ps",
1749
- pe: "pe",
1750
- m: "m",
1751
- mt: "mt",
1752
- mr: "mr",
1753
- mb: "mb",
1754
- ml: "ml",
1755
- mx: "mx",
1756
- my: "my",
1757
- ms: "ms",
1758
- me: "me",
1759
- // Space between
1760
- "space-x": "spaceX",
1761
- "space-y": "spaceY",
1762
- // Sizing
1763
- w: "w",
1764
- "min-w": "minW",
1765
- "max-w": "maxW",
1766
- h: "h",
1767
- "min-h": "minH",
1768
- "max-h": "maxH",
1769
- size: "size",
1770
- // Layout
1771
- aspect: "aspect",
1772
- columns: "columns",
1773
- "break-after": "breakAfter",
1774
- "break-before": "breakBefore",
1775
- "break-inside": "breakInside",
1776
- "box-decoration": "boxDecoration",
1777
- box: "box",
1778
- float: "float",
1779
- clear: "clear",
1780
- object: "objectFit",
1781
- // ambiguous — objectFit vs objectPos (objectPos for position values)
1782
- overflow: "overflow",
1783
- "overflow-x": "overflowX",
1784
- "overflow-y": "overflowY",
1785
- overscroll: "overscroll",
1786
- "overscroll-x": "overscrollX",
1787
- "overscroll-y": "overscrollY",
1788
- z: "z",
1789
- // Inset
1790
- inset: "inset",
1791
- "inset-x": "insetX",
1792
- "inset-y": "insetY",
1793
- top: "top",
1794
- right: "right",
1795
- bottom: "bottom",
1796
- left: "left",
1797
- // TW v4.2: start/end are deprecated — migrate to inset-s/inset-e
1798
- start: "insetS",
1799
- end: "insetE",
1800
- // Typography (ambiguous — text-*, font-* disambiguated)
1801
- text: "color",
1802
- // default for text- prefix
1803
- font: "fontWeight",
1804
- // default for font- prefix
1805
- decoration: "decoration",
1806
- // ambiguous
1807
- "underline-offset": "underlineOffset",
1808
- indent: "indent",
1809
- align: "align",
1810
- whitespace: "whitespace",
1811
- break: "break",
1812
- // ambiguous with break-after/before/inside (handled by prefix matching)
1813
- hyphens: "hyphens",
1814
- content: "content",
1815
- leading: "leading",
1816
- tracking: "tracking",
1817
- list: "list",
1818
- // ambiguous
1819
- "list-image": "listImg",
1820
- // Flex & Grid
1821
- basis: "basis",
1822
- flex: "flex",
1823
- // ambiguous (boolean flex, flexDirection, flexWrap)
1824
- grow: "grow",
1825
- shrink: "shrink",
1826
- order: "order",
1827
- items: "items",
1828
- self: "self",
1829
- justify: "justify",
1830
- "justify-items": "justifyItems",
1831
- "justify-self": "justifySelf",
1832
- "place-content": "placeContent",
1833
- "place-items": "placeItems",
1834
- "place-self": "placeSelf",
1835
- gap: "gap",
1836
- "gap-x": "gapX",
1837
- "gap-y": "gapY",
1838
- // Grid
1839
- "grid-cols": "gridCols",
1840
- "grid-rows": "gridRows",
1841
- col: "col",
1842
- "col-span": "colSpan",
1843
- "col-start": "colStart",
1844
- "col-end": "colEnd",
1845
- row: "row",
1846
- "row-span": "rowSpan",
1847
- "row-start": "rowStart",
1848
- "row-end": "rowEnd",
1849
- "grid-flow": "gridFlow",
1850
- "auto-cols": "autoCols",
1851
- "auto-rows": "autoRows",
1852
- // Effects
1853
- shadow: "shadow",
1854
- // ambiguous (shadow vs shadowColor)
1855
- "inset-shadow": "insetShadow",
1856
- opacity: "opacity",
1857
- "mix-blend": "mixBlend",
1858
- "bg-blend": "bgBlend",
1859
- // Filters
1860
- blur: "blur",
1861
- brightness: "brightness",
1862
- contrast: "contrast",
1863
- "drop-shadow": "dropShadow",
1864
- grayscale: "grayscale",
1865
- "hue-rotate": "hueRotate",
1866
- invert: "invert",
1867
- saturate: "saturate",
1868
- sepia: "sepia",
1869
- "backdrop-blur": "backdropBlur",
1870
- "backdrop-brightness": "backdropBrightness",
1871
- "backdrop-contrast": "backdropContrast",
1872
- "backdrop-grayscale": "backdropGrayscale",
1873
- "backdrop-hue-rotate": "backdropHueRotate",
1874
- "backdrop-invert": "backdropInvert",
1875
- "backdrop-opacity": "backdropOpacity",
1876
- "backdrop-saturate": "backdropSaturate",
1877
- "backdrop-sepia": "backdropSepia",
1878
- // Transforms
1879
- scale: "scale",
1880
- "scale-x": "scaleX",
1881
- "scale-y": "scaleY",
1882
- rotate: "rotate",
1883
- "translate-x": "translateX",
1884
- "translate-y": "translateY",
1885
- "skew-x": "skewX",
1886
- "skew-y": "skewY",
1887
- origin: "origin",
1888
- // Transitions & Animation
1889
- transition: "transition",
1890
- duration: "duration",
1891
- ease: "ease",
1892
- delay: "delay",
1893
- animate: "animate",
1894
- // Interactivity
1895
- cursor: "cursor",
1896
- caret: "caret",
1897
- "pointer-events": "pointerEvents",
1898
- resize: "resize",
1899
- scroll: "scroll",
1900
- "scroll-m": "scrollM",
1901
- "scroll-mt": "scrollMt",
1902
- "scroll-mr": "scrollMr",
1903
- "scroll-mb": "scrollMb",
1904
- "scroll-ml": "scrollMl",
1905
- "scroll-ms": "scrollMs",
1906
- "scroll-me": "scrollMe",
1907
- "scroll-mx": "scrollMx",
1908
- "scroll-my": "scrollMy",
1909
- "scroll-p": "scrollP",
1910
- "scroll-pt": "scrollPt",
1911
- "scroll-pr": "scrollPr",
1912
- "scroll-pb": "scrollPb",
1913
- "scroll-pl": "scrollPl",
1914
- "scroll-ps": "scrollPs",
1915
- "scroll-pe": "scrollPe",
1916
- "scroll-px": "scrollPx",
1917
- "scroll-py": "scrollPy",
1918
- snap: "snapType",
1919
- // ambiguous
1920
- touch: "touch",
1921
- select: "select",
1922
- "will-change": "willChange",
1923
- accent: "accent",
1924
- // SVG
1925
- fill: "fill",
1926
- stroke: "stroke",
1927
- // Tables
1928
- "border-spacing": "borderSpacing",
1929
- table: "tableLayout",
1930
- // ambiguous with boolean "table" display
1931
- caption: "caption",
1932
- // Line clamp
1933
- "line-clamp": "lineClamp",
1934
- wrap: "wrap",
1935
- // Text shadow
1936
- "text-shadow": "textShadow",
1937
- // Gradient stops
1938
- from: "from",
1939
- via: "via",
1940
- to: "to",
1941
- // Masks
1942
- mask: "mask",
1943
- // Forced colors
1944
- "forced-color-adjust": "forcedColorAdjust",
1945
- // Perspective
1946
- perspective: "perspective",
1947
- "perspective-origin": "perspectiveOrigin",
1948
- backface: "backface"
1949
- };
1950
- const REVERSE_BOOLEAN_MAP = {
1951
- // Display
1952
- block: "block",
1953
- inline: "inline",
1954
- "inline-block": "inlineBlock",
1955
- flex: "flex",
1956
- "inline-flex": "inlineFlex",
1957
- grid: "grid",
1958
- "inline-grid": "inlineGrid",
1959
- hidden: "hidden",
1960
- contents: "contents",
1961
- table: "table",
1962
- "table-row": "tableRow",
1963
- "table-cell": "tableCell",
1964
- "flow-root": "flowRoot",
1965
- "list-item": "listItem",
1966
- // Position
1967
- static: "static",
1968
- fixed: "fixed",
1969
- absolute: "absolute",
1970
- relative: "relative",
1971
- sticky: "sticky",
1972
- // Visibility
1973
- visible: "visible",
1974
- invisible: "invisible",
1975
- collapse: "collapse",
1976
- // Typography
1977
- truncate: "truncate",
1978
- uppercase: "uppercase",
1979
- lowercase: "lowercase",
1980
- capitalize: "capitalize",
1981
- "normal-case": "normalCase",
1982
- underline: "underline",
1983
- overline: "overline",
1984
- "line-through": "lineThrough",
1985
- "no-underline": "noUnderline",
1986
- italic: "italic",
1987
- "not-italic": "notItalic",
1988
- antialiased: "antialiased",
1989
- "subpixel-antialiased": "subpixelAntialiased",
1990
- // Flexbox
1991
- // flexWrap is string-based, not boolean — removed from boolean map
1992
- // Filters (defaults)
1993
- blur: "blur",
1994
- grayscale: "grayscale",
1995
- invert: "invert",
1996
- sepia: "sepia",
1997
- "backdrop-blur": "backdropBlur",
1998
- "backdrop-grayscale": "backdropGrayscale",
1999
- "backdrop-invert": "backdropInvert",
2000
- "backdrop-sepia": "backdropSepia",
2001
- // Misc
2002
- container: "container",
2003
- prose: "prose",
2004
- "sr-only": "srOnly",
2005
- "not-sr-only": "notSrOnly",
2006
- isolate: "isolate",
2007
- ordinal: "ordinal",
2008
- "slashed-zero": "slashedZero",
2009
- // Bare `transition` (common transition property) and the `group`/`peer`
2010
- // marker classes round-trip through the compiler as boolean sugar.
2011
- transition: "transition",
2012
- group: "group",
2013
- peer: "peer",
2014
- // Divide/Space reverse
2015
- "divide-x-reverse": "divideXReverse",
2016
- "divide-y-reverse": "divideYReverse",
2017
- "space-x-reverse": "spaceXReverse",
2018
- "space-y-reverse": "spaceYReverse",
2019
- // Ring/Outline/Border-radius (boolean defaults)
2020
- ring: "ring",
2021
- "inset-ring": "insetRing",
2022
- outline: "outline",
2023
- rounded: "rounded",
2024
- // Transforms
2025
- "scale-3d": "scale",
2026
- "translate-3d": "translate",
2027
- // transform-gpu/cpu/none use BOOLEAN_VALUE_MAP → { transform: 'gpu'/'cpu'/'none' }
2028
- // Font numeric
2029
- "normal-nums": "fontVariant",
2030
- "lining-nums": "fontVariant",
2031
- "oldstyle-nums": "fontVariant",
2032
- "proportional-nums": "fontVariant",
2033
- "tabular-nums": "fontVariant",
2034
- "diagonal-fractions": "fontVariant",
2035
- "stacked-fractions": "fontVariant",
2036
- // Snap
2037
- "snap-none": "snapType",
2038
- "snap-x": "snapType",
2039
- "snap-y": "snapType",
2040
- "snap-both": "snapType",
2041
- "snap-mandatory": "snapStrictness",
2042
- "snap-proximity": "snapStrictness",
2043
- "snap-start": "snapAlign",
2044
- "snap-end": "snapAlign",
2045
- "snap-center": "snapAlign",
2046
- "snap-align-none": "snapAlign",
2047
- "snap-normal": "snapStop",
2048
- "snap-always": "snapStop",
2049
- // Divide styles
2050
- "divide-solid": "divideStyle",
2051
- "divide-dashed": "divideStyle",
2052
- "divide-dotted": "divideStyle",
2053
- "divide-double": "divideStyle",
2054
- "divide-none": "divideStyle",
2055
- // Appearance
2056
- "appearance-none": "appearance",
2057
- "appearance-auto": "appearance"
2058
- };
2059
- const BOOLEAN_VALUE_MAP = {
2060
- // Snap types
2061
- "snap-none": { prop: "snapType", value: "none" },
2062
- "snap-x": { prop: "snapType", value: "x" },
2063
- "snap-y": { prop: "snapType", value: "y" },
2064
- "snap-both": { prop: "snapType", value: "both" },
2065
- "snap-mandatory": { prop: "snapStrictness", value: "mandatory" },
2066
- "snap-proximity": { prop: "snapStrictness", value: "proximity" },
2067
- "snap-start": { prop: "snapAlign", value: "start" },
2068
- "snap-end": { prop: "snapAlign", value: "end" },
2069
- "snap-center": { prop: "snapAlign", value: "center" },
2070
- "snap-align-none": { prop: "snapAlign", value: "none" },
2071
- "snap-normal": { prop: "snapStop", value: "normal" },
2072
- "snap-always": { prop: "snapStop", value: "always" },
2073
- // Divide styles
2074
- "divide-solid": { prop: "divideStyle", value: "solid" },
2075
- "divide-dashed": { prop: "divideStyle", value: "dashed" },
2076
- "divide-dotted": { prop: "divideStyle", value: "dotted" },
2077
- "divide-double": { prop: "divideStyle", value: "double" },
2078
- "divide-none": { prop: "divideStyle", value: "none" },
2079
- // Font variants
2080
- "normal-nums": { prop: "fontVariant", value: "normal-nums" },
2081
- "lining-nums": { prop: "fontVariant", value: "lining-nums" },
2082
- "oldstyle-nums": { prop: "fontVariant", value: "oldstyle-nums" },
2083
- "proportional-nums": { prop: "fontVariant", value: "proportional-nums" },
2084
- "tabular-nums": { prop: "fontVariant", value: "tabular-nums" },
2085
- "diagonal-fractions": { prop: "fontVariant", value: "diagonal-fractions" },
2086
- "stacked-fractions": { prop: "fontVariant", value: "stacked-fractions" },
2087
- // Appearance
2088
- "appearance-none": { prop: "appearance", value: "none" },
2089
- "appearance-auto": { prop: "appearance", value: "auto" },
2090
- // Transform
2091
- "transform-none": { prop: "transform", value: "none" },
2092
- "transform-gpu": { prop: "transform", value: "gpu" },
2093
- "transform-cpu": { prop: "transform", value: "cpu" }
2094
- };
2095
- const SORTED_PREFIXES = Object.keys(REVERSE_PROPERTY_MAP).sort((a, b) => {
2096
- if (b.length !== a.length) {
2097
- return b.length - a.length;
2098
- }
2099
- return a.localeCompare(b);
2100
- });
2101
- const NEGATIVE_ALLOWED = /* @__PURE__ */ new Set([
2102
- "m",
2103
- "mt",
2104
- "mr",
2105
- "mb",
2106
- "ml",
2107
- "mx",
2108
- "my",
2109
- "ms",
2110
- "me",
2111
- "top",
2112
- "right",
2113
- "bottom",
2114
- "left",
2115
- "inset",
2116
- "inset-x",
2117
- "inset-y",
2118
- "start",
2119
- "end",
2120
- "z",
2121
- "order",
2122
- "col",
2123
- "col-start",
2124
- "col-end",
2125
- "row",
2126
- "row-start",
2127
- "row-end",
2128
- "rotate",
2129
- "skew-x",
2130
- "skew-y",
2131
- "translate-x",
2132
- "translate-y",
2133
- "space-x",
2134
- "space-y",
2135
- "tracking",
2136
- "indent",
2137
- "scroll-m",
2138
- "scroll-mx",
2139
- "scroll-my",
2140
- "scroll-mt",
2141
- "scroll-mr",
2142
- "scroll-mb",
2143
- "scroll-ml",
2144
- "hue-rotate",
2145
- "backdrop-hue-rotate"
2146
- ]);
2147
- const FRACTION_SUPPORTED = /* @__PURE__ */ new Set([
2148
- "w",
2149
- "min-w",
2150
- "max-w",
2151
- "h",
2152
- "min-h",
2153
- "max-h",
2154
- "size",
2155
- "basis",
2156
- "inset",
2157
- "inset-x",
2158
- "inset-y",
2159
- "top",
2160
- "right",
2161
- "bottom",
2162
- "left",
2163
- "start",
2164
- "end",
2165
- "translate-x",
2166
- "translate-y"
2167
- ]);
2168
- const SPACING_PROPS = /* @__PURE__ */ new Set([
2169
- "p",
2170
- "pt",
2171
- "pr",
2172
- "pb",
2173
- "pl",
2174
- "px",
2175
- "py",
2176
- "ps",
2177
- "pe",
2178
- "m",
2179
- "mt",
2180
- "mr",
2181
- "mb",
2182
- "ml",
2183
- "mx",
2184
- "my",
2185
- "ms",
2186
- "me",
2187
- "gap",
2188
- "gap-x",
2189
- "gap-y",
2190
- "w",
2191
- "h",
2192
- "min-w",
2193
- "max-w",
2194
- "min-h",
2195
- "max-h",
2196
- "size",
2197
- "basis",
2198
- "inset",
2199
- "inset-x",
2200
- "inset-y",
2201
- "top",
2202
- "right",
2203
- "bottom",
2204
- "left",
2205
- "start",
2206
- "end",
2207
- "space-x",
2208
- "space-y",
2209
- "indent",
2210
- "scroll-m",
2211
- "scroll-mx",
2212
- "scroll-my",
2213
- "scroll-mt",
2214
- "scroll-mr",
2215
- "scroll-mb",
2216
- "scroll-ml",
2217
- "scroll-ms",
2218
- "scroll-me",
2219
- "scroll-p",
2220
- "scroll-px",
2221
- "scroll-py",
2222
- "scroll-pt",
2223
- "scroll-pr",
2224
- "scroll-pb",
2225
- "scroll-pl",
2226
- "scroll-ps",
2227
- "scroll-pe",
2228
- "border-spacing",
2229
- "translate-x",
2230
- "translate-y"
2231
- ]);
2232
- const TEXT_SIZE_KEYWORDS = /* @__PURE__ */ new Set([
2233
- "xs",
2234
- "sm",
2235
- "base",
2236
- "lg",
2237
- "xl",
2238
- "2xl",
2239
- "3xl",
2240
- "4xl",
2241
- "5xl",
2242
- "6xl",
2243
- "7xl",
2244
- "8xl",
2245
- "9xl"
2246
- ]);
2247
- const TEXT_ALIGN_KEYWORDS = /* @__PURE__ */ new Set(["left", "center", "right", "justify", "start", "end"]);
2248
- const TEXT_WRAP_KEYWORDS = /* @__PURE__ */ new Set(["wrap", "nowrap", "balance", "pretty"]);
2249
- const TEXT_OVERFLOW_KEYWORDS = /* @__PURE__ */ new Set(["ellipsis", "clip"]);
2250
- const FONT_WEIGHT_KEYWORDS = /* @__PURE__ */ new Set([
2251
- "thin",
2252
- "extralight",
2253
- "light",
2254
- "normal",
2255
- "medium",
2256
- "semibold",
2257
- "bold",
2258
- "extrabold",
2259
- "black"
2260
- ]);
2261
- const FONT_FAMILY_KEYWORDS = /* @__PURE__ */ new Set(["sans", "serif", "mono"]);
2262
- const FONT_STRETCH_KEYWORDS = /* @__PURE__ */ new Set([
2263
- "ultra-condensed",
2264
- "extra-condensed",
2265
- "condensed",
2266
- "semi-condensed",
2267
- "semi-expanded",
2268
- "expanded",
2269
- "extra-expanded",
2270
- "ultra-expanded"
2271
- ]);
2272
- const BORDER_WIDTH_KEYWORDS = /* @__PURE__ */ new Set(["0", "2", "4", "8"]);
2273
- const BORDER_STYLE_KEYWORDS = /* @__PURE__ */ new Set([
2274
- "solid",
2275
- "dashed",
2276
- "dotted",
2277
- "double",
2278
- "none",
2279
- "hidden"
2280
- ]);
2281
- const BG_POSITION_KEYWORDS = /* @__PURE__ */ new Set([
2282
- "center",
2283
- "top",
2284
- "bottom",
2285
- "left",
2286
- "right",
2287
- "left-top",
2288
- "left-bottom",
2289
- "right-top",
2290
- "right-bottom"
2291
- ]);
2292
- const BG_SIZE_KEYWORDS = /* @__PURE__ */ new Set(["cover", "contain", "auto"]);
2293
- const BG_REPEAT_KEYWORDS = /* @__PURE__ */ new Set([
2294
- "repeat",
2295
- "no-repeat",
2296
- "repeat-x",
2297
- "repeat-y",
2298
- "round",
2299
- "space"
2300
- ]);
2301
- const BG_ATTACHMENT_KEYWORDS = /* @__PURE__ */ new Set(["fixed", "local", "scroll"]);
2302
- const OBJECT_FIT_KEYWORDS = /* @__PURE__ */ new Set(["contain", "cover", "fill", "none", "scale-down"]);
2303
- const OBJECT_POSITION_KEYWORDS = /* @__PURE__ */ new Set([
2304
- "center",
2305
- "top",
2306
- "bottom",
2307
- "left",
2308
- "right",
2309
- "left-top",
2310
- "left-bottom",
2311
- "right-top",
2312
- "right-bottom"
2313
- ]);
2314
- const SHADOW_SIZE_KEYWORDS = /* @__PURE__ */ new Set(["sm", "md", "lg", "xl", "2xl", "inner", "none"]);
2315
- const OUTLINE_STYLE_KEYWORDS = /* @__PURE__ */ new Set(["none", "dashed", "dotted", "double"]);
2316
- const DECORATION_STYLE_KEYWORDS = /* @__PURE__ */ new Set(["solid", "double", "dotted", "dashed", "wavy"]);
2317
- const DECORATION_THICKNESS_KEYWORDS = /* @__PURE__ */ new Set([
2318
- "auto",
2319
- "from-font",
2320
- "0",
2321
- "1",
2322
- "2",
2323
- "4",
2324
- "8"
2325
- ]);
2326
- const TRANSITION_PROPERTY_KEYWORDS = /* @__PURE__ */ new Set([
2327
- "none",
2328
- "all",
2329
- "colors",
2330
- "opacity",
2331
- "shadow",
2332
- "transform"
2333
- ]);
2334
- const REVERSE_VARIANT_MAP = {
2335
- "focus-within": "focusWithin",
2336
- "focus-visible": "focusVisible",
2337
- "first-of-type": "firstOfType",
2338
- "last-of-type": "lastOfType",
2339
- "only-of-type": "onlyOfType",
2340
- "motion-reduce": "motionReduce",
2341
- "motion-safe": "motionSafe",
2342
- "contrast-more": "contrastMore",
2343
- "contrast-less": "contrastLess",
2344
- "first-line": "firstLine",
2345
- "first-letter": "firstLetter",
2346
- "placeholder-shown": "placeholderShown",
2347
- "in-range": "inRange",
2348
- "out-of-range": "outOfRange",
2349
- "read-only": "readOnly",
2350
- "pointer-fine": "pointerFine",
2351
- "pointer-coarse": "pointerCoarse",
2352
- "pointer-none": "pointerNone",
2353
- "@max-sm": "@maxSm",
2354
- "@max-md": "@maxMd",
2355
- "@max-lg": "@maxLg",
2356
- "@max-xl": "@maxXl",
2357
- "@max-2xl": "@max2xl"
2358
- };
2359
- const KNOWN_SIMPLE_VARIANTS = /* @__PURE__ */ new Set([
2360
- "sm",
2361
- "md",
2362
- "lg",
2363
- "xl",
2364
- "2xl",
2365
- "@sm",
2366
- "@md",
2367
- "@lg",
2368
- "@xl",
2369
- "@2xl",
2370
- "dark",
2371
- "light",
2372
- "print",
2373
- "portrait",
2374
- "landscape",
2375
- "hover",
2376
- "focus",
2377
- "active",
2378
- "visited",
2379
- "target",
2380
- "disabled",
2381
- "enabled",
2382
- "checked",
2383
- "indeterminate",
2384
- "default",
2385
- "required",
2386
- "valid",
2387
- "invalid",
2388
- "autofill",
2389
- "open",
2390
- "first",
2391
- "last",
2392
- "only",
2393
- "odd",
2394
- "even",
2395
- "empty",
2396
- "before",
2397
- "after",
2398
- "placeholder",
2399
- "file",
2400
- "marker",
2401
- "selection",
2402
- "backdrop",
2403
- "ltr",
2404
- "rtl"
2405
- ]);
2406
- /* @__PURE__ */ new Set([
2407
- ...KNOWN_SIMPLE_VARIANTS,
2408
- "focus-within",
2409
- "focus-visible",
2410
- "first-of-type",
2411
- "last-of-type",
2412
- "only-of-type",
2413
- "first-child",
2414
- "last-child",
2415
- "only-child",
2416
- "motion-reduce",
2417
- "motion-safe",
2418
- "contrast-more",
2419
- "contrast-less",
2420
- "first-line",
2421
- "first-letter",
2422
- "placeholder-shown",
2423
- "in-range",
2424
- "out-of-range",
2425
- "read-only",
2426
- "pointer-fine",
2427
- "pointer-coarse",
2428
- "pointer-none"
2429
- ]);
2430
-
2431
- function parseClass(cls, options = {}) {
2432
- let important = false;
2433
- let input = cls;
2434
- if (input.endsWith("!")) {
2435
- important = true;
2436
- input = input.slice(0, -1);
2437
- }
2438
- let negative = false;
2439
- let negInput = input;
2440
- if (input.startsWith("-")) {
2441
- negative = true;
2442
- negInput = input.slice(1);
2443
- }
2444
- const boolResult = tryBooleanMatch(input, options);
2445
- if (boolResult) {
2446
- return applyImportant(boolResult, important);
2447
- }
2448
- const gradResult = tryGradient(negInput, negative);
2449
- if (gradResult) {
2450
- return applyImportant(gradResult, important);
2451
- }
2452
- const source = negative ? negInput : input;
2453
- for (const prefix of SORTED_PREFIXES) {
2454
- if (source === prefix) {
2455
- const prop = REVERSE_PROPERTY_MAP[prefix];
2456
- if (negative && NEGATIVE_ALLOWED.has(prefix)) {
2457
- continue;
2458
- }
2459
- if (REVERSE_BOOLEAN_MAP[source]) {
2460
- return applyImportant(booleanClassToParsed(source, options), important);
2461
- }
2462
- if (prefix === "divide-x" || prefix === "divide-y") {
2463
- return applyImportant({ prop, value: true }, important);
2464
- }
2465
- if (prefix === "border") {
2466
- return applyImportant({ prop: "border", value: true }, important);
2467
- }
2468
- if ([
2469
- "border-t",
2470
- "border-r",
2471
- "border-b",
2472
- "border-l",
2473
- "border-x",
2474
- "border-y",
2475
- "border-s",
2476
- "border-e"
2477
- ].includes(prefix)) {
2478
- return applyImportant({ prop, value: true }, important);
2479
- }
2480
- continue;
2481
- }
2482
- if (source.startsWith(`${prefix}-`)) {
2483
- const rawValue = source.slice(prefix.length + 1);
2484
- if (!rawValue) {
2485
- continue;
2486
- }
2487
- if (negative && !NEGATIVE_ALLOWED.has(prefix)) {
2488
- continue;
2489
- }
2490
- if (SPACING_PROPS.has(prefix) && !isValidSpacingValue(rawValue)) {
2491
- continue;
2492
- }
2493
- const result = disambiguateAndParse(prefix, rawValue, negative);
2494
- if (result) {
2495
- return applyImportant(result, important);
2496
- }
2497
- }
2498
- }
2499
- const displayResult = tryDisplay(input, options);
2500
- if (displayResult) {
2501
- return applyImportant(displayResult, important);
2502
- }
2503
- if (input.startsWith("[") && input.endsWith("]") && input.includes(":")) {
2504
- const inner = input.slice(1, -1);
2505
- if (inner.startsWith("--")) {
2506
- const colonIdx = inner.indexOf(":");
2507
- return applyImportant(
2508
- {
2509
- prop: inner.slice(0, colonIdx),
2510
- value: inner.slice(colonIdx + 1)
2511
- },
2512
- important
2513
- );
2514
- }
2515
- }
2516
- return null;
2517
- }
2518
- function applyImportant(result, important) {
2519
- if (!important) {
2520
- return result;
2521
- }
2522
- if (typeof result.value === "string") {
2523
- return { prop: result.prop, value: `${result.value}!` };
2524
- }
2525
- if (typeof result.value === "boolean") {
2526
- return { prop: result.prop, value: "!" };
2527
- }
2528
- if (typeof result.value === "number") {
2529
- return { prop: result.prop, value: `${String(result.value)}!` };
2530
- }
2531
- return result;
2532
- }
2533
- function tryBooleanMatch(cls, options) {
2534
- if (BOOLEAN_VALUE_MAP[cls]) {
2535
- const { prop, value } = BOOLEAN_VALUE_MAP[cls];
2536
- return { prop, value };
2537
- }
2538
- if (REVERSE_BOOLEAN_MAP[cls]) {
2539
- return booleanClassToParsed(cls, options);
2540
- }
2541
- return null;
2542
- }
2543
- function tryDisplay(cls, options) {
2544
- const displayValues = /* @__PURE__ */ new Set([
2545
- "block",
2546
- "inline",
2547
- "inline-block",
2548
- "flex",
2549
- "inline-flex",
2550
- "grid",
2551
- "inline-grid",
2552
- "hidden",
2553
- "contents",
2554
- "table",
2555
- "table-row",
2556
- "table-cell",
2557
- "flow-root",
2558
- "list-item"
2559
- ]);
2560
- if (displayValues.has(cls)) {
2561
- return REVERSE_BOOLEAN_MAP[cls] ? booleanClassToParsed(cls, options) : null;
2562
- }
2563
- return null;
2564
- }
2565
- function booleanClassToParsed(cls, options) {
2566
- const displayValue = DISPLAY_CLASS_VALUES[cls];
2567
- if (options.display === "canonical" && displayValue) {
2568
- return { prop: "display", value: displayValue, cssProperty: "display" };
2569
- }
2570
- return { prop: REVERSE_BOOLEAN_MAP[cls], value: true };
2571
- }
2572
- const DISPLAY_CLASS_VALUES = {
2573
- block: "block",
2574
- inline: "inline",
2575
- "inline-block": "inline-block",
2576
- flex: "flex",
2577
- "inline-flex": "inline-flex",
2578
- grid: "grid",
2579
- "inline-grid": "inline-grid",
2580
- hidden: "none",
2581
- contents: "contents",
2582
- table: "table",
2583
- "table-row": "table-row",
2584
- "table-cell": "table-cell",
2585
- "flow-root": "flow-root",
2586
- "list-item": "list-item"
2587
- };
2588
- function tryGradient(cls, negative) {
2589
- let input = cls;
2590
- let type = null;
2591
- if (input.startsWith("bg-linear")) {
2592
- type = "linear";
2593
- input = input.slice("bg-linear".length);
2594
- } else if (input.startsWith("bg-radial")) {
2595
- type = "radial";
2596
- input = input.slice("bg-radial".length);
2597
- } else if (input.startsWith("bg-conic")) {
2598
- type = "conic";
2599
- input = input.slice("bg-conic".length);
2600
- }
2601
- if (!type) {
2602
- return null;
2603
- }
2604
- let colorInterp;
2605
- const slashIdx = findTopLevelSlash$1(input);
2606
- if (slashIdx !== -1) {
2607
- colorInterp = input.slice(slashIdx + 1);
2608
- input = input.slice(0, slashIdx);
2609
- }
2610
- const grad = { gradient: type };
2611
- if (input === "" || input === void 0) ; else if (input.startsWith("-")) {
2612
- const dir = input.slice(1);
2613
- if (dir.startsWith("[") && dir.endsWith("]")) {
2614
- grad.dir = dir.slice(1, -1).replace(/_/g, " ");
2615
- } else if (dir.startsWith("(") && dir.endsWith(")")) {
2616
- grad.dir = dir.slice(1, -1);
2617
- } else if (/^\d+$/.test(dir)) {
2618
- grad.dir = negative ? -parseInt(dir, 10) : parseInt(dir, 10);
2619
- } else {
2620
- grad.dir = dir;
2621
- }
2622
- }
2623
- if (colorInterp) {
2624
- grad.in = colorInterp;
2625
- }
2626
- return { prop: "bgImg", value: grad };
2627
- }
2628
- function findTopLevelSlash$1(s) {
2629
- let depth = 0;
2630
- for (let i = 0; i < s.length; i++) {
2631
- if (s[i] === "[" || s[i] === "(") {
2632
- depth++;
2633
- } else if (s[i] === "]" || s[i] === ")") {
2634
- depth--;
2635
- } else if (s[i] === "/" && depth === 0) {
2636
- return i;
2637
- }
2638
- }
2639
- return -1;
2640
- }
2641
- function disambiguateAndParse(prefix, rawValue, negative) {
2642
- const slashIdx = findTopLevelSlash$1(rawValue);
2643
- let opacity;
2644
- let value = rawValue;
2645
- if (slashIdx !== -1 && !isGradientPrefix(prefix)) {
2646
- const isFraction = FRACTION_SUPPORTED.has(prefix) && /^\d+\/\d+$/.test(rawValue);
2647
- if (!isFraction) {
2648
- opacity = rawValue.slice(slashIdx + 1);
2649
- value = rawValue.slice(0, slashIdx);
2650
- if (opacity.startsWith("[") && opacity.endsWith("]")) {
2651
- opacity = opacity.slice(1, -1);
2652
- if (!String(opacity).includes("%")) {
2653
- const opNum = Number(opacity);
2654
- if (!Number.isNaN(opNum)) {
2655
- opacity = opNum;
2656
- }
2657
- }
2658
- } else if (opacity.startsWith("(") && opacity.endsWith(")")) {
2659
- opacity = opacity.slice(1, -1);
2660
- } else {
2661
- const opNum = Number(opacity);
2662
- if (!Number.isNaN(opNum)) {
2663
- opacity = opNum;
2664
- }
2665
- }
2666
- }
2667
- }
2668
- const result = disambiguate(prefix, value, negative);
2669
- if (!result) {
2670
- return null;
2671
- }
2672
- if (opacity !== void 0 && typeof result.value === "string") {
2673
- return {
2674
- prop: result.prop,
2675
- value: { color: result.value, op: opacity }
2676
- };
2677
- }
2678
- return result;
2679
- }
2680
- function isGradientPrefix(prefix) {
2681
- return prefix === "from" || prefix === "via" || prefix === "to";
2682
- }
2683
- function disambiguate(prefix, value, negative) {
2684
- switch (prefix) {
2685
- case "text":
2686
- return disambiguateText(value);
2687
- case "font":
2688
- return disambiguateFont(value);
2689
- case "border":
2690
- return disambiguateBorder(value);
2691
- case "bg":
2692
- return disambiguateBg(value);
2693
- case "object":
2694
- return disambiguateObject(value);
2695
- case "shadow":
2696
- return disambiguateShadow(value);
2697
- case "outline":
2698
- return disambiguateOutline(value);
2699
- case "decoration":
2700
- return disambiguateDecoration(value);
2701
- case "transition":
2702
- return disambiguateTransition(value);
2703
- case "ring":
2704
- return disambiguateRing(value, negative);
2705
- case "ring-offset":
2706
- return disambiguateRingOffset(value);
2707
- case "inset-ring":
2708
- return disambiguateInsetRing(value, negative);
2709
- case "inset-shadow":
2710
- return disambiguateInsetShadow(value);
2711
- case "stroke":
2712
- return disambiguateStroke(value);
2713
- case "list":
2714
- return disambiguateList(value);
2715
- case "ease":
2716
- return { prop: "ease", value: parseValue("ease", value, negative) };
2717
- case "snap":
2718
- return disambiguateSnap();
2719
- case "flex":
2720
- return disambiguateFlex(value);
2721
- case "table":
2722
- return disambiguateTable(value);
2723
- case "divide":
2724
- return disambiguateDivide(value);
2725
- case "break":
2726
- if (value === "words") {
2727
- return { prop: "wrap", value: "break-word" };
2728
- }
2729
- return { prop: "break", value };
2730
- case "wrap":
2731
- return { prop: "wrap", value };
2732
- default:
2733
- return {
2734
- prop: REVERSE_PROPERTY_MAP[prefix] || prefix,
2735
- value: parseValue(prefix, value, negative)
2736
- };
2737
- }
2738
- }
2739
- function disambiguateText(value) {
2740
- if (TEXT_SIZE_KEYWORDS.has(value)) {
2741
- return { prop: "text", value };
2742
- }
2743
- if (TEXT_ALIGN_KEYWORDS.has(value)) {
2744
- return { prop: "textAlign", value };
2745
- }
2746
- if (TEXT_WRAP_KEYWORDS.has(value)) {
2747
- return { prop: "textWrap", value };
2748
- }
2749
- if (TEXT_OVERFLOW_KEYWORDS.has(value)) {
2750
- return { prop: "textOverflow", value };
2751
- }
2752
- if (isArbitraryDimension(value)) {
2753
- return { prop: "text", value: parseStringValue(value) };
2754
- }
2755
- return { prop: "color", value: parseStringValue(value) };
2756
- }
2757
- function disambiguateFont(value) {
2758
- if (FONT_WEIGHT_KEYWORDS.has(value)) {
2759
- return { prop: "fontWeight", value };
2760
- }
2761
- if (/^\d{3}$/.test(value)) {
2762
- return { prop: "fontWeight", value: parseInt(value, 10) };
2763
- }
2764
- if (FONT_FAMILY_KEYWORDS.has(value)) {
2765
- return { prop: "fontFamily", value };
2766
- }
2767
- if (value.startsWith("stretch-")) {
2768
- const stretchVal = value.slice("stretch-".length);
2769
- return { prop: "fontStretch", value: stretchVal };
2770
- }
2771
- if (FONT_STRETCH_KEYWORDS.has(value)) {
2772
- return { prop: "fontStretch", value };
2773
- }
2774
- return { prop: "fontFamily", value: parseStringValue(value) };
2775
- }
2776
- function disambiguateBorder(value) {
2777
- if (BORDER_WIDTH_KEYWORDS.has(value) || value === "px") {
2778
- return { prop: "border", value: parseNumericOrString("border", value, false) };
2779
- }
2780
- if (BORDER_STYLE_KEYWORDS.has(value)) {
2781
- return { prop: "borderStyle", value };
2782
- }
2783
- if (isArbitraryDimension(value)) {
2784
- return { prop: "border", value: parseStringValue(value) };
2785
- }
2786
- return { prop: "borderColor", value: parseStringValue(value) };
2787
- }
2788
- function disambiguateBg(value) {
2789
- if (BG_POSITION_KEYWORDS.has(value)) {
2790
- return { prop: "bgPos", value };
2791
- }
2792
- if (BG_SIZE_KEYWORDS.has(value)) {
2793
- return { prop: "bgSize", value };
2794
- }
2795
- if (BG_REPEAT_KEYWORDS.has(value)) {
2796
- return { prop: "bgRepeat", value };
2797
- }
2798
- if (BG_ATTACHMENT_KEYWORDS.has(value)) {
2799
- return { prop: "bgAttach", value };
2800
- }
2801
- if (value === "none") {
2802
- return { prop: "bgImg", value: "none" };
2803
- }
2804
- return { prop: "bg", value: parseStringValue(value) };
2805
- }
2806
- function disambiguateObject(value) {
2807
- if (OBJECT_FIT_KEYWORDS.has(value)) {
2808
- return { prop: "objectFit", value };
2809
- }
2810
- if (OBJECT_POSITION_KEYWORDS.has(value)) {
2811
- return { prop: "objectPos", value };
2812
- }
2813
- return { prop: "objectPos", value: parseStringValue(value) };
2814
- }
2815
- function disambiguateShadow(value) {
2816
- if (SHADOW_SIZE_KEYWORDS.has(value)) {
2817
- return { prop: "shadow", value };
2818
- }
2819
- return { prop: "shadowColor", value: parseStringValue(value) };
2820
- }
2821
- function disambiguateOutline(value) {
2822
- if (OUTLINE_STYLE_KEYWORDS.has(value)) {
2823
- return { prop: "outlineStyle", value };
2824
- }
2825
- const num = Number(value);
2826
- if (!Number.isNaN(num) && Number.isInteger(num)) {
2827
- return { prop: "outline", value: num };
2828
- }
2829
- if (isArbitraryDimension(value)) {
2830
- return { prop: "outline", value: parseStringValue(value) };
2831
- }
2832
- return { prop: "outlineColor", value: parseStringValue(value) };
2833
- }
2834
- function disambiguateDecoration(value) {
2835
- if (DECORATION_STYLE_KEYWORDS.has(value)) {
2836
- return { prop: "decorationStyle", value };
2837
- }
2838
- if (DECORATION_THICKNESS_KEYWORDS.has(value)) {
2839
- const num = Number(value);
2840
- if (!Number.isNaN(num)) {
2841
- return { prop: "decorationThickness", value };
2842
- }
2843
- return { prop: "decorationThickness", value };
2844
- }
2845
- return { prop: "decorationColor", value: parseStringValue(value) };
2846
- }
2847
- function disambiguateRing(value, negative) {
2848
- const num = Number(value);
2849
- if (!Number.isNaN(num) && Number.isInteger(num)) {
2850
- return { prop: "ring", value: negative ? -num : num };
2851
- }
2852
- if (isArbitraryDimension(value)) {
2853
- return { prop: "ring", value: parseStringValue(value) };
2854
- }
2855
- return { prop: "ringColor", value: parseStringValue(value) };
2856
- }
2857
- function disambiguateRingOffset(value) {
2858
- const num = Number(value);
2859
- if (!Number.isNaN(num) && Number.isInteger(num)) {
2860
- return { prop: "ringOffset", value: num };
2861
- }
2862
- return { prop: "ringOffsetColor", value: parseStringValue(value) };
2863
- }
2864
- function disambiguateInsetRing(value, negative) {
2865
- const num = Number(value);
2866
- if (!Number.isNaN(num) && Number.isInteger(num)) {
2867
- return { prop: "insetRing", value: negative ? -num : num };
2868
- }
2869
- if (isArbitraryDimension(value)) {
2870
- return { prop: "insetRing", value: parseStringValue(value) };
2871
- }
2872
- return { prop: "insetRingColor", value: parseStringValue(value) };
2873
- }
2874
- function disambiguateInsetShadow(value) {
2875
- const INSET_SHADOW_SIZE_KEYWORDS = /* @__PURE__ */ new Set(["sm", "md", "lg", "xl", "2xl", "none", "inner"]);
2876
- if (INSET_SHADOW_SIZE_KEYWORDS.has(value)) {
2877
- return { prop: "insetShadow", value };
2878
- }
2879
- if (isArbitraryDimension(value)) {
2880
- return { prop: "insetShadow", value: parseStringValue(value) };
2881
- }
2882
- return { prop: "insetShadowColor", value: parseStringValue(value) };
2883
- }
2884
- function disambiguateStroke(value) {
2885
- const num = Number(value);
2886
- if (!Number.isNaN(num) && Number.isInteger(num)) {
2887
- return { prop: "strokeWidth", value: num };
2888
- }
2889
- return { prop: "stroke", value: parseStringValue(value) };
2890
- }
2891
- function disambiguateTransition(value) {
2892
- if (TRANSITION_PROPERTY_KEYWORDS.has(value)) {
2893
- return { prop: "transition", value };
2894
- }
2895
- return { prop: "transition", value: parseStringValue(value) };
2896
- }
2897
- function disambiguateList(value) {
2898
- if (value === "inside" || value === "outside") {
2899
- return { prop: "listPos", value };
2900
- }
2901
- return { prop: "list", value: parseStringValue(value) };
2902
- }
2903
- function disambiguateSnap(_value) {
2904
- return null;
2905
- }
2906
- function disambiguateFlex(value) {
2907
- const dirValues = /* @__PURE__ */ new Set(["row", "col", "row-reverse", "col-reverse"]);
2908
- if (dirValues.has(value)) {
2909
- return { prop: "flexDir", value };
2910
- }
2911
- const wrapValues = /* @__PURE__ */ new Set(["wrap", "nowrap", "wrap-reverse"]);
2912
- if (wrapValues.has(value)) {
2913
- return { prop: "flexWrap", value };
2914
- }
2915
- if (value === "1" || value === "auto" || value === "initial" || value === "none") {
2916
- return { prop: "flex", value: parseStringValue(value) };
2917
- }
2918
- return { prop: "flex", value: parseStringValue(value) };
2919
- }
2920
- function disambiguateTable(value) {
2921
- if (value === "auto" || value === "fixed") {
2922
- return { prop: "tableLayout", value };
2923
- }
2924
- return null;
2925
- }
2926
- function disambiguateDivide(value) {
2927
- return { prop: "divideColor", value: parseStringValue(value) };
2928
- }
2929
- const CSS_DIMENSION_RE = /^-?[\d.]+(?:px|r?em|ex|ch|vw|vh|vmin|vmax|svh|svw|dvh|dvw|lvh|lvw|cqw|cqh|cqi|cqb|%|fr|deg|rad|turn|grad|ms|s|pt|pc|cm|mm|in)$/;
2930
- function isArbitraryDimension(value) {
2931
- if (!value.startsWith("[") || !value.endsWith("]")) {
2932
- return false;
2933
- }
2934
- return CSS_DIMENSION_RE.test(value.slice(1, -1));
2935
- }
2936
- function isValidSpacingValue(value) {
2937
- if (value.startsWith("[") && value.endsWith("]")) {
2938
- return true;
2939
- }
2940
- if (value.startsWith("(") && value.endsWith(")")) {
2941
- return true;
2942
- }
2943
- if (!Number.isNaN(Number(value))) {
2944
- return true;
2945
- }
2946
- if (/^\d+\/\d+$/.test(value)) {
2947
- return true;
2948
- }
2949
- if ([
2950
- "auto",
2951
- "full",
2952
- "screen",
2953
- "px",
2954
- "min",
2955
- "max",
2956
- "fit",
2957
- "none",
2958
- "dvh",
2959
- "dvw",
2960
- "svh",
2961
- "svw",
2962
- "lvh",
2963
- "lvw",
2964
- // Max-width size keywords
2965
- "xs",
2966
- "sm",
2967
- "md",
2968
- "lg",
2969
- "xl",
2970
- "2xl",
2971
- "3xl",
2972
- "4xl",
2973
- "5xl",
2974
- "6xl",
2975
- "7xl",
2976
- "prose",
2977
- "screen-sm",
2978
- "screen-md",
2979
- "screen-lg",
2980
- "screen-xl",
2981
- "screen-2xl",
2982
- // Size keywords used in min-h, max-h
2983
- "content"
2984
- ].includes(value)) {
2985
- return true;
2986
- }
2987
- if (value.includes("/")) {
2988
- return true;
2989
- }
2990
- return false;
2991
- }
2992
- function parseValue(prefix, value, negative) {
2993
- if (value.startsWith("[") && value.endsWith("]")) {
2994
- const inner = value.slice(1, -1).replace(/_/g, " ");
2995
- if (negative) {
2996
- return `-${inner}`;
2997
- }
2998
- if (prefix === "content") {
2999
- const isQuoted = inner.startsWith("'") && inner.endsWith("'") || inner.startsWith('"') && inner.endsWith('"');
3000
- if (isQuoted) {
3001
- return `"${inner.slice(1, -1)}"`;
3002
- }
3003
- }
3004
- return inner;
3005
- }
3006
- if (value.startsWith("(") && value.endsWith(")")) {
3007
- const inner = value.slice(1, -1);
3008
- if (negative) {
3009
- return `-${inner}`;
3010
- }
3011
- return inner;
3012
- }
3013
- if (FRACTION_SUPPORTED.has(prefix) && /^\d+\/\d+$/.test(value)) {
3014
- return value;
3015
- }
3016
- if (value === "px") {
3017
- return negative ? "-px" : "px";
3018
- }
3019
- if (value === "auto") {
3020
- return "auto";
3021
- }
3022
- if (value === "full") {
3023
- return "full";
3024
- }
3025
- if (value === "screen") {
3026
- return "screen";
3027
- }
3028
- const num = Number(value);
3029
- if (!Number.isNaN(num)) {
3030
- if (negative) {
3031
- return -num;
3032
- }
3033
- return num;
3034
- }
3035
- if (negative) {
3036
- return `-${value}`;
3037
- }
3038
- return value;
3039
- }
3040
- function parseNumericOrString(prefix, value, negative) {
3041
- if (value === "px") {
3042
- return "px";
3043
- }
3044
- const num = Number(value);
3045
- if (!Number.isNaN(num)) {
3046
- return negative ? -num : num;
3047
- }
3048
- return value;
3049
- }
3050
- function parseStringValue(value) {
3051
- if (value.startsWith("[") && value.endsWith("]")) {
3052
- return value.slice(1, -1).replace(/_/g, " ");
3053
- }
3054
- if (value.startsWith("(") && value.endsWith(")")) {
3055
- return value.slice(1, -1);
3056
- }
3057
- return value;
3058
- }
3059
-
3060
- function tokenize(className) {
3061
- return className.trim().split(/\s+/).filter(Boolean);
3062
- }
3063
- function extractVariants(token) {
3064
- const parts = [];
3065
- let current = "";
3066
- let depth = 0;
3067
- for (let i = 0; i < token.length; i++) {
3068
- const ch = token[i];
3069
- if (ch === "[" || ch === "(") {
3070
- depth++;
3071
- current += ch;
3072
- } else if (ch === "]" || ch === ")") {
3073
- depth--;
3074
- current += ch;
3075
- } else if (ch === ":" && depth === 0) {
3076
- parts.push(current);
3077
- current = "";
3078
- } else {
3079
- current += ch;
3080
- }
3081
- }
3082
- if (parts.length === 0) {
3083
- return { variantParts: [], baseClass: current };
3084
- }
3085
- return { variantParts: parts, baseClass: current };
3086
- }
3087
- function mapVariant(variant) {
3088
- if (variant.startsWith("@")) {
3089
- if (variant === "@container") {
3090
- return ["@container"];
3091
- }
3092
- const slashIdx = variant.indexOf("/");
3093
- if (slashIdx !== -1) {
3094
- const queryPart = variant.slice(0, slashIdx);
3095
- const namePart = variant.slice(slashIdx + 1);
3096
- return [normalizeVariantKey(queryPart), namePart];
3097
- }
3098
- const match = variant.match(/^(@min|@max)-\[(.+)\]$/);
3099
- if (match) {
3100
- return [match[1], match[2]];
3101
- }
3102
- return [normalizeVariantKey(variant)];
3103
- }
3104
- if (variant.startsWith("group-") || variant.startsWith("peer-")) {
3105
- return parseGroupPeerVariant(variant);
3106
- }
3107
- if (variant.startsWith("has-")) {
3108
- const rest = variant.slice(4);
3109
- if (rest.startsWith("[") && rest.endsWith("]")) {
3110
- let selector = rest.slice(1, -1);
3111
- if (selector.startsWith(":")) {
3112
- selector = selector.slice(1);
3113
- }
3114
- return ["has", selector];
3115
- }
3116
- return ["has", rest];
3117
- }
3118
- if (variant.startsWith("not-")) {
3119
- const rest = variant.slice(4);
3120
- if (rest.startsWith("supports-[") && rest.endsWith("]")) {
3121
- const cond = rest.slice(10, -1);
3122
- return ["not", "supports", cond];
3123
- }
3124
- return ["not", normalizeVariantKey(rest)];
3125
- }
3126
- if (variant.startsWith("data-")) {
3127
- const rest = variant.slice(5);
3128
- if (rest.startsWith("[") && rest.endsWith("]")) {
3129
- return ["data", rest.slice(1, -1)];
3130
- }
3131
- return ["data", rest];
3132
- }
3133
- if (variant.startsWith("aria-")) {
3134
- const rest = variant.slice(5);
3135
- if (rest.startsWith("[") && rest.endsWith("]")) {
3136
- return ["aria", rest.slice(1, -1)];
3137
- }
3138
- return ["aria", rest];
3139
- }
3140
- if (variant.startsWith("supports-")) {
3141
- const rest = variant.slice(9);
3142
- if (rest.startsWith("[") && rest.endsWith("]")) {
3143
- return ["supports", rest.slice(1, -1)];
3144
- }
3145
- return ["supports", rest];
3146
- }
3147
- if (variant.startsWith("min-") || variant.startsWith("max-")) {
3148
- const prefix = variant.startsWith("min-") ? "min" : "max";
3149
- const rest = variant.slice(4);
3150
- if (rest.startsWith("[") && rest.endsWith("]")) {
3151
- return [prefix, rest.slice(1, -1)];
3152
- }
3153
- return [prefix, rest];
3154
- }
3155
- if (variant.startsWith("[") && variant.endsWith("]")) {
3156
- return [variant];
3157
- }
3158
- return [normalizeVariantKey(variant)];
3159
- }
3160
- function parseGroupPeerVariant(variant) {
3161
- const isGroup = variant.startsWith("group-");
3162
- const type = isGroup ? "group" : "peer";
3163
- let rest = variant.slice(type.length + 1);
3164
- let name;
3165
- const slashIdx = findTopLevelSlash(rest);
3166
- if (slashIdx !== -1) {
3167
- name = rest.slice(slashIdx + 1);
3168
- rest = rest.slice(0, slashIdx);
3169
- }
3170
- const keys = [type];
3171
- if (name) {
3172
- keys.push(name);
3173
- }
3174
- if (rest.startsWith("[") && rest.endsWith("]")) {
3175
- keys.push(rest.slice(1, -1));
3176
- } else if (rest.startsWith("has-")) {
3177
- const hasRest = rest.slice(4);
3178
- if (hasRest.startsWith("[") && hasRest.endsWith("]")) {
3179
- keys.push("has");
3180
- keys.push(hasRest.slice(1, -1));
3181
- } else {
3182
- keys.push("has");
3183
- keys.push(hasRest);
3184
- }
3185
- } else if (rest.startsWith("data-")) {
3186
- const dataRest = rest.slice(5);
3187
- keys.push("data");
3188
- if (dataRest.startsWith("[") && dataRest.endsWith("]")) {
3189
- keys.push(dataRest.slice(1, -1));
3190
- } else if (dataRest.startsWith("(") && dataRest.endsWith(")")) {
3191
- keys.push(dataRest.slice(1, -1));
3192
- } else {
3193
- keys.push(dataRest);
3194
- }
3195
- } else if (rest.startsWith("aria-")) {
3196
- const ariaRest = rest.slice(5);
3197
- keys.push("aria");
3198
- if (ariaRest.startsWith("[") && ariaRest.endsWith("]")) {
3199
- keys.push(ariaRest.slice(1, -1));
3200
- } else {
3201
- keys.push(ariaRest);
3202
- }
3203
- } else {
3204
- keys.push(normalizeVariantKey(rest));
3205
- }
3206
- return keys;
3207
- }
3208
- function findTopLevelSlash(s) {
3209
- let depth = 0;
3210
- for (let i = 0; i < s.length; i++) {
3211
- if (s[i] === "[" || s[i] === "(") {
3212
- depth++;
3213
- } else if (s[i] === "]" || s[i] === ")") {
3214
- depth--;
3215
- } else if (s[i] === "/" && depth === 0) {
3216
- return i;
3217
- }
3218
- }
3219
- return -1;
3220
- }
3221
- function normalizeVariantKey(variant) {
3222
- if (REVERSE_VARIANT_MAP[variant]) {
3223
- return REVERSE_VARIANT_MAP[variant];
3224
- }
3225
- if (variant.startsWith("@")) {
3226
- return variant;
3227
- }
3228
- return variant;
3229
- }
3230
- const TODO_KEEP = "sz:keep";
3231
- const TODO_REMOVE = "sz:remove";
3232
- const TODO_PENDING = "sz:todo";
3233
- const MAX_TOKEN_CACHE_SIZE = 4096;
3234
- const parsedTokenCache = /* @__PURE__ */ new Map();
3235
- function resolveCustomMapEntry(token, customMap, resolveString) {
3236
- if (!(token in customMap)) {
3237
- return null;
3238
- }
3239
- const val = customMap[token];
3240
- if (val && typeof val === "object" && !Array.isArray(val)) {
3241
- return { action: "sz", value: val };
3242
- }
3243
- if (typeof val === "string") {
3244
- if (val === TODO_KEEP) {
3245
- return { action: "keep" };
3246
- }
3247
- if (val === TODO_REMOVE) {
3248
- return { action: "remove" };
3249
- }
3250
- if (val === TODO_PENDING) {
3251
- return { action: "unresolved" };
3252
- }
3253
- const result = resolveString(val);
3254
- if (result && Object.keys(result.sz).length > 0) {
3255
- return { action: "sz", value: result.sz, cascade: result.cascade };
3256
- }
3257
- return { action: "unresolved" };
3258
- }
3259
- return { action: "unresolved" };
3260
- }
3261
- function classNameToSzObject(className, customMap) {
3262
- const tokens = tokenize(className);
3263
- const szObject = {};
3264
- const unrecognized = [];
3265
- const keepInClassName = [];
3266
- const seenCssPropertiesByPath = /* @__PURE__ */ new Map();
3267
- const conflictedCssPropertiesByPath = /* @__PURE__ */ new Map();
3268
- for (const token of tokens) {
3269
- if (customMap && token in customMap) {
3270
- const entry = resolveCustomMapEntry(
3271
- token,
3272
- customMap,
3273
- // Inline resolver: parse Tailwind string recursively (no customMap to avoid infinite loop).
3274
- // Returns both the sz object and any unrecognized tokens from the string value,
3275
- // so partially-valid strings cascade their unknowns back to the unrecognized list.
3276
- (twStr) => {
3277
- const inner = classNameToSzObject(twStr);
3278
- if (Object.keys(inner.szObject).length === 0) {
3279
- return null;
3280
- }
3281
- return { sz: inner.szObject, cascade: inner.unrecognized };
3282
- }
3283
- );
3284
- if (entry) {
3285
- if (entry.action === "sz") {
3286
- Object.assign(szObject, entry.value);
3287
- if (entry.cascade && entry.cascade.length > 0) {
3288
- unrecognized.push(...entry.cascade);
3289
- }
3290
- continue;
3291
- }
3292
- if (entry.action === "keep") {
3293
- keepInClassName.push(token);
3294
- continue;
3295
- }
3296
- if (entry.action === "remove") {
3297
- continue;
3298
- }
3299
- if (entry.action === "unresolved") {
3300
- unrecognized.push(token);
3301
- continue;
3302
- }
3303
- }
3304
- }
3305
- const parsedToken = parseClassTokenCached(token);
3306
- if (!parsedToken) {
3307
- unrecognized.push(token);
3308
- continue;
3309
- }
3310
- if (isCssPropertyConflicted(conflictedCssPropertiesByPath, parsedToken)) {
3311
- unrecognized.push(token);
3312
- continue;
3313
- }
3314
- const conflict = findCssPropertyConflict(seenCssPropertiesByPath, parsedToken, token);
3315
- if (conflict) {
3316
- rememberCssPropertyConflict(conflictedCssPropertiesByPath, parsedToken);
3317
- unrecognized.push(conflict, token);
3318
- removeNestedValue(szObject, parsedToken.keyPath, parsedToken.prop);
3319
- continue;
3320
- }
3321
- rememberCssProperty(seenCssPropertiesByPath, parsedToken, token);
3322
- setNestedValue(
3323
- szObject,
3324
- parsedToken.keyPath,
3325
- parsedToken.prop,
3326
- cloneParsedValue(parsedToken.value)
3327
- );
3328
- }
3329
- return { szObject, unrecognized, keepInClassName };
3330
- }
3331
- function parseClassTokenCached(token) {
3332
- if (parsedTokenCache.has(token)) {
3333
- return parsedTokenCache.get(token) ?? null;
3334
- }
3335
- const parsed = parseClassToken(token);
3336
- rememberParsedToken(token, parsed);
3337
- return parsed;
3338
- }
3339
- function parseClassToken(token) {
3340
- const { variantParts, baseClass } = extractVariants(token);
3341
- const parsed = parseClass(baseClass, { display: "canonical" });
3342
- if (!parsed) {
3343
- return null;
3344
- }
3345
- const keyPath = [];
3346
- for (const variant of variantParts) {
3347
- keyPath.push(...mapVariant(variant));
3348
- }
3349
- return {
3350
- keyPath,
3351
- prop: parsed.prop,
3352
- value: parsed.value,
3353
- cssProperty: parsed.cssProperty
3354
- };
3355
- }
3356
- function rememberParsedToken(token, parsed) {
3357
- if (parsedTokenCache.size >= MAX_TOKEN_CACHE_SIZE) {
3358
- const oldest = parsedTokenCache.keys().next().value;
3359
- if (oldest !== void 0) {
3360
- parsedTokenCache.delete(oldest);
3361
- }
3362
- }
3363
- parsedTokenCache.set(token, parsed);
3364
- }
3365
- function cloneParsedValue(value) {
3366
- if (Array.isArray(value)) {
3367
- return value.map(cloneParsedValue);
3368
- }
3369
- if (value && typeof value === "object") {
3370
- const clone = {};
3371
- for (const [key, nested] of Object.entries(value)) {
3372
- clone[key] = cloneParsedValue(nested);
3373
- }
3374
- return clone;
3375
- }
3376
- return value;
3377
- }
3378
- function setNestedValue(obj, keyPath, prop, value) {
3379
- let current = obj;
3380
- for (const key of keyPath) {
3381
- if (!(key in current) || typeof current[key] !== "object" || current[key] === null) {
3382
- current[key] = {};
3383
- }
3384
- current = current[key];
3385
- }
3386
- current[prop] = value;
3387
- }
3388
- function findCssPropertyConflict(seen, parsed, token) {
3389
- if (!parsed.cssProperty) {
3390
- return null;
3391
- }
3392
- const scope = parsed.keyPath.join("\0");
3393
- const previous = seen.get(scope)?.get(parsed.cssProperty);
3394
- return previous && previous !== token ? previous : null;
3395
- }
3396
- function isCssPropertyConflicted(conflicted, parsed) {
3397
- if (!parsed.cssProperty) {
3398
- return false;
3399
- }
3400
- return conflicted.get(parsed.keyPath.join("\0"))?.has(parsed.cssProperty) === true;
3401
- }
3402
- function rememberCssPropertyConflict(conflicted, parsed) {
3403
- if (!parsed.cssProperty) {
3404
- return;
3405
- }
3406
- const scope = parsed.keyPath.join("\0");
3407
- let properties = conflicted.get(scope);
3408
- if (!properties) {
3409
- properties = /* @__PURE__ */ new Set();
3410
- conflicted.set(scope, properties);
3411
- }
3412
- properties.add(parsed.cssProperty);
3413
- }
3414
- function rememberCssProperty(seen, parsed, token) {
3415
- if (!parsed.cssProperty) {
3416
- return;
3417
- }
3418
- const scope = parsed.keyPath.join("\0");
3419
- let properties = seen.get(scope);
3420
- if (!properties) {
3421
- properties = /* @__PURE__ */ new Map();
3422
- seen.set(scope, properties);
3423
- }
3424
- properties.set(parsed.cssProperty, token);
3425
- }
3426
- function removeNestedValue(obj, keyPath, prop) {
3427
- let current = obj;
3428
- const parents = [];
3429
- for (const key of keyPath) {
3430
- const next = current[key];
3431
- if (!next || typeof next !== "object" || Array.isArray(next)) {
3432
- return;
3433
- }
3434
- parents.push([current, key]);
3435
- current = next;
3436
- }
3437
- delete current[prop];
3438
- for (let index = parents.length - 1; index >= 0; index--) {
3439
- const [parent, key] = parents[index];
3440
- const child = parent[key];
3441
- if (child && typeof child === "object" && Object.keys(child).length === 0) {
3442
- delete parent[key];
3443
- }
3444
- }
3445
- }
3446
-
3447
- const CLSX_LIKE_NAMES = /* @__PURE__ */ new Set(["clsx", "cn", "cx", "twMerge", "classNames", "classnames"]);
3448
- function isClsxLikeName(name) {
3449
- return CLSX_LIKE_NAMES.has(name);
3450
- }
3451
- function handleClsxCall(node, source, t, customMap) {
3452
- const warnings = [];
3453
- const allUnrecognized = [];
3454
- const elements = [];
3455
- let converted = 0;
3456
- for (const arg of node.arguments) {
3457
- if (t.isSpreadElement(arg)) {
3458
- const argSrc2 = safeSlice(source, arg.start, arg.end);
3459
- warnings.push(`Cannot migrate spread argument: ${argSrc2}`);
3460
- return skip(allUnrecognized, warnings);
3461
- }
3462
- if (t.isStringLiteral(arg)) {
3463
- const result = migrateString(arg.value, customMap);
3464
- if (!result) {
3465
- return skip(allUnrecognized, warnings);
3466
- }
3467
- elements.push(result.objectStr);
3468
- allUnrecognized.push(...result.unrecognized);
3469
- converted++;
3470
- continue;
3471
- }
3472
- if (t.isLogicalExpression(arg) && arg.operator === "&&") {
3473
- const result = handleLogicalAndInner(arg, source, t, customMap);
3474
- if (!result) {
3475
- const argSrc2 = safeSlice(source, arg.start, arg.end);
3476
- warnings.push(`Cannot migrate logical expression: ${argSrc2}`);
3477
- return skip(allUnrecognized, warnings);
3478
- }
3479
- elements.push(result.exprStr);
3480
- allUnrecognized.push(...result.unrecognized);
3481
- converted++;
3482
- continue;
3483
- }
3484
- if (t.isConditionalExpression(arg)) {
3485
- const result = handleTernaryInner(arg, source, t, customMap);
3486
- if (!result) {
3487
- const argSrc2 = safeSlice(source, arg.start, arg.end);
3488
- warnings.push(`Cannot migrate ternary: ${argSrc2}`);
3489
- return skip(allUnrecognized, warnings);
3490
- }
3491
- elements.push(result.exprStr);
3492
- allUnrecognized.push(...result.unrecognized);
3493
- converted++;
3494
- continue;
3495
- }
3496
- const argSrc = safeSlice(source, arg.start, arg.end);
3497
- warnings.push(`Cannot migrate argument: ${argSrc}`);
3498
- return skip(allUnrecognized, warnings);
3499
- }
3500
- if (elements.length === 0) {
3501
- return skip(allUnrecognized, warnings);
3502
- }
3503
- if (elements.length === 1 && !elements[0].includes("&&") && !elements[0].includes("?")) {
3504
- return {
3505
- replacement: `sz={${elements[0]}}`,
3506
- unrecognized: allUnrecognized,
3507
- warnings,
3508
- converted,
3509
- migrated: true
3510
- };
3511
- }
3512
- return {
3513
- replacement: `sz={[${elements.join(", ")}]}`,
3514
- unrecognized: allUnrecognized,
3515
- warnings,
3516
- converted,
3517
- migrated: true
3518
- };
3519
- }
3520
- function handleTernary(node, source, t, customMap) {
3521
- const result = handleTernaryInner(node, source, t, customMap);
3522
- if (!result) {
3523
- return {
3524
- replacement: "",
3525
- unrecognized: [],
3526
- warnings: ["Ternary branches must be string literals"],
3527
- converted: 0,
3528
- migrated: false
3529
- };
3530
- }
3531
- return {
3532
- replacement: `sz={${result.exprStr}}`,
3533
- unrecognized: result.unrecognized,
3534
- warnings: [],
3535
- converted: 1,
3536
- migrated: true
3537
- };
3538
- }
3539
- function handleLogicalAnd(node, source, t, customMap) {
3540
- if (node.operator !== "&&") {
3541
- return {
3542
- replacement: "",
3543
- unrecognized: [],
3544
- warnings: [`Unsupported logical operator: ${node.operator}`],
3545
- converted: 0,
3546
- migrated: false
3547
- };
3548
- }
3549
- const result = handleLogicalAndInner(node, source, t, customMap);
3550
- if (!result) {
3551
- return {
3552
- replacement: "",
3553
- unrecognized: [],
3554
- warnings: ["Right side of && must be a string literal"],
3555
- converted: 0,
3556
- migrated: false
3557
- };
3558
- }
3559
- return {
3560
- replacement: `sz={${result.exprStr}}`,
3561
- unrecognized: result.unrecognized,
3562
- warnings: [],
3563
- converted: 1,
3564
- migrated: true
3565
- };
3566
- }
3567
- function handleTemplateLiteral(node, source, t, customMap) {
3568
- const warnings = [];
3569
- const allUnrecognized = [];
3570
- const staticText = node.quasis.map((q) => q.value.cooked ?? q.value.raw).join(" ");
3571
- const trimmedStatic = staticText.replace(/\s+/g, " ").trim();
3572
- let baseObject = {};
3573
- if (trimmedStatic) {
3574
- const { szObject, unrecognized } = classNameToSzObject(trimmedStatic, customMap);
3575
- baseObject = szObject;
3576
- allUnrecognized.push(...unrecognized);
3577
- }
3578
- const dynamicElements = [];
3579
- let converted = 0;
3580
- for (const expr of node.expressions) {
3581
- if (!isExpression(expr, t)) {
3582
- const exprSrc2 = safeSlice(
3583
- source,
3584
- expr.start,
3585
- expr.end
3586
- );
3587
- warnings.push(`Cannot migrate template expression: ${exprSrc2}`);
3588
- return skip(allUnrecognized, warnings);
3589
- }
3590
- if (t.isStringLiteral(expr)) {
3591
- const result = migrateString(expr.value, customMap);
3592
- if (result) {
3593
- const { szObject } = classNameToSzObject(expr.value, customMap);
3594
- baseObject = { ...baseObject, ...szObject };
3595
- allUnrecognized.push(...result.unrecognized);
3596
- converted++;
3597
- }
3598
- continue;
3599
- }
3600
- if (t.isConditionalExpression(expr)) {
3601
- const result = handleTernaryInner(expr, source, t, customMap);
3602
- if (!result) {
3603
- const exprSrc2 = safeSlice(source, expr.start, expr.end);
3604
- warnings.push(`Cannot migrate template ternary: ${exprSrc2}`);
3605
- return skip(allUnrecognized, warnings);
3606
- }
3607
- dynamicElements.push(result.exprStr);
3608
- allUnrecognized.push(...result.unrecognized);
3609
- converted++;
3610
- continue;
3611
- }
3612
- if (t.isLogicalExpression(expr) && expr.operator === "&&") {
3613
- const result = handleLogicalAndInner(expr, source, t, customMap);
3614
- if (!result) {
3615
- const exprSrc2 = safeSlice(source, expr.start, expr.end);
3616
- warnings.push(`Cannot migrate template logical expr: ${exprSrc2}`);
3617
- return skip(allUnrecognized, warnings);
3618
- }
3619
- dynamicElements.push(result.exprStr);
3620
- allUnrecognized.push(...result.unrecognized);
3621
- converted++;
3622
- continue;
3623
- }
3624
- const exprSrc = safeSlice(source, expr.start, expr.end);
3625
- warnings.push(`Cannot migrate template expression: ${exprSrc}`);
3626
- return skip(allUnrecognized, warnings);
3627
- }
3628
- const hasBase = Object.keys(baseObject).length > 0;
3629
- const hasDynamic = dynamicElements.length > 0;
3630
- if (!hasBase && !hasDynamic) {
3631
- return skip(allUnrecognized, warnings);
3632
- }
3633
- if (hasBase && !hasDynamic) {
3634
- return {
3635
- replacement: `sz=${generateSzExpression(baseObject)}`,
3636
- unrecognized: allUnrecognized,
3637
- warnings,
3638
- converted: converted + 1,
3639
- migrated: true
3640
- };
3641
- }
3642
- const parts = [];
3643
- if (hasBase) {
3644
- parts.push(generateSzObjectLiteral(baseObject));
3645
- }
3646
- parts.push(...dynamicElements);
3647
- return {
3648
- replacement: `sz={[${parts.join(", ")}]}`,
3649
- unrecognized: allUnrecognized,
3650
- warnings,
3651
- converted: converted + (hasBase ? 1 : 0),
3652
- migrated: true
3653
- };
3654
- }
3655
- function handleTernaryInner(node, source, t, customMap) {
3656
- if (!t.isStringLiteral(node.consequent) || !t.isStringLiteral(node.alternate)) {
3657
- return null;
3658
- }
3659
- const condSource = safeSlice(source, node.test.start, node.test.end);
3660
- const conValue = node.consequent.value.trim();
3661
- const altValue = node.alternate.value.trim();
3662
- if (altValue === "") {
3663
- if (!conValue) {
3664
- return null;
3665
- }
3666
- const conResult2 = migrateString(conValue, customMap);
3667
- if (!conResult2 || conResult2.unrecognized.length > 0) {
3668
- return null;
3669
- }
3670
- return {
3671
- exprStr: `${condSource} && ${conResult2.objectStr}`,
3672
- unrecognized: []
3673
- };
3674
- }
3675
- if (conValue === "") {
3676
- const altResult2 = migrateString(altValue, customMap);
3677
- if (!altResult2 || altResult2.unrecognized.length > 0) {
3678
- return null;
3679
- }
3680
- return {
3681
- exprStr: `!${wrapCondition(condSource)} && ${altResult2.objectStr}`,
3682
- unrecognized: []
3683
- };
3684
- }
3685
- const conResult = migrateString(conValue, customMap);
3686
- const altResult = migrateString(altValue, customMap);
3687
- if (!conResult || !altResult) {
3688
- return null;
3689
- }
3690
- const unrecognized = [...conResult.unrecognized, ...altResult.unrecognized];
3691
- if (unrecognized.length > 0) {
3692
- return null;
3693
- }
3694
- return {
3695
- exprStr: `${condSource} ? ${conResult.objectStr} : ${altResult.objectStr}`,
3696
- unrecognized: []
3697
- };
3698
- }
3699
- function handleLogicalAndInner(node, source, t, customMap) {
3700
- if (!t.isStringLiteral(node.right)) {
3701
- return null;
3702
- }
3703
- const result = migrateString(node.right.value, customMap);
3704
- if (!result || result.unrecognized.length > 0) {
3705
- return null;
3706
- }
3707
- const condSource = safeSlice(source, node.left.start, node.left.end);
3708
- return {
3709
- exprStr: `${condSource} && ${result.objectStr}`,
3710
- unrecognized: []
3711
- };
3712
- }
3713
- function migrateString(className, customMap) {
3714
- const trimmed = className.trim();
3715
- if (!trimmed) {
3716
- return null;
3717
- }
3718
- const { szObject, unrecognized } = classNameToSzObject(trimmed, customMap);
3719
- if (Object.keys(szObject).length === 0) {
3720
- return null;
3721
- }
3722
- return {
3723
- objectStr: generateSzObjectLiteral(szObject),
3724
- unrecognized
3725
- };
3726
- }
3727
- function skip(unrecognized, warnings) {
3728
- return {
3729
- replacement: "",
3730
- unrecognized,
3731
- warnings,
3732
- converted: 0,
3733
- migrated: false
3734
- };
3735
- }
3736
- function safeSlice(source, start, end) {
3737
- if (start === null || start === void 0 || end === null || end === void 0) {
3738
- return "<unknown>";
3739
- }
3740
- return source.slice(start, end);
3741
- }
3742
- function wrapCondition(cond) {
3743
- if (cond.includes(" ") || cond.includes("||") || cond.includes("&&")) {
3744
- return `(${cond})`;
3745
- }
3746
- return cond;
3747
- }
3748
- function isExpression(node, t) {
3749
- return t.isExpression(node);
3750
- }
3751
-
3752
- const VISITOR_KEYS = t.VISITOR_KEYS;
3753
- function injectTodoComment(unrecognized, parent, options, replacements) {
3754
- if (!options.injectTodos || unrecognized.length === 0) {
3755
- return;
3756
- }
3757
- if (!t.isJSXOpeningElement(parent) || parent.start === null || parent.start === void 0) {
3758
- return;
3759
- }
3760
- replacements.push({
3761
- start: parent.start,
3762
- end: parent.start,
3763
- text: `
3764
- {/* @sz-todo: ${unrecognized.join(", ")} */}
3765
- `
3766
- });
3767
- }
3768
- function walkAst(node, visitors, ancestors = []) {
3769
- if (t.isImportDeclaration(node)) {
3770
- visitors.ImportDeclaration?.(node);
3771
- } else if (t.isCallExpression(node)) {
3772
- visitors.CallExpression?.(node, ancestors);
3773
- } else if (t.isJSXAttribute(node)) {
3774
- visitors.JSXAttribute?.(node, ancestors[ancestors.length - 1] ?? null);
3775
- }
3776
- const keys = VISITOR_KEYS[node.type];
3777
- if (!keys) {
3778
- return;
3779
- }
3780
- ancestors.push(node);
3781
- for (const key of keys) {
3782
- const child = node[key];
3783
- if (Array.isArray(child)) {
3784
- for (const item of child) {
3785
- if (isAstNode(item)) {
3786
- walkAst(item, visitors, ancestors);
3787
- }
3788
- }
3789
- } else if (isAstNode(child)) {
3790
- walkAst(child, visitors, ancestors);
3791
- }
3792
- }
3793
- ancestors.pop();
3794
- }
3795
- function isAstNode(value) {
3796
- return Boolean(value && typeof value === "object" && "type" in value);
3797
- }
3798
- function isClassNameJsxAttribute(node) {
3799
- return t.isJSXAttribute(node) && t.isJSXIdentifier(node.name) && node.name.name === "className";
3800
- }
3801
- function transformSource(source, filePath, options = {}) {
3802
- const warnings = [];
3803
- let classNamesTransformed = 0;
3804
- let classNamesSkipped = 0;
3805
- let classNamesSkippedComponent = 0;
3806
- const classesUnrecognized = [];
3807
- const replacements = [];
3808
- const clsxImportNames = /* @__PURE__ */ new Set();
3809
- let clsxUsedOutsideClassName = false;
3810
- const clsxCallsitesMigrated = /* @__PURE__ */ new Set();
3811
- let hasCvaImport = false;
3812
- if (source.indexOf("className") === -1 && source.indexOf("cva") === -1) {
3813
- return {
3814
- code: source,
3815
- changed: false,
3816
- warnings: [],
3817
- stats: {
3818
- classNamesTransformed: 0,
3819
- classNamesSkipped: 0,
3820
- classNamesSkippedComponent: 0,
3821
- classesUnrecognized: []
3822
- },
3823
- potentiallyUnusedImports: []
3824
- };
3825
- }
3826
- let ast;
3827
- try {
3828
- ast = parse(source, {
3829
- sourceType: "module",
3830
- plugins: ["jsx", "typescript", "decorators-legacy"],
3831
- ranges: true
3832
- });
3833
- } catch (err) {
3834
- const msg = err instanceof Error ? err.message : String(err);
3835
- return {
3836
- code: source,
3837
- changed: false,
3838
- warnings: [`Parse error in ${filePath}: ${msg}`],
3839
- stats: {
3840
- classNamesTransformed: 0,
3841
- classNamesSkipped: 0,
3842
- classNamesSkippedComponent: 0,
3843
- classesUnrecognized: []
3844
- },
3845
- potentiallyUnusedImports: []
3846
- };
3847
- }
3848
- walkAst(ast, {
3849
- ImportDeclaration(node) {
3850
- const src = node.source.value;
3851
- const clsxPackages = ["clsx", "clsx/lite", "classnames", "tailwind-merge"];
3852
- const isClsxPkg = clsxPackages.some((p) => src === p || src.startsWith(`${p}/`));
3853
- const cvaPkgs = ["cva", "class-variance-authority"];
3854
- if (cvaPkgs.some((p) => src === p || src.startsWith(`${p}/`))) {
3855
- hasCvaImport = true;
3856
- }
3857
- for (const spec of node.specifiers) {
3858
- const localName = spec.local.name;
3859
- if (isClsxPkg || isClsxLikeName(localName)) {
3860
- clsxImportNames.add(localName);
3861
- }
3862
- }
3863
- },
3864
- CallExpression(node, ancestors) {
3865
- if (t.isIdentifier(node.callee) && clsxImportNames.has(node.callee.name)) {
3866
- const inClassName = ancestors.some(isClassNameJsxAttribute);
3867
- if (!inClassName) {
3868
- clsxUsedOutsideClassName = true;
3869
- }
3870
- }
3871
- },
3872
- JSXAttribute(node, parent) {
3873
- const attrName = node.name;
3874
- if (!t.isJSXIdentifier(attrName) || attrName.name !== "className") {
3875
- return;
3876
- }
3877
- if (t.isJSXOpeningElement(parent)) {
3878
- const elementName = parent.name;
3879
- const isCapitalized = t.isJSXIdentifier(elementName) && /^[A-Z]/.test(elementName.name) || t.isJSXMemberExpression(elementName);
3880
- if (isCapitalized) {
3881
- classNamesSkippedComponent++;
3882
- return;
3883
- }
3884
- }
3885
- if (t.isJSXOpeningElement(parent)) {
3886
- const hasSz = parent.attributes.some(
3887
- (attr) => t.isJSXAttribute(attr) && t.isJSXIdentifier(attr.name) && attr.name.name === "sz"
3888
- );
3889
- if (hasSz) {
3890
- classNamesSkipped++;
3891
- return;
3892
- }
3893
- }
3894
- const value = node.value;
3895
- const attrStart = node.start;
3896
- const attrEnd = node.end;
3897
- if (attrStart === null || attrStart === void 0 || attrEnd === null || attrEnd === void 0) {
3898
- return;
3899
- }
3900
- if (t.isStringLiteral(value)) {
3901
- const result = processStaticString(value.value, options.customMap);
3902
- if (result) {
3903
- replacements.push({ start: attrStart, end: attrEnd, text: result.replacement });
3904
- classNamesTransformed++;
3905
- classesUnrecognized.push(...result.unrecognized);
3906
- injectTodoComment(result.unrecognized, parent, options, replacements);
3907
- } else {
3908
- classNamesSkipped++;
3909
- }
3910
- return;
3911
- }
3912
- if (t.isJSXExpressionContainer(value)) {
3913
- const expr = value.expression;
3914
- if (t.isStringLiteral(expr)) {
3915
- const result = processStaticString(expr.value, options.customMap);
3916
- if (result) {
3917
- replacements.push({
3918
- start: attrStart,
3919
- end: attrEnd,
3920
- text: result.replacement
3921
- });
3922
- classNamesTransformed++;
3923
- classesUnrecognized.push(...result.unrecognized);
3924
- injectTodoComment(result.unrecognized, parent, options, replacements);
3925
- } else {
3926
- classNamesSkipped++;
3927
- }
3928
- return;
3929
- }
3930
- if (t.isTemplateLiteral(expr)) {
3931
- const result = handleTemplateLiteral(expr, source, t, options.customMap);
3932
- if (result.migrated) {
3933
- replacements.push({
3934
- start: attrStart,
3935
- end: attrEnd,
3936
- text: result.replacement
3937
- });
3938
- classNamesTransformed += result.converted;
3939
- classesUnrecognized.push(...result.unrecognized);
3940
- } else {
3941
- classNamesSkipped++;
3942
- warnings.push(...result.warnings.map((w) => `[${filePath}] ${w}`));
3943
- classesUnrecognized.push(...result.unrecognized);
3944
- }
3945
- injectTodoComment(result.unrecognized, parent, options, replacements);
3946
- return;
3947
- }
3948
- if (t.isCallExpression(expr) && t.isIdentifier(expr.callee) && isClsxLikeName(expr.callee.name)) {
3949
- const result = handleClsxCall(expr, source, t, options.customMap);
3950
- if (result.migrated) {
3951
- replacements.push({
3952
- start: attrStart,
3953
- end: attrEnd,
3954
- text: result.replacement
3955
- });
3956
- classNamesTransformed += result.converted;
3957
- classesUnrecognized.push(...result.unrecognized);
3958
- if (expr.start !== null && expr.start !== void 0) {
3959
- clsxCallsitesMigrated.add(expr.start);
3960
- }
3961
- } else {
3962
- classNamesSkipped++;
3963
- warnings.push(...result.warnings.map((w) => `[${filePath}] ${w}`));
3964
- classesUnrecognized.push(...result.unrecognized);
3965
- }
3966
- injectTodoComment(result.unrecognized, parent, options, replacements);
3967
- return;
3968
- }
3969
- if (t.isConditionalExpression(expr)) {
3970
- const result = handleTernary(expr, source, t, options.customMap);
3971
- if (result.migrated) {
3972
- replacements.push({
3973
- start: attrStart,
3974
- end: attrEnd,
3975
- text: result.replacement
3976
- });
3977
- classNamesTransformed += result.converted;
3978
- classesUnrecognized.push(...result.unrecognized);
3979
- } else {
3980
- classNamesSkipped++;
3981
- warnings.push(...result.warnings.map((w) => `[${filePath}] ${w}`));
3982
- classesUnrecognized.push(...result.unrecognized);
3983
- }
3984
- injectTodoComment(result.unrecognized, parent, options, replacements);
3985
- return;
3986
- }
3987
- if (t.isLogicalExpression(expr) && expr.operator === "&&") {
3988
- const result = handleLogicalAnd(expr, source, t, options.customMap);
3989
- if (result.migrated) {
3990
- replacements.push({
3991
- start: attrStart,
3992
- end: attrEnd,
3993
- text: result.replacement
3994
- });
3995
- classNamesTransformed += result.converted;
3996
- classesUnrecognized.push(...result.unrecognized);
3997
- } else {
3998
- classNamesSkipped++;
3999
- warnings.push(...result.warnings.map((w) => `[${filePath}] ${w}`));
4000
- classesUnrecognized.push(...result.unrecognized);
4001
- }
4002
- injectTodoComment(result.unrecognized, parent, options, replacements);
4003
- return;
4004
- }
4005
- classNamesSkipped++;
4006
- return;
4007
- }
4008
- classNamesSkipped++;
4009
- }
4010
- });
4011
- if (hasCvaImport) {
4012
- warnings.push(
4013
- `[${filePath}] File uses cva() \u2014 consider migrating to szv() from @csszyx/runtime for type-safe variant-based styling.`
4014
- );
4015
- }
4016
- let output = source;
4017
- const sorted = replacements.sort((a, b) => b.start - a.start);
4018
- for (const r of sorted) {
4019
- output = output.slice(0, r.start) + r.text + output.slice(r.end);
4020
- }
4021
- const potentiallyUnusedImports = [];
4022
- if (clsxImportNames.size > 0 && !clsxUsedOutsideClassName && replacements.length > 0) {
4023
- for (const name of clsxImportNames) {
4024
- const callPattern = new RegExp(`\\b${name}\\s*\\(`, "g");
4025
- if (!callPattern.test(output)) {
4026
- potentiallyUnusedImports.push(name);
4027
- }
4028
- }
4029
- }
4030
- return {
4031
- code: output,
4032
- changed: replacements.length > 0,
4033
- warnings,
4034
- stats: {
4035
- classNamesTransformed,
4036
- classNamesSkipped,
4037
- classNamesSkippedComponent,
4038
- classesUnrecognized
4039
- },
4040
- potentiallyUnusedImports
4041
- };
4042
- }
4043
- function processStaticString(classNameStr, customMap) {
4044
- const trimmed = classNameStr.trim();
4045
- if (!trimmed) {
4046
- return null;
4047
- }
4048
- const { szObject, unrecognized, keepInClassName } = classNameToSzObject(trimmed, customMap);
4049
- if (Object.keys(szObject).length === 0) {
4050
- return null;
4051
- }
4052
- const szExpr = generateSzExpression(szObject);
4053
- const remainingClassName = [...keepInClassName, ...unrecognized];
4054
- if (remainingClassName.length > 0) {
4055
- return {
4056
- replacement: `className="${remainingClassName.join(" ")}" sz=${szExpr}`,
4057
- unrecognized
4058
- };
4059
- }
4060
- return {
4061
- replacement: `sz=${szExpr}`,
4062
- unrecognized: []
4063
- };
4064
- }
4065
- const FOUC_CSS = `<style>
4066
- /* csszyx: hide [sz] elements until runtime processes them */
4067
- [sz] { visibility: hidden; }
4068
- body.sz-ready [sz] { visibility: visible; }
4069
- </style>`;
4070
- function transformHtmlSourceSimple(source, filePath, options = {}) {
4071
- const {
4072
- braces = false,
4073
- injectFouc = true,
4074
- injectRuntime = false,
4075
- cdnUrl = "https://cdn.csszyx.com/runtime.js",
4076
- localPath = "csszyx-runtime.js"
4077
- } = options;
4078
- const warnings = [];
4079
- let classNamesTransformed = 0;
4080
- let classNamesSkipped = 0;
4081
- const classNamesSkippedComponent = 0;
4082
- const classesUnrecognized = [];
4083
- let changed = false;
4084
- let output = source.replace(/\bclass="([^"]*)"/g, (match, classStr) => {
4085
- return processClassAttr(match, classStr, '"');
4086
- });
4087
- output = output.replace(/\bclass='([^']*)'/g, (match, classStr) => {
4088
- return processClassAttr(match, classStr, "'");
4089
- });
4090
- if (injectFouc && output.includes("</head>") && !output.includes("csszyx: hide [sz]")) {
4091
- output = output.replace("</head>", `${FOUC_CSS}
4092
- </head>`);
4093
- changed = true;
4094
- }
4095
- if (injectRuntime && output.includes("</body>")) {
4096
- const scriptSrc = injectRuntime === "cdn" ? cdnUrl : localPath;
4097
- const scriptTag = `<script src="${scriptSrc}"><\/script>`;
4098
- if (!output.includes(scriptSrc)) {
4099
- output = output.replace("</body>", `${scriptTag}
4100
- </body>`);
4101
- changed = true;
4102
- }
4103
- }
4104
- function processClassAttr(match, classStr, quote) {
4105
- const trimmed = classStr.trim();
4106
- if (!trimmed) {
4107
- classNamesSkipped++;
4108
- return match;
4109
- }
4110
- const { szObject, unrecognized } = classNameToSzObject(trimmed);
4111
- if (Object.keys(szObject).length === 0) {
4112
- classNamesSkipped++;
4113
- classesUnrecognized.push(...unrecognized);
4114
- return match;
4115
- }
4116
- const szVal = generateSzHtmlValue(szObject, braces);
4117
- changed = true;
4118
- classNamesTransformed++;
4119
- if (unrecognized.length > 0) {
4120
- classesUnrecognized.push(...unrecognized);
4121
- return `class=${quote}${unrecognized.join(" ")}${quote} sz="${szVal}"`;
4122
- }
4123
- return `sz="${szVal}"`;
4124
- }
4125
- return {
4126
- code: output,
4127
- changed,
4128
- warnings,
4129
- stats: {
4130
- classNamesTransformed,
4131
- classNamesSkipped,
4132
- classNamesSkippedComponent,
4133
- classesUnrecognized
4134
- },
4135
- potentiallyUnusedImports: []
4136
- };
4137
- }
4138
-
4139
- function createLogFile(cwd) {
4140
- const now = /* @__PURE__ */ new Date();
4141
- const ts = now.toISOString().slice(0, 19).replace("T", "_").replace(/:/g, "-");
4142
- const logDir = path__default.join(cwd, ".csszyx", "logs");
4143
- fs$1.mkdirSync(logDir, { recursive: true });
4144
- const filePath = path__default.join(logDir, `migrate-${ts}.log`);
4145
- const lines = [`csszyx migrate \u2014 ${now.toISOString()}`, ""];
4146
- return {
4147
- filePath,
4148
- writeLine: (line) => lines.push(line),
4149
- flush: () => fs$1.writeFileSync(filePath, `${lines.join("\n")}
4150
- `, "utf-8")
4151
- };
4152
- }
4153
- function isGitignored(cwd, pattern) {
4154
- try {
4155
- const content = fs$1.readFileSync(path__default.join(cwd, ".gitignore"), "utf-8");
4156
- return content.split("\n").some((l) => {
4157
- const t = l.trim();
4158
- return t === pattern || t === `${pattern}/` || t === `/${pattern}`;
4159
- });
4160
- } catch {
4161
- return false;
4162
- }
4163
- }
4164
- async function askYesNo(question) {
4165
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
4166
- return new Promise((resolve) => {
4167
- rl.question(question, (answer) => {
4168
- rl.close();
4169
- resolve(answer.trim().toLowerCase() === "y");
4170
- });
4171
- });
4172
- }
4173
- async function migrate(options = {}) {
4174
- const cwd = options.cwd || process.cwd();
4175
- let dryRun = options.dryRun || false;
4176
- const ignorePatterns = options.ignore || [];
4177
- const audit = options.audit || false;
4178
- const resolveTodosPath = options.resolveTodos;
4179
- let customMap, files;
4180
- if (audit) {
4181
- dryRun = true;
4182
- }
4183
- let injectTodos = options.injectTodos || false;
4184
- if (resolveTodosPath && !injectTodos) {
4185
- injectTodos = true;
4186
- }
4187
- printHeader("csszyx Migration Tool");
4188
- if (process.stdout.isTTY && !injectTodos && !audit && !resolveTodosPath) {
4189
- const answer = await askYesNo(
4190
- "Add {/* @sz-todo */} comments above elements with unrecognized classes? [y/N] "
4191
- );
4192
- if (answer) {
4193
- injectTodos = true;
4194
- }
4195
- }
4196
- if (resolveTodosPath) {
4197
- try {
4198
- const absolutePath = path__default.resolve(cwd, resolveTodosPath);
4199
- const content = fs$1.readFileSync(absolutePath, "utf-8");
4200
- customMap = JSON.parse(content);
4201
- printInfo(`Loaded resolution map from ${resolveTodosPath}`);
4202
- } catch {
4203
- printWarn(
4204
- `Could not load resolve map from ${resolveTodosPath}. Ensure the file exists and is valid JSON.`
4205
- );
4206
- return;
4207
- }
4208
- }
4209
- if (audit) {
4210
- printInfo("Audit mode \u2014 scanning for unrecognized classes to generate a mapping file...");
4211
- } else if (dryRun) {
4212
- printInfo("Dry run mode \u2014 no files will be modified");
4213
- }
4214
- const log = createLogFile(cwd);
4215
- log.writeLine(
4216
- `Mode: ${audit ? "audit" : dryRun ? "dry-run" : "migrate"}${resolveTodosPath ? ` (resolve-todos: ${resolveTodosPath})` : ""}`
4217
- );
4218
- log.writeLine(`injectTodos: ${injectTodos}`);
4219
- log.writeLine("");
4220
- if (!isGitignored(cwd, ".csszyx")) {
4221
- printWarn(
4222
- "Tip: add .csszyx/ to your .gitignore to exclude migration logs from version control."
4223
- );
4224
- }
4225
- const patterns = options.pattern ? [options.pattern] : ["**/*.{jsx,tsx,html}"];
4226
- const ignore = [
4227
- "**/node_modules/**",
4228
- "**/dist/**",
4229
- "**/build/**",
4230
- "**/.next/**",
4231
- "**/.nuxt/**",
4232
- ...ignorePatterns
4233
- ];
4234
- const s = spinner.start("Scanning for files...");
4235
- try {
4236
- files = await fg(patterns, { cwd, ignore, absolute: true });
4237
- } catch (err) {
4238
- s.fail("File scan failed");
4239
- printWarn(`Could not scan files: ${err instanceof Error ? err.message : String(err)}`);
4240
- log.flush();
4241
- return;
4242
- }
4243
- s.succeed(`Found ${files.length} files`);
4244
- if (files.length === 0) {
4245
- printWarn(
4246
- options.pattern ? `No files found matching pattern: ${options.pattern}` : "No JSX/TSX/HTML files found"
4247
- );
4248
- log.writeLine("No files found.");
4249
- log.flush();
4250
- return;
4251
- }
4252
- let totalTransformed = 0;
4253
- let totalSkipped = 0;
4254
- let totalSkippedComponent = 0;
4255
- let totalFiles = 0;
4256
- const allUnrecognized = [];
4257
- const allWarnings = [];
4258
- const unusedImportFiles = [];
4259
- const s2 = spinner.start("Migrating...");
4260
- for (const filePath of files) {
4261
- const source = fs$1.readFileSync(filePath, "utf-8");
4262
- const isHtml = filePath.endsWith(".html");
4263
- const hasRelevantAttr = isHtml ? source.includes("class=") : source.includes("className=");
4264
- if (!hasRelevantAttr) {
4265
- continue;
4266
- }
4267
- let processSource = source;
4268
- if (resolveTodosPath && !isHtml) {
4269
- processSource = processSource.replace(
4270
- /\{\/\*\s*@sz-todo:\s*(\S(?:.*\S)?)\s*\*\/\}\n?/g,
4271
- ""
4272
- );
4273
- }
4274
- const result = isHtml ? transformHtmlSourceSimple(processSource, filePath, {
4275
- braces: options.braces,
4276
- injectFouc: options.injectFouc,
4277
- injectRuntime: options.injectRuntime,
4278
- cdnUrl: options.cdnUrl,
4279
- localPath: options.localPath
4280
- }) : transformSource(processSource, filePath, { injectTodos, customMap });
4281
- allWarnings.push(...result.warnings);
4282
- if (result.changed) {
4283
- totalFiles++;
4284
- totalTransformed += result.stats.classNamesTransformed;
4285
- totalSkipped += result.stats.classNamesSkipped;
4286
- totalSkippedComponent += result.stats.classNamesSkippedComponent;
4287
- allUnrecognized.push(...result.stats.classesUnrecognized);
4288
- if (result.potentiallyUnusedImports.length > 0) {
4289
- const rel2 = path__default.relative(cwd, filePath);
4290
- unusedImportFiles.push({ file: rel2, imports: result.potentiallyUnusedImports });
4291
- }
4292
- if (!dryRun) {
4293
- try {
4294
- fs$1.writeFileSync(filePath, result.code, "utf-8");
4295
- } catch (err) {
4296
- const rel2 = path__default.relative(cwd, filePath);
4297
- printWarn(
4298
- `Could not write ${rel2}: ${err instanceof Error ? err.message : String(err)}`
4299
- );
4300
- log.writeLine(` Write error: ${rel2}`);
4301
- continue;
4302
- }
4303
- }
4304
- const rel = path__default.relative(cwd, filePath);
4305
- if (dryRun) {
4306
- printInfo(` ${rel}: ${result.stats.classNamesTransformed} className(s) \u2192 sz`);
4307
- log.writeLine(` ${rel}: ${result.stats.classNamesTransformed} className(s) \u2192 sz`);
4308
- } else {
4309
- log.writeLine(` ${rel}: ${result.stats.classNamesTransformed} className(s) \u2192 sz`);
4310
- }
4311
- }
4312
- }
4313
- s2.succeed("Migration complete");
4314
- console.info();
4315
- printSuccess(`Files modified: ${totalFiles}`);
4316
- printSuccess(`classNames converted: ${totalTransformed}`);
4317
- log.writeLine(`Files modified: ${totalFiles}`);
4318
- log.writeLine(`classNames converted: ${totalTransformed}`);
4319
- if (totalSkipped > 0) {
4320
- printWarn(`classNames skipped (dynamic): ${totalSkipped}`);
4321
- log.writeLine(`classNames skipped (dynamic): ${totalSkipped}`);
4322
- }
4323
- if (totalSkippedComponent > 0) {
4324
- printWarn(`classNames kept on components (no sz support): ${totalSkippedComponent}`);
4325
- log.writeLine(`classNames kept on components (no sz support): ${totalSkippedComponent}`);
4326
- }
4327
- if (allUnrecognized.length > 0) {
4328
- const unique = [...new Set(allUnrecognized)];
4329
- printWarn(
4330
- `Unrecognized classes (${unique.length}): ${unique.slice(0, 10).join(", ")}${unique.length > 10 ? "..." : ""}`
4331
- );
4332
- log.writeLine(`Unrecognized classes (${unique.length}): ${unique.join(", ")}`);
4333
- }
4334
- if (allWarnings.length > 0) {
4335
- console.info();
4336
- for (const w of allWarnings.slice(0, 5)) {
4337
- printWarn(w);
4338
- }
4339
- if (allWarnings.length > 5) {
4340
- printWarn(`... and ${allWarnings.length - 5} more warnings`);
4341
- }
4342
- log.writeLine("");
4343
- log.writeLine("Warnings:");
4344
- for (const w of allWarnings) {
4345
- log.writeLine(` ${w}`);
4346
- }
4347
- }
4348
- if (audit) {
4349
- const todoPath = path__default.join(cwd, ".csszyx-todo.json");
4350
- const unique = [...new Set(allUnrecognized)];
4351
- console.info();
4352
- if (unique.length === 0) {
4353
- printSuccess(
4354
- "Audit complete. 100% of your classes are perfectly recognized by csszyx!"
4355
- );
4356
- log.writeLine("Audit: 100% recognized.");
4357
- } else {
4358
- const todoObj = {};
4359
- for (const u of unique) {
4360
- todoObj[u] = "sz:todo";
4361
- }
4362
- try {
4363
- fs$1.writeFileSync(todoPath, JSON.stringify(todoObj, null, 2));
4364
- } catch (err) {
4365
- printWarn(
4366
- `Could not write ${path__default.relative(cwd, todoPath)}: ${err instanceof Error ? err.message : String(err)}`
4367
- );
4368
- log.flush();
4369
- return;
4370
- }
4371
- printSuccess(
4372
- `Audit complete. Exported ${unique.length} unrecognized classes to ${path__default.relative(cwd, todoPath)}.`
4373
- );
4374
- printInfo(
4375
- "Edit this file to map custom classes, then run: npx @csszyx/cli migrate --resolve-todos .csszyx-todo.json"
4376
- );
4377
- log.writeLine(
4378
- `Audit: ${unique.length} unrecognized classes written to ${path__default.relative(cwd, todoPath)}`
4379
- );
4380
- }
4381
- }
4382
- if (resolveTodosPath) {
4383
- const unique = [...new Set(allUnrecognized)];
4384
- if (unique.length > 0) {
4385
- console.info();
4386
- printWarn(
4387
- `Still unresolved after this pass (${unique.length}): ${unique.slice(0, 10).join(", ")}${unique.length > 10 ? "..." : ""}`
4388
- );
4389
- printInfo("Re-run --audit to generate a fresh snapshot when ready.");
4390
- log.writeLine(`Still unresolved (${unique.length}): ${unique.join(", ")}`);
4391
- }
4392
- }
4393
- if (unusedImportFiles.length > 0) {
4394
- console.info();
4395
- printWarn("Potentially unused imports (run ESLint to clean up):");
4396
- for (const { file, imports } of unusedImportFiles) {
4397
- printInfo(` ${file}: ${imports.map((i) => `import { ${i} }`).join(", ")}`);
4398
- log.writeLine(` Unused import in ${file}: ${imports.join(", ")}`);
4399
- }
4400
- }
4401
- try {
4402
- log.flush();
4403
- printInfo(`Migration log saved to ${path__default.relative(cwd, log.filePath)}`);
4404
- } catch {
4405
- }
4406
- }
4407
-
4408
- const DEFAULT_NEXT_SOURCE_PATTERN = "{app,pages,src}/**/*.{ts,tsx,js,jsx,mjs,cjs}";
4409
- const DEFAULT_NEXT_SOURCE_IGNORE = [
4410
- "node_modules/**",
4411
- ".git/**",
4412
- ".next/**",
4413
- ".next-turbo-*/**",
4414
- ".csszyx/**",
4415
- "dist/**",
4416
- "build/**"
4417
- ];
4418
-
4419
- async function nextPrebuild(options = {}) {
4420
- const cwd = path__default.resolve(options.cwd ?? process.cwd());
4421
- const root = path__default.resolve(options.root ?? cwd);
4422
- const pattern = options.pattern ?? DEFAULT_NEXT_SOURCE_PATTERN;
4423
- try {
4424
- const mode = normalizeMode(options.mode);
4425
- const parserMode = normalizeParserMode$1(options.parserMode);
4426
- const matches = await fg(pattern, {
4427
- cwd: root,
4428
- absolute: true,
4429
- ignore: [...DEFAULT_NEXT_SOURCE_IGNORE, ...options.extraIgnore ?? []],
4430
- dot: false,
4431
- onlyFiles: true
4432
- });
4433
- if (matches.length === 0) {
4434
- const message = `No source files matched pattern \`${pattern}\` under ${root}.`;
4435
- if (options.json) {
4436
- console.log(
4437
- JSON.stringify(
4438
- { ok: false, reason: "no-files-matched", root, pattern, mode },
4439
- null,
4440
- 2
4441
- )
4442
- );
4443
- } else {
4444
- console.error(`${colors.error(icons.error)} ${message}`);
4445
- }
4446
- return 1;
4447
- }
4448
- const result = runNextPrebuild({
4449
- files: matches,
4450
- explicitRoot: root,
4451
- cwd,
4452
- mode,
4453
- parserMode,
4454
- safelistOutputFile: options.outputFile,
4455
- cacheDir: options.cacheDir,
4456
- config: { mangleVars: false }
4457
- // Versions intentionally omitted: runNextPrebuild's package.json
4458
- // fallback reads the real installed @csszyx/unplugin and
4459
- // @csszyx/compiler versions so the manifest's generation identity
4460
- // tracks the engine that actually runs the transform.
4461
- });
4462
- if (options.json) {
4463
- console.log(
4464
- JSON.stringify(
4465
- {
4466
- ok: true,
4467
- root,
4468
- mode,
4469
- scannedCount: result.scannedCount,
4470
- transformedCount: result.transformedCount,
4471
- skippedMissingCount: result.skippedMissingCount,
4472
- sourceCount: result.sourceCount,
4473
- classCount: result.classCount,
4474
- manifestPath: result.manifestPath,
4475
- safelistOutputPath: result.safelistOutputPath
4476
- },
4477
- null,
4478
- 2
4479
- )
4480
- );
4481
- } else {
4482
- console.log(`${colors.success(icons.success)} csszyx next prebuild done`);
4483
- console.log(` root: ${root}`);
4484
- console.log(` mode: ${mode}`);
4485
- console.log(` scanned: ${result.scannedCount}`);
4486
- console.log(` transformed: ${result.transformedCount}`);
4487
- console.log(` skipped: ${result.skippedMissingCount}`);
4488
- console.log(` sources: ${result.sourceCount}`);
4489
- console.log(` classes: ${result.classCount}`);
4490
- console.log(` safelist: ${result.safelistOutputPath}`);
4491
- console.log(` manifest: ${result.manifestPath}`);
4492
- }
4493
- return 0;
4494
- } catch (error) {
4495
- const message = error instanceof Error ? error.message : String(error);
4496
- if (options.json) {
4497
- console.log(JSON.stringify({ ok: false, reason: message }, null, 2));
4498
- } else {
4499
- console.error(`${colors.error(icons.error)} ${message}`);
4500
- }
4501
- return 1;
4502
- }
4503
- }
4504
- function normalizeMode(mode) {
4505
- if (mode === void 0) {
4506
- return "production";
4507
- }
4508
- if (mode === "development" || mode === "production") {
4509
- return mode;
4510
- }
4511
- throw new Error(`Invalid --mode "${mode}". Expected "development" or "production".`);
4512
- }
4513
- function normalizeParserMode$1(parserMode) {
4514
- if (parserMode === void 0) {
4515
- return void 0;
4516
- }
4517
- if (parserMode === "rust" || parserMode === "oxc" || parserMode === "babel") {
4518
- return parserMode;
4519
- }
4520
- throw new Error(`Invalid --parser-mode "${parserMode}". Expected "rust", "oxc", or "babel".`);
4521
- }
4522
-
4523
- const SOURCE_EXTENSION = /\.[cm]?[jt]sx?$/i;
4524
- async function startNextWatch(options = {}, dependencies = {}) {
4525
- const cwd = path.resolve(options.cwd ?? process.cwd());
4526
- const root = path.resolve(options.root ?? cwd);
4527
- const pattern = options.pattern ?? DEFAULT_NEXT_SOURCE_PATTERN;
4528
- const ignore = [...DEFAULT_NEXT_SOURCE_IGNORE, ...options.extraIgnore ?? []];
4529
- const parserMode = normalizeParserMode(options.parserMode);
4530
- const debounceMs = normalizeDebounceMs(options.debounceMs);
4531
- const files = await fg(pattern, {
4532
- cwd: root,
4533
- absolute: true,
4534
- ignore,
4535
- dot: false,
4536
- onlyFiles: true
4537
- });
4538
- if (files.length === 0) {
4539
- throw new Error(`No source files matched pattern \`${pattern}\` under ${root}.`);
4540
- }
4541
- const prebuild = runNextPrebuild({
4542
- files,
4543
- explicitRoot: root,
4544
- cwd,
4545
- mode: "development",
4546
- parserMode,
4547
- safelistOutputFile: options.outputFile,
4548
- cacheDir: options.cacheDir,
4549
- config: { mangleVars: false }
4550
- });
4551
- let resolveFailure = () => {
4552
- };
4553
- let failed = false;
4554
- const failure = new Promise((resolve) => {
4555
- resolveFailure = resolve;
4556
- });
4557
- const reportFailure = (error) => {
4558
- if (failed) {
4559
- return;
4560
- }
4561
- failed = true;
4562
- resolveFailure(error instanceof Error ? error : new Error(String(error)));
4563
- };
4564
- const controller = new NextSafelistWatcher({
4565
- context: prebuild.context,
4566
- debounceMs,
4567
- onError: reportFailure
4568
- });
4569
- const watchFactory = dependencies.watch ?? watch;
4570
- const fsWatcher = watchFactory(root, {
4571
- ignoreInitial: true,
4572
- persistent: true,
4573
- atomic: true,
4574
- awaitWriteFinish: {
4575
- stabilityThreshold: 25,
4576
- pollInterval: 10
4577
- },
4578
- ignored: createIgnoredMatcher(root, prebuild.context.safelist.shardsDir, ignore)
4579
- });
4580
- fsWatcher.on("all", (event, filePath) => {
4581
- const absolutePath = path.resolve(filePath);
4582
- if (event === "add" || event === "change" || event === "unlink") {
4583
- if (controller.notify(event, absolutePath) || event !== "unlink" || !SOURCE_EXTENSION.test(absolutePath)) {
4584
- return;
4585
- }
4586
- controller.notifySourceRemoval(absolutePath);
4587
- }
4588
- });
4589
- fsWatcher.on("error", reportFailure);
4590
- try {
4591
- await waitForWatcherReady(fsWatcher);
4592
- controller.start();
4593
- } catch (error) {
4594
- await fsWatcher.close();
4595
- controller.close();
4596
- throw error;
4597
- }
4598
- let closed = false;
4599
- return {
4600
- root,
4601
- sourcePattern: pattern,
4602
- safelistOutputPath: prebuild.safelistOutputPath,
4603
- manifestPath: prebuild.manifestPath,
4604
- failure,
4605
- close: async () => {
4606
- if (closed) {
4607
- return;
4608
- }
4609
- closed = true;
4610
- await fsWatcher.close();
4611
- controller.close();
4612
- }
4613
- };
4614
- }
4615
- async function nextWatch(options = {}) {
4616
- let session;
4617
- let exitCode = 0;
4618
- try {
4619
- session = await startNextWatch(options);
4620
- if (!options.silent) {
4621
- console.log(`${colors.success(icons.success)} csszyx next watch ready`);
4622
- console.log(` root: ${session.root}`);
4623
- console.log(` pattern: ${session.sourcePattern}`);
4624
- console.log(` safelist: ${session.safelistOutputPath}`);
4625
- console.log(` manifest: ${session.manifestPath}`);
4626
- }
4627
- const outcome = await waitForShutdown(session.failure);
4628
- if (outcome) {
4629
- console.error(`${colors.error(icons.error)} ${outcome.message}`);
4630
- exitCode = 1;
4631
- }
4632
- } catch (error) {
4633
- const message = error instanceof Error ? error.message : String(error);
4634
- console.error(`${colors.error(icons.error)} ${message}`);
4635
- exitCode = 1;
4636
- }
4637
- try {
4638
- await session?.close();
4639
- } catch (error) {
4640
- const message = error instanceof Error ? error.message : String(error);
4641
- console.error(`${colors.error(icons.error)} Failed to close Next watcher: ${message}`);
4642
- exitCode = 1;
4643
- }
4644
- return exitCode;
4645
- }
4646
- function waitForWatcherReady(watcher) {
4647
- return new Promise((resolve, reject) => {
4648
- const onReady = () => {
4649
- watcher.off("error", onStartupError);
4650
- resolve();
4651
- };
4652
- const onStartupError = (error) => {
4653
- watcher.off("ready", onReady);
4654
- reject(error);
4655
- };
4656
- watcher.once("ready", onReady);
4657
- watcher.once("error", onStartupError);
4658
- });
4659
- }
4660
- function waitForShutdown(failure) {
4661
- return new Promise((resolve) => {
4662
- const cleanup = () => {
4663
- process.off("SIGINT", onSignal);
4664
- process.off("SIGTERM", onSignal);
4665
- };
4666
- const onSignal = () => {
4667
- cleanup();
4668
- resolve(void 0);
4669
- };
4670
- process.once("SIGINT", onSignal);
4671
- process.once("SIGTERM", onSignal);
4672
- failure.then((error) => {
4673
- cleanup();
4674
- resolve(error);
4675
- });
4676
- });
4677
- }
4678
- function createIgnoredMatcher(root, shardsDir, ignore) {
4679
- const normalizedShardsDir = path.resolve(shardsDir);
4680
- const matchers = ignore.flatMap((pattern) => {
4681
- const normalized = pattern.replace(/\\/g, "/");
4682
- const variants = normalized.endsWith("/**") ? [normalized, normalized.slice(0, -3)] : [normalized];
4683
- return variants.map((variant) => new Minimatch(variant, { dot: true }));
4684
- });
4685
- return (candidate) => {
4686
- const absolute = path.resolve(candidate);
4687
- const relativeToShards = path.relative(absolute, normalizedShardsDir);
4688
- if (absolute === normalizedShardsDir || absolute.startsWith(`${normalizedShardsDir}${path.sep}`) || relativeToShards !== ".." && !relativeToShards.startsWith(`..${path.sep}`) && !path.isAbsolute(relativeToShards)) {
4689
- return false;
4690
- }
4691
- const relative = path.relative(root, absolute).replace(/\\/g, "/");
4692
- if (!relative || relative.startsWith("../") || path.isAbsolute(relative)) {
4693
- return false;
4694
- }
4695
- return matchers.some((matcher) => matcher.match(relative));
4696
- };
4697
- }
4698
- function normalizeParserMode(parserMode) {
4699
- if (parserMode === void 0) {
4700
- return void 0;
4701
- }
4702
- if (parserMode === "rust" || parserMode === "oxc" || parserMode === "babel") {
4703
- return parserMode;
4704
- }
4705
- throw new Error(`Invalid --parser-mode "${parserMode}". Expected "rust", "oxc", or "babel".`);
4706
- }
4707
- function normalizeDebounceMs(debounceMs) {
4708
- if (debounceMs === void 0) {
4709
- return void 0;
4710
- }
4711
- const parsed = typeof debounceMs === "number" ? debounceMs : Number(debounceMs);
4712
- if (!Number.isInteger(parsed) || parsed < 0 || parsed > 6e4) {
4713
- throw new Error("Invalid --debounce-ms. Expected an integer between 0 and 60000.");
4714
- }
4715
- return parsed;
4716
- }
4717
-
4718
- const cli = cac("csszyx");
4719
- normalizeNextCommandAlias(process.argv);
4720
- function readCliVersion() {
4721
- try {
4722
- const manifest = JSON.parse(
4723
- readFileSync(new URL("../package.json", import.meta.url), "utf8")
4724
- );
4725
- return typeof manifest.version === "string" ? manifest.version : "0.0.0";
4726
- } catch {
4727
- return "0.0.0";
4728
- }
4729
- }
4730
- const VERSION = readCliVersion();
4731
- async function runNextPrebuildCommand(pattern, options) {
4732
- const code = await nextPrebuild({
4733
- cwd: options.cwd,
4734
- root: options.root,
4735
- mode: options.mode,
4736
- parserMode: options.parserMode,
4737
- outputFile: options.outputFile,
4738
- cacheDir: options.cacheDir,
4739
- pattern,
4740
- extraIgnore: options.ignore ? String(options.ignore).split(",") : void 0,
4741
- json: options.json
4742
- });
4743
- if (code !== 0) {
4744
- process.exit(code);
4745
- }
4746
- }
4747
- async function runNextWatchCommand(pattern, options) {
4748
- const code = await nextWatch({
4749
- cwd: options.cwd,
4750
- root: options.root,
4751
- parserMode: options.parserMode,
4752
- outputFile: options.outputFile,
4753
- cacheDir: options.cacheDir,
4754
- pattern,
4755
- extraIgnore: options.ignore ? String(options.ignore).split(",") : void 0,
4756
- debounceMs: options.debounceMs
4757
- });
4758
- process.exitCode = code;
4759
- }
4760
- cli.command("init", "Setup csszyx in your project").option("--framework <name>", "Specify framework").option("--yes", "Skip prompts (use defaults)").option("--cwd <dir>", "Current working directory").action(async (options) => {
4761
- await init({
4762
- framework: options.framework,
4763
- yes: options.yes,
4764
- cwd: options.cwd
4765
- });
4766
- });
4767
- cli.command("doctor", "Diagnose mangling issues").option("--verbose", "Show detailed output").option("--cwd <dir>", "Current working directory").action(async (options) => {
4768
- await doctor({
4769
- verbose: options.verbose,
4770
- cwd: options.cwd
4771
- });
4772
- });
4773
- cli.command("audit", "Analyze mangling performance").option("--json", "Output as JSON").option("--watch", "Live updates").option("--compare <dir>", "Compare with previous build").option("--cwd <dir>", "Current working directory").action(async (options) => {
4774
- await audit({
4775
- json: options.json,
4776
- watch: options.watch,
4777
- compare: options.compare,
4778
- cwd: options.cwd
4779
- });
4780
- });
4781
- cli.command("generate-types", "Generate TypeScript declarations from tailwind.config.js").option("-c, --config <path>", "Path to tailwind.config.js").option("-o, --output <path>", "Output file path (default: ./csszyx.d.ts)").option("--cwd <dir>", "Current working directory").option("--silent", "Silent mode (no output)").action(async (options) => {
4782
- await generateTypes({
4783
- config: options.config,
4784
- output: options.output,
4785
- cwd: options.cwd,
4786
- silent: options.silent
4787
- });
4788
- });
4789
- cli.command("migrate [dir]", "Convert Tailwind className to sz prop").option("--dry-run", "Show changes without modifying files").option("--ignore <patterns>", "Glob patterns to ignore (comma-separated)").option("--pattern <glob>", "Custom glob pattern for file discovery").option("--cwd <dir>", "Current working directory").option("--braces", "Wrap HTML sz values in outer { } braces (default: bare)").option("--no-fouc", "Skip FOUC-prevention CSS injection into HTML files").option("--inject-runtime <mode>", "Inject runtime script into HTML: local | cdn").option("--cdn-url <url>", "Custom CDN URL for --inject-runtime cdn").option(
4790
- "--local-path <path>",
4791
- "Local script path for --inject-runtime local (default: csszyx-runtime.js)"
4792
- ).option("--audit", "Scan without modifying files and output .csszyx-todo.json").option("--inject-todos", "Inject {/* @sz-todo */} comments above unrecognized classes").option("--resolve-todos <file>", "Path to a JSON file mapping custom classes to sz properties").action(async (dir, options) => {
4793
- await migrate({
4794
- dryRun: options.dryRun,
4795
- ignore: options.ignore ? options.ignore.split(",") : void 0,
4796
- pattern: options.pattern,
4797
- cwd: dir || options.cwd,
4798
- braces: options.braces,
4799
- injectFouc: options.fouc !== false,
4800
- injectRuntime: options.injectRuntime === "local" ? "local" : options.injectRuntime === "cdn" ? "cdn" : false,
4801
- cdnUrl: options.cdnUrl,
4802
- localPath: options.localPath,
4803
- audit: options.audit,
4804
- injectTodos: options.injectTodos,
4805
- resolveTodos: options.resolveTodos
4806
- });
4807
- });
4808
- cli.command(
4809
- "next-prebuild [pattern]",
4810
- "Seed the Next.js Turbopack csszyx safelist and generation manifest"
4811
- ).option("--root <dir>", "Next app root (defaults to cwd)").option("--cwd <dir>", "Current working directory").option("--mode <mode>", "development | production (default: production)").option("--parser-mode <mode>", "rust | oxc | babel (default: rust)").option(
4812
- "--output-file <path>",
4813
- "Tailwind @source safelist output (default: csszyx-classes.html)"
4814
- ).option("--cache-dir <dir>", "Cache directory relative to root (default: .csszyx/cache)").option("--ignore <patterns>", "Extra glob patterns to ignore (comma-separated)").option("--json", "Emit a single JSON result instead of formatted text").action(runNextPrebuildCommand);
4815
- cli.command("next-watch [pattern]", "Maintain the Next.js Turbopack csszyx safelist").option("--root <dir>", "Next app root (defaults to cwd)").option("--cwd <dir>", "Current working directory").option("--parser-mode <mode>", "rust | oxc | babel (default: rust)").option(
4816
- "--output-file <path>",
4817
- "Tailwind @source safelist output (default: csszyx-classes.html)"
4818
- ).option("--cache-dir <dir>", "Cache directory relative to root (default: .csszyx/cache)").option("--ignore <patterns>", "Extra glob patterns to ignore (comma-separated)").option("--debounce-ms <ms>", "Safelist materialization debounce (default: 50)").action(runNextWatchCommand);
4819
- cli.command("").action(() => {
4820
- cli.outputHelp();
4821
- });
4822
- cli.help();
4823
- cli.version(VERSION);
4824
- cli.parse();
4825
- function normalizeNextCommandAlias(argv) {
4826
- if (argv[2] === "next" && (argv[3] === "prebuild" || argv[3] === "watch")) {
4827
- argv.splice(2, 2, `next-${argv[3]}`);
4828
- }
4829
- }
4830
-
4831
- export { classNameToSzObject, extractScreenKeys, extractSpacingKeys, findConfigFile, flattenColors, generateAndWriteTypes, generateTypeDeclarations, generateTypes, transformSource as migrateSource, scanTailwindConfig, writeDeclarationFile };
1
+ export { c as classNameToSzObject, e as extractScreenKeys, a as extractSpacingKeys, f as findConfigFile, b as flattenColors, g as generateAndWriteTypes, d as generateTypeDeclarations, h as generateTypes, t as migrateSource, s as scanTailwindConfig, w as writeDeclarationFile } from './shared/cli.C2lx9IpE.mjs';
2
+ import 'node:path';
3
+ import 'node:fs';
4
+ import 'node:fs/promises';
5
+ import 'node:url';
6
+ import 'tailwindcss/resolveConfig.js';
7
+ import '@babel/parser';
8
+ import '@babel/types';
9
+ import '@csszyx/compiler';