@csszyx/cli 0.3.1 → 0.5.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.js CHANGED
@@ -80,25 +80,30 @@ async function audit(options = {}) {
80
80
  }
81
81
  printHeader("csszyx Audit Report");
82
82
  printSection("\u{1F4CA} Mangle Statistics");
83
- console.log(` Total Classes: ${stats.totalClasses}`);
84
- console.log(` Mangled Classes: ${stats.totalClasses} (100%)`);
85
- console.log(" Unmangled Classes: 0");
86
- console.log();
87
- console.log(" Tier Distribution:");
88
- const tierNames = [
89
- "Tier 1 (a-Z)",
90
- "Tier 2 (a0-Z9)",
91
- "Tier 3 (aa-ZZ)",
92
- "Tier 4 (a00-Z99)",
93
- "Tier 5 (aaa+)"
94
- ];
95
- for (let i = 1; i <= 5; i++) {
96
- const count = stats.tierDistribution[i] || 0;
97
- const percent = stats.totalClasses ? Math.round(count / stats.totalClasses * 100) : 0;
98
- const bar = printBar([count], stats.totalClasses, 20);
99
- console.log(
100
- ` \u2022 ${tierNames[i - 1].padEnd(18)} ${String(count).padStart(3)} (${String(percent).padStart(2)}%) ${colors.dim(bar)}`
101
- );
83
+ if (stats.totalClasses === 0) {
84
+ console.log(" Tier distribution not yet available.");
85
+ console.log(" Run a production build first, then re-run csszyx audit.");
86
+ } else {
87
+ console.log(` Total Classes: ${stats.totalClasses}`);
88
+ console.log(` Mangled Classes: ${stats.totalClasses} (100%)`);
89
+ console.log(" Unmangled Classes: 0");
90
+ console.log();
91
+ console.log(" Tier Distribution:");
92
+ const tierNames = [
93
+ "Tier 1 (a-Z)",
94
+ "Tier 2 (a0-Z9)",
95
+ "Tier 3 (aa-ZZ)",
96
+ "Tier 4 (a00-Z99)",
97
+ "Tier 5 (aaa+)"
98
+ ];
99
+ for (let i = 1; i <= 5; i++) {
100
+ const count = stats.tierDistribution[i] || 0;
101
+ const percent = stats.totalClasses ? Math.round(count / stats.totalClasses * 100) : 0;
102
+ const bar = printBar([count], stats.totalClasses, 20);
103
+ console.log(
104
+ ` \u2022 ${tierNames[i - 1].padEnd(18)} ${String(count).padStart(3)} (${String(percent).padStart(2)}%) ${colors.dim(bar)}`
105
+ );
106
+ }
102
107
  }
103
108
  printSection("\u{1F4BE} Bundle Size Impact");
104
109
  if (stats.bundleSavings.originalHTML > 0) {
@@ -145,14 +150,6 @@ async function collectStats(cwd) {
145
150
  stats.bundleSavings.mangledCSS = Buffer.byteLength(cssContent);
146
151
  stats.bundleSavings.originalCSS = Math.round(stats.bundleSavings.mangledCSS * 1.71);
147
152
  }
148
- stats.totalClasses = 247;
149
- stats.tierDistribution = {
150
- 1: 52,
151
- 2: 87,
152
- 3: 64,
153
- 4: 32,
154
- 5: 12
155
- };
156
153
  return stats;
157
154
  }
158
155
  function formatBytes(bytes) {
@@ -328,8 +325,6 @@ async function doctor(options = {}) {
328
325
  printSuccess("\u2728 No issues found! Your setup looks good.");
329
326
  } else {
330
327
  printWarn(`Found ${issueCount} issue(s)`);
331
- console.log();
332
- console.log("Run `csszyx doctor --fix` to auto-fix common issues");
333
328
  }
334
329
  }
335
330
 
@@ -1283,6 +1278,16 @@ import path4 from "path";
1283
1278
  import { execa } from "execa";
1284
1279
  import fs4 from "fs-extra";
1285
1280
  import prompts from "prompts";
1281
+ var VITE_FRAMEWORKS = /* @__PURE__ */ new Set(["vite-react", "vite-vue", "vite-svelte"]);
1282
+ var NEXTJS_FRAMEWORKS = /* @__PURE__ */ new Set(["nextjs-app", "nextjs-pages"]);
1283
+ var CSS_ENTRY_CANDIDATES = [
1284
+ "src/index.css",
1285
+ "src/app.css",
1286
+ "src/globals.css",
1287
+ "app/globals.css",
1288
+ "src/styles/index.css",
1289
+ "styles/globals.css"
1290
+ ];
1286
1291
  async function init(options = {}) {
1287
1292
  const cwd = options.cwd || process.cwd();
1288
1293
  const projectInfo = getProjectInfo(cwd);
@@ -1294,14 +1299,16 @@ async function init(options = {}) {
1294
1299
  let config = {
1295
1300
  enableSSR: true,
1296
1301
  enableRecovery: true,
1297
- installTailwind: !projectInfo.hasTailwind
1302
+ installTailwind: !projectInfo.hasTailwind,
1303
+ setupGitignore: true,
1304
+ setupTsconfig: !!projectInfo.hasTypeScript
1298
1305
  };
1299
1306
  if (!options.yes) {
1300
1307
  const answers = await prompts([
1301
1308
  {
1302
1309
  type: projectInfo.hasTailwind ? null : "confirm",
1303
1310
  name: "installTailwind",
1304
- message: "Install Tailwind CSS?",
1311
+ message: "Install Tailwind CSS v4?",
1305
1312
  initial: true
1306
1313
  },
1307
1314
  {
@@ -1315,6 +1322,18 @@ async function init(options = {}) {
1315
1322
  name: "enableRecovery",
1316
1323
  message: "Enable development mode recovery?",
1317
1324
  initial: true
1325
+ },
1326
+ {
1327
+ type: "confirm",
1328
+ name: "setupGitignore",
1329
+ message: "Add .csszyx to .gitignore?",
1330
+ initial: true
1331
+ },
1332
+ {
1333
+ type: projectInfo.hasTypeScript ? "confirm" : null,
1334
+ name: "setupTsconfig",
1335
+ message: "Add .csszyx/theme.d.ts to tsconfig.json (Theme Auto-Scan)?",
1336
+ initial: true
1318
1337
  }
1319
1338
  ]);
1320
1339
  config = { ...config, ...answers };
@@ -1323,9 +1342,10 @@ async function init(options = {}) {
1323
1342
  try {
1324
1343
  await execa(projectInfo.packageManager, ["add", "csszyx"], { cwd });
1325
1344
  if (config.installTailwind) {
1345
+ const twPackage = VITE_FRAMEWORKS.has(projectInfo.framework) ? "@tailwindcss/vite" : "@tailwindcss/postcss";
1326
1346
  await execa(
1327
1347
  projectInfo.packageManager,
1328
- ["add", "-D", "tailwindcss", "postcss", "autoprefixer"],
1348
+ ["add", "-D", "tailwindcss", twPackage],
1329
1349
  { cwd }
1330
1350
  );
1331
1351
  }
@@ -1337,17 +1357,30 @@ async function init(options = {}) {
1337
1357
  }
1338
1358
  const spin2 = spinner.start("Creating config files...");
1339
1359
  try {
1340
- const configContent = generateConfigFile(config, projectInfo.framework);
1360
+ const configContent = generateConfigFile(config);
1341
1361
  const configPath = path4.join(
1342
1362
  cwd,
1343
1363
  projectInfo.hasTypeScript ? "csszyx.config.ts" : "csszyx.config.js"
1344
1364
  );
1345
1365
  await fs4.writeFile(configPath, configContent);
1346
1366
  if (config.installTailwind) {
1347
- await fs4.writeFile(
1348
- path4.join(cwd, "tailwind.config.js"),
1349
- generateTailwindConfig()
1350
- );
1367
+ await setupTailwindCss(cwd, projectInfo.framework);
1368
+ }
1369
+ await injectPlugin(cwd, projectInfo.framework);
1370
+ if (config.setupGitignore) {
1371
+ const gitignorePath = path4.join(cwd, ".gitignore");
1372
+ const ignoreEntry = "\n# csszyx generated theme types\n.csszyx\n";
1373
+ if (fs4.existsSync(gitignorePath)) {
1374
+ const content = await fs4.readFile(gitignorePath, "utf8");
1375
+ if (!content.includes(".csszyx")) {
1376
+ await fs4.appendFile(gitignorePath, ignoreEntry);
1377
+ }
1378
+ } else {
1379
+ await fs4.writeFile(gitignorePath, "node_modules\n.csszyx\n");
1380
+ }
1381
+ }
1382
+ if (config.setupTsconfig) {
1383
+ await setupTsconfig(cwd);
1351
1384
  }
1352
1385
  spinner.succeed(spin2, "Created configuration files");
1353
1386
  } catch (error) {
@@ -1360,10 +1393,150 @@ async function init(options = {}) {
1360
1393
  console.log();
1361
1394
  printInfo("Next steps:");
1362
1395
  console.log(` \u2022 Run '${projectInfo.packageManager} run dev' to start`);
1363
- console.log(" \u2022 Open http://localhost:5174 for the dashboard");
1364
- console.log(" \u2022 Check the docs at https://github.com/nguyennhutien/csszyx");
1396
+ console.log(" \u2022 Check the docs at https://csszyx.dev");
1365
1397
  }
1366
- function generateConfigFile(config, framework) {
1398
+ async function setupTailwindCss(cwd, framework) {
1399
+ let cssPath;
1400
+ for (const candidate of CSS_ENTRY_CANDIDATES) {
1401
+ const full = path4.join(cwd, candidate);
1402
+ if (fs4.existsSync(full)) {
1403
+ cssPath = full;
1404
+ break;
1405
+ }
1406
+ }
1407
+ if (!cssPath) {
1408
+ cssPath = path4.join(cwd, "src/index.css");
1409
+ await fs4.ensureDir(path4.dirname(cssPath));
1410
+ await fs4.writeFile(cssPath, '@import "tailwindcss";\n');
1411
+ printInfo(`Created ${path4.relative(cwd, cssPath)} with Tailwind v4 import`);
1412
+ return;
1413
+ }
1414
+ const content = await fs4.readFile(cssPath, "utf8");
1415
+ if (!content.includes('@import "tailwindcss"') && !content.includes("@import 'tailwindcss'")) {
1416
+ await fs4.writeFile(cssPath, `@import "tailwindcss";
1417
+
1418
+ ${content}`);
1419
+ printInfo(`Added Tailwind v4 import to ${path4.relative(cwd, cssPath)}`);
1420
+ }
1421
+ if (NEXTJS_FRAMEWORKS.has(framework)) {
1422
+ const postcssMjs = path4.join(cwd, "postcss.config.mjs");
1423
+ const postcssJs = path4.join(cwd, "postcss.config.js");
1424
+ const postcssTs = path4.join(cwd, "postcss.config.ts");
1425
+ if (!fs4.existsSync(postcssMjs) && !fs4.existsSync(postcssJs) && !fs4.existsSync(postcssTs)) {
1426
+ await fs4.writeFile(postcssMjs, generatePostcssConfig());
1427
+ printInfo("Created postcss.config.mjs for Tailwind v4");
1428
+ }
1429
+ }
1430
+ }
1431
+ async function injectPlugin(cwd, framework) {
1432
+ if (VITE_FRAMEWORKS.has(framework)) {
1433
+ const injected = await injectVitePlugin(cwd);
1434
+ if (!injected) {
1435
+ printWarn("Could not auto-inject csszyx plugin. Add manually to vite.config.ts:");
1436
+ console.log(generateVitePluginInstructions());
1437
+ }
1438
+ } else if (NEXTJS_FRAMEWORKS.has(framework)) {
1439
+ const injected = await injectNextPlugin(cwd);
1440
+ if (!injected) {
1441
+ printWarn("Could not auto-inject csszyx plugin. Add manually to next.config.js:");
1442
+ console.log(generateNextPluginInstructions());
1443
+ }
1444
+ } else {
1445
+ printInfo("Add csszyx plugin to your bundler config:");
1446
+ console.log(generateVitePluginInstructions());
1447
+ }
1448
+ }
1449
+ async function injectVitePlugin(cwd) {
1450
+ const candidates = [
1451
+ path4.join(cwd, "vite.config.ts"),
1452
+ path4.join(cwd, "vite.config.js"),
1453
+ path4.join(cwd, "vite.config.mts"),
1454
+ path4.join(cwd, "vite.config.mjs")
1455
+ ];
1456
+ let configPath;
1457
+ for (const c of candidates) {
1458
+ if (fs4.existsSync(c)) {
1459
+ configPath = c;
1460
+ break;
1461
+ }
1462
+ }
1463
+ if (!configPath) {
1464
+ return false;
1465
+ }
1466
+ let content = await fs4.readFile(configPath, "utf8");
1467
+ if (content.includes("csszyx")) {
1468
+ return true;
1469
+ }
1470
+ const importBlock = [
1471
+ "import csszyx from 'csszyx/vite';",
1472
+ content.includes("@tailwindcss/vite") ? null : "import tailwindcss from '@tailwindcss/vite';"
1473
+ ].filter(Boolean).join("\n");
1474
+ const lastImportMatch = [...content.matchAll(/^import .+$/gm)].pop();
1475
+ if (!lastImportMatch || lastImportMatch.index === void 0) {
1476
+ return false;
1477
+ }
1478
+ const insertAt = lastImportMatch.index + lastImportMatch[0].length;
1479
+ content = content.slice(0, insertAt) + "\n" + importBlock + content.slice(insertAt);
1480
+ const pluginsMatch = content.match(/plugins\s*:\s*\[/);
1481
+ if (!pluginsMatch || pluginsMatch.index === void 0) {
1482
+ return false;
1483
+ }
1484
+ const pluginsInsertAt = pluginsMatch.index + pluginsMatch[0].length;
1485
+ const twEntry = content.includes("tailwindcss()") ? "" : "\n tailwindcss(),";
1486
+ content = content.slice(0, pluginsInsertAt) + `
1487
+ ...csszyx(), // csszyx MUST come before tailwindcss${twEntry}` + content.slice(pluginsInsertAt);
1488
+ await fs4.writeFile(configPath, content);
1489
+ printInfo(`Injected csszyx plugin into ${path4.basename(configPath)}`);
1490
+ return true;
1491
+ }
1492
+ async function injectNextPlugin(cwd) {
1493
+ const candidates = [
1494
+ path4.join(cwd, "next.config.ts"),
1495
+ path4.join(cwd, "next.config.mjs"),
1496
+ path4.join(cwd, "next.config.js")
1497
+ ];
1498
+ let configPath;
1499
+ for (const c of candidates) {
1500
+ if (fs4.existsSync(c)) {
1501
+ configPath = c;
1502
+ break;
1503
+ }
1504
+ }
1505
+ if (!configPath) {
1506
+ configPath = path4.join(cwd, "next.config.js");
1507
+ await fs4.writeFile(configPath, generateNextConfig());
1508
+ printInfo("Created next.config.js with csszyx plugin");
1509
+ return true;
1510
+ }
1511
+ const content = await fs4.readFile(configPath, "utf8");
1512
+ if (content.includes("csszyx")) {
1513
+ return true;
1514
+ }
1515
+ return false;
1516
+ }
1517
+ async function setupTsconfig(cwd) {
1518
+ let tsconfigPath = path4.join(cwd, "tsconfig.json");
1519
+ if (!fs4.existsSync(tsconfigPath)) {
1520
+ const viteTsConfig = path4.join(cwd, "tsconfig.app.json");
1521
+ if (fs4.existsSync(viteTsConfig)) {
1522
+ tsconfigPath = viteTsConfig;
1523
+ }
1524
+ }
1525
+ if (!fs4.existsSync(tsconfigPath)) {
1526
+ return;
1527
+ }
1528
+ let content = await fs4.readFile(tsconfigPath, "utf8");
1529
+ if (content.includes(".csszyx")) {
1530
+ return;
1531
+ }
1532
+ const includeMatch = content.match(/"include"\s*:\s*\[/);
1533
+ if (includeMatch && includeMatch.index !== void 0) {
1534
+ const insertPos = includeMatch.index + includeMatch[0].length;
1535
+ content = content.slice(0, insertPos) + '\n "./.csszyx/theme.d.ts",\n "./.csszyx",' + content.slice(insertPos);
1536
+ await fs4.writeFile(tsconfigPath, content);
1537
+ }
1538
+ }
1539
+ function generateConfigFile(config) {
1367
1540
  return `import type { CsszyxConfig } from 'csszyx';
1368
1541
 
1369
1542
  const config: CsszyxConfig = {
@@ -1380,27 +1553,86 @@ const config: CsszyxConfig = {
1380
1553
  export default config;
1381
1554
  `;
1382
1555
  }
1383
- function generateTailwindConfig() {
1384
- return `/** @type {import('tailwindcss').Config} */
1385
- export default {
1386
- content: ['./index.html', './src/**/*.{js,ts,jsx,tsx,vue,svelte}'],
1387
- theme: {
1388
- extend: {},
1556
+ function generatePostcssConfig() {
1557
+ return `export default {
1558
+ plugins: {
1559
+ '@tailwindcss/postcss': {},
1389
1560
  },
1390
- plugins: [],
1391
1561
  };
1392
1562
  `;
1393
1563
  }
1564
+ function generateNextConfig() {
1565
+ return `const csszyxWebpack = require('@csszyx/unplugin/webpack').default;
1566
+
1567
+ /** @type {import('next').NextConfig} */
1568
+ const nextConfig = {
1569
+ webpack(config) {
1570
+ config.plugins.unshift(...csszyxWebpack());
1571
+ return config;
1572
+ },
1573
+ };
1574
+
1575
+ module.exports = nextConfig;
1576
+ `;
1577
+ }
1578
+ function generateVitePluginInstructions() {
1579
+ return `
1580
+ import csszyx from 'csszyx/vite';
1581
+ import tailwindcss from '@tailwindcss/vite';
1582
+
1583
+ export default defineConfig({
1584
+ plugins: [
1585
+ ...csszyx(), // csszyx MUST come before tailwindcss
1586
+ tailwindcss(),
1587
+ // ...your other plugins
1588
+ ],
1589
+ });
1590
+ `;
1591
+ }
1592
+ function generateNextPluginInstructions() {
1593
+ return `
1594
+ const csszyxWebpack = require('@csszyx/unplugin/webpack').default;
1595
+
1596
+ module.exports = {
1597
+ webpack(config) {
1598
+ config.plugins.unshift(...csszyxWebpack());
1599
+ return config;
1600
+ },
1601
+ };
1602
+ `;
1603
+ }
1394
1604
 
1395
1605
  // src/commands/migrate.ts
1396
1606
  import fs5 from "fs";
1397
1607
  import path5 from "path";
1608
+ import readline from "readline";
1398
1609
  import fg from "fast-glob";
1399
1610
 
1611
+ // src/migrate/ast-transformer.ts
1612
+ import { parse } from "@babel/parser";
1613
+ import _traverse from "@babel/traverse";
1614
+ import * as t from "@babel/types";
1615
+
1400
1616
  // src/migrate/sz-codegen.ts
1401
1617
  function generateSzExpression(obj) {
1402
1618
  return `{${objectToString(obj)}}`;
1403
1619
  }
1620
+ function generateSzHtmlValue(obj, braces = false) {
1621
+ const s = objectToString(obj);
1622
+ if (braces) {
1623
+ return s;
1624
+ }
1625
+ if (s.startsWith("{ ") && s.endsWith(" }")) {
1626
+ return s.slice(2, -2);
1627
+ }
1628
+ if (s.startsWith("{") && s.endsWith("}")) {
1629
+ return s.slice(1, -1).trim();
1630
+ }
1631
+ return s;
1632
+ }
1633
+ function generateSzObjectLiteral(obj) {
1634
+ return objectToString(obj);
1635
+ }
1404
1636
  function objectToString(obj, indent = 0) {
1405
1637
  const entries = Object.entries(obj);
1406
1638
  if (entries.length === 0) {
@@ -1526,9 +1758,10 @@ var REVERSE_PROPERTY_MAP = {
1526
1758
  // Outline (ambiguous)
1527
1759
  "outline": "outline",
1528
1760
  "outline-offset": "outlineOffset",
1529
- // Ring
1761
+ // Ring (v4: outset ring + inset ring)
1530
1762
  "ring": "ring",
1531
1763
  "ring-offset": "ringOffset",
1764
+ "inset-ring": "insetRing",
1532
1765
  // Spacing
1533
1766
  "p": "p",
1534
1767
  "pt": "pt",
@@ -1586,8 +1819,9 @@ var REVERSE_PROPERTY_MAP = {
1586
1819
  "right": "right",
1587
1820
  "bottom": "bottom",
1588
1821
  "left": "left",
1589
- "start": "start",
1590
- "end": "end",
1822
+ // TW v4.2: start/end are deprecated — migrate to inset-s/inset-e
1823
+ "start": "insetS",
1824
+ "end": "insetE",
1591
1825
  // Typography (ambiguous — text-*, font-* disambiguated)
1592
1826
  "text": "color",
1593
1827
  // default for text- prefix
@@ -1643,6 +1877,7 @@ var REVERSE_PROPERTY_MAP = {
1643
1877
  // Effects
1644
1878
  "shadow": "shadow",
1645
1879
  // ambiguous (shadow vs shadowColor)
1880
+ "inset-shadow": "insetShadow",
1646
1881
  "opacity": "opacity",
1647
1882
  "mix-blend": "mixBlend",
1648
1883
  "bg-blend": "bgBlend",
@@ -1801,16 +2036,15 @@ var REVERSE_BOOLEAN_MAP = {
1801
2036
  "divide-y-reverse": "divideYReverse",
1802
2037
  "space-x-reverse": "spaceXReverse",
1803
2038
  "space-y-reverse": "spaceYReverse",
1804
- // Ring/Outline (boolean defaults)
2039
+ // Ring/Outline/Border-radius (boolean defaults)
1805
2040
  "ring": "ring",
2041
+ "inset-ring": "insetRing",
1806
2042
  "outline": "outline",
2043
+ "rounded": "rounded",
1807
2044
  // Transforms
1808
- "scale-3d": "scale3d",
1809
- "rotate-3d": "rotate3d",
1810
- "translate-3d": "translate3d",
1811
- "transform-gpu": "transformGpu",
1812
- "transform-cpu": "transformCpu",
1813
- "transform-none": "transformNone",
2045
+ "scale-3d": "scale",
2046
+ "translate-3d": "translate",
2047
+ // transform-gpu/cpu/none use BOOLEAN_VALUE_MAP → { transform: 'gpu'/'cpu'/'none' }
1814
2048
  // Font numeric
1815
2049
  "normal-nums": "fontVariant",
1816
2050
  "lining-nums": "fontVariant",
@@ -1872,7 +2106,11 @@ var BOOLEAN_VALUE_MAP = {
1872
2106
  "stacked-fractions": { prop: "fontVariant", value: "stacked-fractions" },
1873
2107
  // Appearance
1874
2108
  "appearance-none": { prop: "appearance", value: "none" },
1875
- "appearance-auto": { prop: "appearance", value: "auto" }
2109
+ "appearance-auto": { prop: "appearance", value: "auto" },
2110
+ // Transform
2111
+ "transform-none": { prop: "transform", value: "none" },
2112
+ "transform-gpu": { prop: "transform", value: "gpu" },
2113
+ "transform-cpu": { prop: "transform", value: "cpu" }
1876
2114
  };
1877
2115
  var SORTED_PREFIXES = Object.keys(REVERSE_PROPERTY_MAP).sort((a, b) => {
1878
2116
  if (b.length !== a.length) {
@@ -2259,6 +2497,9 @@ function parseClass(cls) {
2259
2497
  if (prefix === "border") {
2260
2498
  return applyImportant({ prop: "border", value: true }, important);
2261
2499
  }
2500
+ if (["border-t", "border-r", "border-b", "border-l", "border-x", "border-y", "border-s", "border-e"].includes(prefix)) {
2501
+ return applyImportant({ prop, value: true }, important);
2502
+ }
2262
2503
  continue;
2263
2504
  }
2264
2505
  if (source.startsWith(prefix + "-")) {
@@ -2406,6 +2647,12 @@ function disambiguateAndParse(prefix, rawValue, negative) {
2406
2647
  value = rawValue.slice(0, slashIdx);
2407
2648
  if (opacity.startsWith("[") && opacity.endsWith("]")) {
2408
2649
  opacity = opacity.slice(1, -1);
2650
+ if (!String(opacity).includes("%")) {
2651
+ const opNum = Number(opacity);
2652
+ if (!isNaN(opNum)) {
2653
+ opacity = opNum;
2654
+ }
2655
+ }
2409
2656
  } else if (opacity.startsWith("(") && opacity.endsWith(")")) {
2410
2657
  opacity = opacity.slice(1, -1);
2411
2658
  } else {
@@ -2455,6 +2702,10 @@ function disambiguate(prefix, value, negative) {
2455
2702
  return disambiguateRing(value, negative);
2456
2703
  case "ring-offset":
2457
2704
  return disambiguateRingOffset(value);
2705
+ case "inset-ring":
2706
+ return disambiguateInsetRing(value, negative);
2707
+ case "inset-shadow":
2708
+ return disambiguateInsetShadow(value);
2458
2709
  case "stroke":
2459
2710
  return disambiguateStroke(value);
2460
2711
  case "list":
@@ -2496,6 +2747,9 @@ function disambiguateText(value) {
2496
2747
  if (TEXT_OVERFLOW_KEYWORDS.has(value)) {
2497
2748
  return { prop: "textOverflow", value };
2498
2749
  }
2750
+ if (isArbitraryDimension(value)) {
2751
+ return { prop: "text", value: parseStringValue(value) };
2752
+ }
2499
2753
  return { prop: "color", value: parseStringValue(value) };
2500
2754
  }
2501
2755
  function disambiguateFont(value) {
@@ -2524,6 +2778,9 @@ function disambiguateBorder(value) {
2524
2778
  if (BORDER_STYLE_KEYWORDS.has(value)) {
2525
2779
  return { prop: "borderStyle", value };
2526
2780
  }
2781
+ if (isArbitraryDimension(value)) {
2782
+ return { prop: "border", value: parseStringValue(value) };
2783
+ }
2527
2784
  return { prop: "borderColor", value: parseStringValue(value) };
2528
2785
  }
2529
2786
  function disambiguateBg(value) {
@@ -2567,6 +2824,9 @@ function disambiguateOutline(value) {
2567
2824
  if (!isNaN(num) && Number.isInteger(num)) {
2568
2825
  return { prop: "outline", value: num };
2569
2826
  }
2827
+ if (isArbitraryDimension(value)) {
2828
+ return { prop: "outline", value: parseStringValue(value) };
2829
+ }
2570
2830
  return { prop: "outlineColor", value: parseStringValue(value) };
2571
2831
  }
2572
2832
  function disambiguateDecoration(value) {
@@ -2587,8 +2847,8 @@ function disambiguateRing(value, negative) {
2587
2847
  if (!isNaN(num) && Number.isInteger(num)) {
2588
2848
  return { prop: "ring", value: negative ? -num : num };
2589
2849
  }
2590
- if (value === "inset") {
2591
- return { prop: "ring", value: "inset" };
2850
+ if (isArbitraryDimension(value)) {
2851
+ return { prop: "ring", value: parseStringValue(value) };
2592
2852
  }
2593
2853
  return { prop: "ringColor", value: parseStringValue(value) };
2594
2854
  }
@@ -2599,6 +2859,26 @@ function disambiguateRingOffset(value) {
2599
2859
  }
2600
2860
  return { prop: "ringOffsetColor", value: parseStringValue(value) };
2601
2861
  }
2862
+ function disambiguateInsetRing(value, negative) {
2863
+ const num = Number(value);
2864
+ if (!isNaN(num) && Number.isInteger(num)) {
2865
+ return { prop: "insetRing", value: negative ? -num : num };
2866
+ }
2867
+ if (isArbitraryDimension(value)) {
2868
+ return { prop: "insetRing", value: parseStringValue(value) };
2869
+ }
2870
+ return { prop: "insetRingColor", value: parseStringValue(value) };
2871
+ }
2872
+ function disambiguateInsetShadow(value) {
2873
+ const INSET_SHADOW_SIZE_KEYWORDS = /* @__PURE__ */ new Set(["sm", "md", "lg", "xl", "2xl", "none", "inner"]);
2874
+ if (INSET_SHADOW_SIZE_KEYWORDS.has(value)) {
2875
+ return { prop: "insetShadow", value };
2876
+ }
2877
+ if (isArbitraryDimension(value)) {
2878
+ return { prop: "insetShadow", value: parseStringValue(value) };
2879
+ }
2880
+ return { prop: "insetShadowColor", value: parseStringValue(value) };
2881
+ }
2602
2882
  function disambiguateStroke(value) {
2603
2883
  const num = Number(value);
2604
2884
  if (!isNaN(num) && Number.isInteger(num)) {
@@ -2644,6 +2924,13 @@ function disambiguateTable(value) {
2644
2924
  function disambiguateDivide(value) {
2645
2925
  return { prop: "divideColor", value: parseStringValue(value) };
2646
2926
  }
2927
+ var 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)$/;
2928
+ function isArbitraryDimension(value) {
2929
+ if (!value.startsWith("[") || !value.endsWith("]")) {
2930
+ return false;
2931
+ }
2932
+ return CSS_DIMENSION_RE.test(value.slice(1, -1));
2933
+ }
2647
2934
  function isValidSpacingValue(value) {
2648
2935
  if (value.startsWith("[") && value.endsWith("]")) {
2649
2936
  return true;
@@ -2706,6 +2993,12 @@ function parseValue(prefix, value, negative) {
2706
2993
  if (negative) {
2707
2994
  return "-" + inner;
2708
2995
  }
2996
+ if (prefix === "content") {
2997
+ const isQuoted = inner.startsWith("'") && inner.endsWith("'") || inner.startsWith('"') && inner.endsWith('"');
2998
+ if (isQuoted) {
2999
+ return `"${inner.slice(1, -1)}"`;
3000
+ }
3001
+ }
2709
3002
  return inner;
2710
3003
  }
2711
3004
  if (value.startsWith("(") && value.endsWith(")")) {
@@ -2719,7 +3012,7 @@ function parseValue(prefix, value, negative) {
2719
3012
  return value;
2720
3013
  }
2721
3014
  if (value === "px") {
2722
- return "px";
3015
+ return negative ? "-px" : "px";
2723
3016
  }
2724
3017
  if (value === "auto") {
2725
3018
  return "auto";
@@ -2888,6 +3181,24 @@ function parseGroupPeerVariant(variant) {
2888
3181
  keys.push("has");
2889
3182
  keys.push(hasRest);
2890
3183
  }
3184
+ } else if (rest.startsWith("data-")) {
3185
+ const dataRest = rest.slice(5);
3186
+ keys.push("data");
3187
+ if (dataRest.startsWith("[") && dataRest.endsWith("]")) {
3188
+ keys.push(dataRest.slice(1, -1));
3189
+ } else if (dataRest.startsWith("(") && dataRest.endsWith(")")) {
3190
+ keys.push(dataRest.slice(1, -1));
3191
+ } else {
3192
+ keys.push(dataRest);
3193
+ }
3194
+ } else if (rest.startsWith("aria-")) {
3195
+ const ariaRest = rest.slice(5);
3196
+ keys.push("aria");
3197
+ if (ariaRest.startsWith("[") && ariaRest.endsWith("]")) {
3198
+ keys.push(ariaRest.slice(1, -1));
3199
+ } else {
3200
+ keys.push(ariaRest);
3201
+ }
2891
3202
  } else {
2892
3203
  keys.push(normalizeVariantKey(rest));
2893
3204
  }
@@ -2915,11 +3226,77 @@ function normalizeVariantKey(variant) {
2915
3226
  }
2916
3227
  return variant;
2917
3228
  }
2918
- function classNameToSzObject(className) {
3229
+ var TODO_KEEP = "sz:keep";
3230
+ var TODO_REMOVE = "sz:remove";
3231
+ var TODO_PENDING = "sz:todo";
3232
+ function resolveCustomMapEntry(token, customMap, resolveString) {
3233
+ if (!(token in customMap)) {
3234
+ return null;
3235
+ }
3236
+ const val = customMap[token];
3237
+ if (val && typeof val === "object" && !Array.isArray(val)) {
3238
+ return { action: "sz", value: val };
3239
+ }
3240
+ if (typeof val === "string") {
3241
+ if (val === TODO_KEEP) {
3242
+ return { action: "keep" };
3243
+ }
3244
+ if (val === TODO_REMOVE) {
3245
+ return { action: "remove" };
3246
+ }
3247
+ if (val === TODO_PENDING) {
3248
+ return { action: "unresolved" };
3249
+ }
3250
+ const result = resolveString(val);
3251
+ if (result && Object.keys(result.sz).length > 0) {
3252
+ return { action: "sz", value: result.sz, cascade: result.cascade };
3253
+ }
3254
+ return { action: "unresolved" };
3255
+ }
3256
+ return { action: "unresolved" };
3257
+ }
3258
+ function classNameToSzObject(className, customMap) {
2919
3259
  const tokens = tokenize(className);
2920
3260
  const szObject = {};
2921
3261
  const unrecognized = [];
3262
+ const keepInClassName = [];
2922
3263
  for (const token of tokens) {
3264
+ if (customMap && token in customMap) {
3265
+ const entry = resolveCustomMapEntry(
3266
+ token,
3267
+ customMap,
3268
+ // Inline resolver: parse Tailwind string recursively (no customMap to avoid infinite loop).
3269
+ // Returns both the sz object and any unrecognized tokens from the string value,
3270
+ // so partially-valid strings cascade their unknowns back to the unrecognized list.
3271
+ (twStr) => {
3272
+ const inner = classNameToSzObject(twStr);
3273
+ if (Object.keys(inner.szObject).length === 0) {
3274
+ return null;
3275
+ }
3276
+ return { sz: inner.szObject, cascade: inner.unrecognized };
3277
+ }
3278
+ );
3279
+ if (entry) {
3280
+ if (entry.action === "sz") {
3281
+ Object.assign(szObject, entry.value);
3282
+ if (entry.cascade && entry.cascade.length > 0) {
3283
+ unrecognized.push(...entry.cascade);
3284
+ }
3285
+ continue;
3286
+ }
3287
+ if (entry.action === "keep") {
3288
+ keepInClassName.push(token);
3289
+ continue;
3290
+ }
3291
+ if (entry.action === "remove") {
3292
+ continue;
3293
+ }
3294
+ if (entry.action === "unresolved") {
3295
+ unrecognized.push(token);
3296
+ continue;
3297
+ }
3298
+ }
3299
+ }
2923
3300
  const { variantParts, baseClass } = extractVariants(token);
2924
3301
  const parsed = parseClass(baseClass);
2925
3302
  if (!parsed) {
@@ -2933,7 +3310,7 @@ function classNameToSzObject(className) {
2933
3310
  }
2934
3311
  setNestedValue(szObject, keyPath, parsed.prop, parsed.value);
2935
3312
  }
2936
- return { szObject, unrecognized };
3313
+ return { szObject, unrecognized, keepInClassName };
2937
3314
  }
2938
3315
  function setNestedValue(obj, keyPath, prop, value) {
2939
3316
  let current = obj;
@@ -2946,21 +3323,593 @@ function setNestedValue(obj, keyPath, prop, value) {
2946
3323
  current[prop] = value;
2947
3324
  }
2948
3325
 
3326
+ // src/migrate/dynamic-patterns.ts
3327
+ var CLSX_LIKE_NAMES = /* @__PURE__ */ new Set([
3328
+ "clsx",
3329
+ "cn",
3330
+ "cx",
3331
+ "twMerge",
3332
+ "classNames",
3333
+ "classnames"
3334
+ ]);
3335
+ function isClsxLikeName(name) {
3336
+ return CLSX_LIKE_NAMES.has(name);
3337
+ }
3338
+ function handleClsxCall(node, source, t2, customMap) {
3339
+ const warnings = [];
3340
+ const allUnrecognized = [];
3341
+ const elements = [];
3342
+ let converted = 0;
3343
+ for (const arg of node.arguments) {
3344
+ if (t2.isSpreadElement(arg)) {
3345
+ const argSrc2 = safeSlice(source, arg.start, arg.end);
3346
+ warnings.push(`Cannot migrate spread argument: ${argSrc2}`);
3347
+ return skip(allUnrecognized, warnings);
3348
+ }
3349
+ if (t2.isStringLiteral(arg)) {
3350
+ const result = migrateString(arg.value, customMap);
3351
+ if (!result) {
3352
+ return skip(allUnrecognized, warnings);
3353
+ }
3354
+ elements.push(result.objectStr);
3355
+ allUnrecognized.push(...result.unrecognized);
3356
+ converted++;
3357
+ continue;
3358
+ }
3359
+ if (t2.isLogicalExpression(arg) && arg.operator === "&&") {
3360
+ const result = handleLogicalAndInner(arg, source, t2, customMap);
3361
+ if (!result) {
3362
+ const argSrc2 = safeSlice(source, arg.start, arg.end);
3363
+ warnings.push(`Cannot migrate logical expression: ${argSrc2}`);
3364
+ return skip(allUnrecognized, warnings);
3365
+ }
3366
+ elements.push(result.exprStr);
3367
+ allUnrecognized.push(...result.unrecognized);
3368
+ converted++;
3369
+ continue;
3370
+ }
3371
+ if (t2.isConditionalExpression(arg)) {
3372
+ const result = handleTernaryInner(arg, source, t2, customMap);
3373
+ if (!result) {
3374
+ const argSrc2 = safeSlice(source, arg.start, arg.end);
3375
+ warnings.push(`Cannot migrate ternary: ${argSrc2}`);
3376
+ return skip(allUnrecognized, warnings);
3377
+ }
3378
+ elements.push(result.exprStr);
3379
+ allUnrecognized.push(...result.unrecognized);
3380
+ converted++;
3381
+ continue;
3382
+ }
3383
+ const argSrc = safeSlice(source, arg.start, arg.end);
3384
+ warnings.push(`Cannot migrate argument: ${argSrc}`);
3385
+ return skip(allUnrecognized, warnings);
3386
+ }
3387
+ if (elements.length === 0) {
3388
+ return skip(allUnrecognized, warnings);
3389
+ }
3390
+ if (elements.length === 1 && !elements[0].includes("&&") && !elements[0].includes("?")) {
3391
+ return {
3392
+ replacement: `sz={${elements[0]}}`,
3393
+ unrecognized: allUnrecognized,
3394
+ warnings,
3395
+ converted,
3396
+ migrated: true
3397
+ };
3398
+ }
3399
+ return {
3400
+ replacement: `sz={[${elements.join(", ")}]}`,
3401
+ unrecognized: allUnrecognized,
3402
+ warnings,
3403
+ converted,
3404
+ migrated: true
3405
+ };
3406
+ }
3407
+ function handleTernary(node, source, t2, customMap) {
3408
+ const result = handleTernaryInner(node, source, t2, customMap);
3409
+ if (!result) {
3410
+ return {
3411
+ replacement: "",
3412
+ unrecognized: [],
3413
+ warnings: ["Ternary branches must be string literals"],
3414
+ converted: 0,
3415
+ migrated: false
3416
+ };
3417
+ }
3418
+ return {
3419
+ replacement: `sz={${result.exprStr}}`,
3420
+ unrecognized: result.unrecognized,
3421
+ warnings: [],
3422
+ converted: 1,
3423
+ migrated: true
3424
+ };
3425
+ }
3426
+ function handleLogicalAnd(node, source, t2, customMap) {
3427
+ if (node.operator !== "&&") {
3428
+ return {
3429
+ replacement: "",
3430
+ unrecognized: [],
3431
+ warnings: [`Unsupported logical operator: ${node.operator}`],
3432
+ converted: 0,
3433
+ migrated: false
3434
+ };
3435
+ }
3436
+ const result = handleLogicalAndInner(node, source, t2, customMap);
3437
+ if (!result) {
3438
+ return {
3439
+ replacement: "",
3440
+ unrecognized: [],
3441
+ warnings: ["Right side of && must be a string literal"],
3442
+ converted: 0,
3443
+ migrated: false
3444
+ };
3445
+ }
3446
+ return {
3447
+ replacement: `sz={${result.exprStr}}`,
3448
+ unrecognized: result.unrecognized,
3449
+ warnings: [],
3450
+ converted: 1,
3451
+ migrated: true
3452
+ };
3453
+ }
3454
+ function handleTemplateLiteral(node, source, t2, customMap) {
3455
+ const warnings = [];
3456
+ const allUnrecognized = [];
3457
+ const staticText = node.quasis.map((q) => q.value.cooked ?? q.value.raw).join(" ");
3458
+ const trimmedStatic = staticText.replace(/\s+/g, " ").trim();
3459
+ let baseObject = {};
3460
+ if (trimmedStatic) {
3461
+ const { szObject, unrecognized } = classNameToSzObject(trimmedStatic, customMap);
3462
+ baseObject = szObject;
3463
+ allUnrecognized.push(...unrecognized);
3464
+ }
3465
+ const dynamicElements = [];
3466
+ let converted = 0;
3467
+ for (const expr of node.expressions) {
3468
+ if (!isExpression(expr, t2)) {
3469
+ const exprSrc2 = safeSlice(source, expr.start, expr.end);
3470
+ warnings.push(`Cannot migrate template expression: ${exprSrc2}`);
3471
+ return skip(allUnrecognized, warnings);
3472
+ }
3473
+ if (t2.isStringLiteral(expr)) {
3474
+ const result = migrateString(expr.value, customMap);
3475
+ if (result) {
3476
+ const { szObject } = classNameToSzObject(expr.value, customMap);
3477
+ baseObject = { ...baseObject, ...szObject };
3478
+ allUnrecognized.push(...result.unrecognized);
3479
+ converted++;
3480
+ }
3481
+ continue;
3482
+ }
3483
+ if (t2.isConditionalExpression(expr)) {
3484
+ const result = handleTernaryInner(expr, source, t2, customMap);
3485
+ if (!result) {
3486
+ const exprSrc2 = safeSlice(source, expr.start, expr.end);
3487
+ warnings.push(`Cannot migrate template ternary: ${exprSrc2}`);
3488
+ return skip(allUnrecognized, warnings);
3489
+ }
3490
+ dynamicElements.push(result.exprStr);
3491
+ allUnrecognized.push(...result.unrecognized);
3492
+ converted++;
3493
+ continue;
3494
+ }
3495
+ if (t2.isLogicalExpression(expr) && expr.operator === "&&") {
3496
+ const result = handleLogicalAndInner(expr, source, t2, customMap);
3497
+ if (!result) {
3498
+ const exprSrc2 = safeSlice(source, expr.start, expr.end);
3499
+ warnings.push(`Cannot migrate template logical expr: ${exprSrc2}`);
3500
+ return skip(allUnrecognized, warnings);
3501
+ }
3502
+ dynamicElements.push(result.exprStr);
3503
+ allUnrecognized.push(...result.unrecognized);
3504
+ converted++;
3505
+ continue;
3506
+ }
3507
+ const exprSrc = safeSlice(source, expr.start, expr.end);
3508
+ warnings.push(`Cannot migrate template expression: ${exprSrc}`);
3509
+ return skip(allUnrecognized, warnings);
3510
+ }
3511
+ const hasBase = Object.keys(baseObject).length > 0;
3512
+ const hasDynamic = dynamicElements.length > 0;
3513
+ if (!hasBase && !hasDynamic) {
3514
+ return skip(allUnrecognized, warnings);
3515
+ }
3516
+ if (hasBase && !hasDynamic) {
3517
+ return {
3518
+ replacement: `sz=${generateSzExpression(baseObject)}`,
3519
+ unrecognized: allUnrecognized,
3520
+ warnings,
3521
+ converted: converted + 1,
3522
+ migrated: true
3523
+ };
3524
+ }
3525
+ const parts = [];
3526
+ if (hasBase) {
3527
+ parts.push(generateSzObjectLiteral(baseObject));
3528
+ }
3529
+ parts.push(...dynamicElements);
3530
+ return {
3531
+ replacement: `sz={[${parts.join(", ")}]}`,
3532
+ unrecognized: allUnrecognized,
3533
+ warnings,
3534
+ converted: converted + (hasBase ? 1 : 0),
3535
+ migrated: true
3536
+ };
3537
+ }
3538
+ function handleTernaryInner(node, source, t2, customMap) {
3539
+ if (!t2.isStringLiteral(node.consequent) || !t2.isStringLiteral(node.alternate)) {
3540
+ return null;
3541
+ }
3542
+ const condSource = safeSlice(source, node.test.start, node.test.end);
3543
+ const conValue = node.consequent.value.trim();
3544
+ const altValue = node.alternate.value.trim();
3545
+ if (altValue === "") {
3546
+ if (!conValue) {
3547
+ return null;
3548
+ }
3549
+ const conResult2 = migrateString(conValue, customMap);
3550
+ if (!conResult2 || conResult2.unrecognized.length > 0) {
3551
+ return null;
3552
+ }
3553
+ return {
3554
+ exprStr: `${condSource} && ${conResult2.objectStr}`,
3555
+ unrecognized: []
3556
+ };
3557
+ }
3558
+ if (conValue === "") {
3559
+ const altResult2 = migrateString(altValue, customMap);
3560
+ if (!altResult2 || altResult2.unrecognized.length > 0) {
3561
+ return null;
3562
+ }
3563
+ return {
3564
+ exprStr: `!${wrapCondition(condSource)} && ${altResult2.objectStr}`,
3565
+ unrecognized: []
3566
+ };
3567
+ }
3568
+ const conResult = migrateString(conValue, customMap);
3569
+ const altResult = migrateString(altValue, customMap);
3570
+ if (!conResult || !altResult) {
3571
+ return null;
3572
+ }
3573
+ const unrecognized = [...conResult.unrecognized, ...altResult.unrecognized];
3574
+ if (unrecognized.length > 0) {
3575
+ return null;
3576
+ }
3577
+ return {
3578
+ exprStr: `${condSource} ? ${conResult.objectStr} : ${altResult.objectStr}`,
3579
+ unrecognized: []
3580
+ };
3581
+ }
3582
+ function handleLogicalAndInner(node, source, t2, customMap) {
3583
+ if (!t2.isStringLiteral(node.right)) {
3584
+ return null;
3585
+ }
3586
+ const result = migrateString(node.right.value, customMap);
3587
+ if (!result || result.unrecognized.length > 0) {
3588
+ return null;
3589
+ }
3590
+ const condSource = safeSlice(source, node.left.start, node.left.end);
3591
+ return {
3592
+ exprStr: `${condSource} && ${result.objectStr}`,
3593
+ unrecognized: []
3594
+ };
3595
+ }
3596
+ function migrateString(className, customMap) {
3597
+ const trimmed = className.trim();
3598
+ if (!trimmed) {
3599
+ return null;
3600
+ }
3601
+ const { szObject, unrecognized } = classNameToSzObject(trimmed, customMap);
3602
+ if (Object.keys(szObject).length === 0) {
3603
+ return null;
3604
+ }
3605
+ return {
3606
+ objectStr: generateSzObjectLiteral(szObject),
3607
+ unrecognized
3608
+ };
3609
+ }
3610
+ function skip(unrecognized, warnings) {
3611
+ return {
3612
+ replacement: "",
3613
+ unrecognized,
3614
+ warnings,
3615
+ converted: 0,
3616
+ migrated: false
3617
+ };
3618
+ }
3619
+ function safeSlice(source, start, end) {
3620
+ if (start === null || start === void 0 || end === null || end === void 0) {
3621
+ return "<unknown>";
3622
+ }
3623
+ return source.slice(start, end);
3624
+ }
3625
+ function wrapCondition(cond) {
3626
+ if (cond.includes(" ") || cond.includes("||") || cond.includes("&&")) {
3627
+ return `(${cond})`;
3628
+ }
3629
+ return cond;
3630
+ }
3631
+ function isExpression(node, t2) {
3632
+ return t2.isExpression(node);
3633
+ }
3634
+
2949
3635
  // src/migrate/ast-transformer.ts
2950
- function transformSourceSimple(source, filePath) {
3636
+ var traverse = typeof _traverse === "function" ? _traverse : _traverse.default;
3637
+ function injectTodoComment(unrecognized, parent, options, replacements) {
3638
+ if (!options.injectTodos || unrecognized.length === 0) {
3639
+ return;
3640
+ }
3641
+ if (!t.isJSXOpeningElement(parent) || parent.start === null || parent.start === void 0) {
3642
+ return;
3643
+ }
3644
+ replacements.push({
3645
+ start: parent.start,
3646
+ end: parent.start,
3647
+ text: `
3648
+ {/* @sz-todo: ${unrecognized.join(", ")} */}
3649
+ `
3650
+ });
3651
+ }
3652
+ function transformSource(source, filePath, options = {}) {
3653
+ const warnings = [];
3654
+ let classNamesTransformed = 0;
3655
+ let classNamesSkipped = 0;
3656
+ const classesUnrecognized = [];
3657
+ const replacements = [];
3658
+ const clsxImportNames = /* @__PURE__ */ new Set();
3659
+ let clsxUsedOutsideClassName = false;
3660
+ const clsxCallsitesMigrated = /* @__PURE__ */ new Set();
3661
+ let hasCvaImport = false;
3662
+ let ast;
3663
+ try {
3664
+ ast = parse(source, {
3665
+ sourceType: "module",
3666
+ plugins: ["jsx", "typescript", "decorators-legacy"],
3667
+ ranges: true
3668
+ });
3669
+ } catch (err) {
3670
+ const msg = err instanceof Error ? err.message : String(err);
3671
+ return {
3672
+ code: source,
3673
+ changed: false,
3674
+ warnings: [`Parse error in ${filePath}: ${msg}`],
3675
+ stats: { classNamesTransformed: 0, classNamesSkipped: 0, classesUnrecognized: [] },
3676
+ potentiallyUnusedImports: []
3677
+ };
3678
+ }
3679
+ traverse(ast, {
3680
+ // Track clsx-like and CVA imports
3681
+ ImportDeclaration(path6) {
3682
+ const src = path6.node.source.value;
3683
+ const clsxPackages = ["clsx", "clsx/lite", "classnames", "tailwind-merge"];
3684
+ const isClsxPkg = clsxPackages.some((p) => src === p || src.startsWith(p + "/"));
3685
+ const cvaPkgs = ["cva", "class-variance-authority"];
3686
+ if (cvaPkgs.some((p) => src === p || src.startsWith(p + "/"))) {
3687
+ hasCvaImport = true;
3688
+ }
3689
+ for (const spec of path6.node.specifiers) {
3690
+ const localName = spec.local.name;
3691
+ if (isClsxPkg || isClsxLikeName(localName)) {
3692
+ clsxImportNames.add(localName);
3693
+ }
3694
+ }
3695
+ },
3696
+ // Track clsx usage outside className
3697
+ CallExpression(path6) {
3698
+ if (t.isIdentifier(path6.node.callee) && clsxImportNames.has(path6.node.callee.name)) {
3699
+ const inClassName = path6.findParent(
3700
+ (p) => t.isJSXAttribute(p.node) && t.isJSXIdentifier(p.node.name) && p.node.name.name === "className"
3701
+ );
3702
+ if (!inClassName) {
3703
+ clsxUsedOutsideClassName = true;
3704
+ }
3705
+ }
3706
+ },
3707
+ // Main transformation: className → sz
3708
+ JSXAttribute(path6) {
3709
+ const attrName = path6.node.name;
3710
+ if (!t.isJSXIdentifier(attrName) || attrName.name !== "className") {
3711
+ return;
3712
+ }
3713
+ const parent = path6.parent;
3714
+ if (t.isJSXOpeningElement(parent)) {
3715
+ const elementName = parent.name;
3716
+ const isCapitalized = t.isJSXIdentifier(elementName) && /^[A-Z]/.test(elementName.name) || t.isJSXMemberExpression(elementName);
3717
+ if (isCapitalized) {
3718
+ classNamesSkipped++;
3719
+ return;
3720
+ }
3721
+ }
3722
+ if (t.isJSXOpeningElement(parent)) {
3723
+ const hasSz = parent.attributes.some(
3724
+ (attr) => t.isJSXAttribute(attr) && t.isJSXIdentifier(attr.name) && attr.name.name === "sz"
3725
+ );
3726
+ if (hasSz) {
3727
+ classNamesSkipped++;
3728
+ return;
3729
+ }
3730
+ }
3731
+ const value = path6.node.value;
3732
+ const attrStart = path6.node.start;
3733
+ const attrEnd = path6.node.end;
3734
+ if (attrStart === null || attrStart === void 0 || attrEnd === null || attrEnd === void 0) {
3735
+ return;
3736
+ }
3737
+ if (t.isStringLiteral(value)) {
3738
+ const result = processStaticString(value.value, options.customMap);
3739
+ if (result) {
3740
+ replacements.push({ start: attrStart, end: attrEnd, text: result.replacement });
3741
+ classNamesTransformed++;
3742
+ classesUnrecognized.push(...result.unrecognized);
3743
+ injectTodoComment(result.unrecognized, path6.parent, options, replacements);
3744
+ } else {
3745
+ classNamesSkipped++;
3746
+ }
3747
+ return;
3748
+ }
3749
+ if (t.isJSXExpressionContainer(value)) {
3750
+ const expr = value.expression;
3751
+ if (t.isStringLiteral(expr)) {
3752
+ const result = processStaticString(expr.value, options.customMap);
3753
+ if (result) {
3754
+ replacements.push({ start: attrStart, end: attrEnd, text: result.replacement });
3755
+ classNamesTransformed++;
3756
+ classesUnrecognized.push(...result.unrecognized);
3757
+ injectTodoComment(result.unrecognized, path6.parent, options, replacements);
3758
+ } else {
3759
+ classNamesSkipped++;
3760
+ }
3761
+ return;
3762
+ }
3763
+ if (t.isTemplateLiteral(expr)) {
3764
+ const result = handleTemplateLiteral(expr, source, t, options.customMap);
3765
+ if (result.migrated) {
3766
+ replacements.push({ start: attrStart, end: attrEnd, text: result.replacement });
3767
+ classNamesTransformed += result.converted;
3768
+ classesUnrecognized.push(...result.unrecognized);
3769
+ } else {
3770
+ classNamesSkipped++;
3771
+ warnings.push(...result.warnings.map((w) => `[${filePath}] ${w}`));
3772
+ }
3773
+ injectTodoComment(result.unrecognized, path6.parent, options, replacements);
3774
+ return;
3775
+ }
3776
+ if (t.isCallExpression(expr) && t.isIdentifier(expr.callee) && isClsxLikeName(expr.callee.name)) {
3777
+ const result = handleClsxCall(expr, source, t, options.customMap);
3778
+ if (result.migrated) {
3779
+ replacements.push({ start: attrStart, end: attrEnd, text: result.replacement });
3780
+ classNamesTransformed += result.converted;
3781
+ classesUnrecognized.push(...result.unrecognized);
3782
+ if (expr.start !== null && expr.start !== void 0) {
3783
+ clsxCallsitesMigrated.add(expr.start);
3784
+ }
3785
+ } else {
3786
+ classNamesSkipped++;
3787
+ warnings.push(...result.warnings.map((w) => `[${filePath}] ${w}`));
3788
+ }
3789
+ injectTodoComment(result.unrecognized, path6.parent, options, replacements);
3790
+ return;
3791
+ }
3792
+ if (t.isConditionalExpression(expr)) {
3793
+ const result = handleTernary(expr, source, t, options.customMap);
3794
+ if (result.migrated) {
3795
+ replacements.push({ start: attrStart, end: attrEnd, text: result.replacement });
3796
+ classNamesTransformed += result.converted;
3797
+ classesUnrecognized.push(...result.unrecognized);
3798
+ } else {
3799
+ classNamesSkipped++;
3800
+ warnings.push(...result.warnings.map((w) => `[${filePath}] ${w}`));
3801
+ }
3802
+ injectTodoComment(result.unrecognized, path6.parent, options, replacements);
3803
+ return;
3804
+ }
3805
+ if (t.isLogicalExpression(expr) && expr.operator === "&&") {
3806
+ const result = handleLogicalAnd(expr, source, t, options.customMap);
3807
+ if (result.migrated) {
3808
+ replacements.push({ start: attrStart, end: attrEnd, text: result.replacement });
3809
+ classNamesTransformed += result.converted;
3810
+ classesUnrecognized.push(...result.unrecognized);
3811
+ } else {
3812
+ classNamesSkipped++;
3813
+ warnings.push(...result.warnings.map((w) => `[${filePath}] ${w}`));
3814
+ }
3815
+ injectTodoComment(result.unrecognized, path6.parent, options, replacements);
3816
+ return;
3817
+ }
3818
+ classNamesSkipped++;
3819
+ return;
3820
+ }
3821
+ classNamesSkipped++;
3822
+ }
3823
+ });
3824
+ if (hasCvaImport) {
3825
+ warnings.push(
3826
+ `[${filePath}] File uses cva() \u2014 consider migrating to szv() from @csszyx/runtime for type-safe variant-based styling.`
3827
+ );
3828
+ }
3829
+ let output = source;
3830
+ const sorted = replacements.sort((a, b) => b.start - a.start);
3831
+ for (const r of sorted) {
3832
+ output = output.slice(0, r.start) + r.text + output.slice(r.end);
3833
+ }
3834
+ const potentiallyUnusedImports = [];
3835
+ if (clsxImportNames.size > 0 && !clsxUsedOutsideClassName && replacements.length > 0) {
3836
+ for (const name of clsxImportNames) {
3837
+ const callPattern = new RegExp(`\\b${name}\\s*\\(`, "g");
3838
+ if (!callPattern.test(output)) {
3839
+ potentiallyUnusedImports.push(name);
3840
+ }
3841
+ }
3842
+ }
3843
+ return {
3844
+ code: output,
3845
+ changed: replacements.length > 0,
3846
+ warnings,
3847
+ stats: { classNamesTransformed, classNamesSkipped, classesUnrecognized },
3848
+ potentiallyUnusedImports
3849
+ };
3850
+ }
3851
+ function processStaticString(classNameStr, customMap) {
3852
+ const trimmed = classNameStr.trim();
3853
+ if (!trimmed) {
3854
+ return null;
3855
+ }
3856
+ const { szObject, unrecognized, keepInClassName } = classNameToSzObject(trimmed, customMap);
3857
+ if (Object.keys(szObject).length === 0) {
3858
+ return null;
3859
+ }
3860
+ const szExpr = generateSzExpression(szObject);
3861
+ const remainingClassName = [...keepInClassName, ...unrecognized];
3862
+ if (remainingClassName.length > 0) {
3863
+ return {
3864
+ replacement: `className="${remainingClassName.join(" ")}" sz=${szExpr}`,
3865
+ unrecognized
3866
+ };
3867
+ }
3868
+ return {
3869
+ replacement: `sz=${szExpr}`,
3870
+ unrecognized: []
3871
+ };
3872
+ }
3873
+ var FOUC_CSS = `<style>
3874
+ /* csszyx: hide [sz] elements until runtime processes them */
3875
+ [sz] { visibility: hidden; }
3876
+ body.sz-ready [sz] { visibility: visible; }
3877
+ </style>`;
3878
+ function transformHtmlSourceSimple(source, filePath, options = {}) {
3879
+ const {
3880
+ braces = false,
3881
+ injectFouc = true,
3882
+ injectRuntime = false,
3883
+ cdnUrl = "https://cdn.csszyx.com/runtime.js",
3884
+ localPath = "csszyx-runtime.js"
3885
+ } = options;
2951
3886
  const warnings = [];
2952
3887
  let classNamesTransformed = 0;
2953
3888
  let classNamesSkipped = 0;
2954
3889
  const classesUnrecognized = [];
2955
3890
  let changed = false;
2956
- const output = source.replace(/className="([^"]*)"/g, (match, classNameStr) => {
2957
- return processClassNameMatch(match, classNameStr, '"');
3891
+ let output = source.replace(/\bclass="([^"]*)"/g, (match, classStr) => {
3892
+ return processClassAttr(match, classStr, '"');
2958
3893
  });
2959
- const output2 = output.replace(/className='([^']*)'/g, (match, classNameStr) => {
2960
- return processClassNameMatch(match, classNameStr, "'");
3894
+ output = output.replace(/\bclass='([^']*)'/g, (match, classStr) => {
3895
+ return processClassAttr(match, classStr, "'");
2961
3896
  });
2962
- function processClassNameMatch(match, classNameStr, quote) {
2963
- const trimmed = classNameStr.trim();
3897
+ if (injectFouc && output.includes("</head>") && !output.includes("csszyx: hide [sz]")) {
3898
+ output = output.replace("</head>", `${FOUC_CSS}
3899
+ </head>`);
3900
+ changed = true;
3901
+ }
3902
+ if (injectRuntime && output.includes("</body>")) {
3903
+ const scriptSrc = injectRuntime === "cdn" ? cdnUrl : localPath;
3904
+ const scriptTag = `<script src="${scriptSrc}"></script>`;
3905
+ if (!output.includes(scriptSrc)) {
3906
+ output = output.replace("</body>", `${scriptTag}
3907
+ </body>`);
3908
+ changed = true;
3909
+ }
3910
+ }
3911
+ function processClassAttr(match, classStr, quote) {
3912
+ const trimmed = classStr.trim();
2964
3913
  if (!trimmed) {
2965
3914
  classNamesSkipped++;
2966
3915
  return match;
@@ -2971,33 +3920,105 @@ function transformSourceSimple(source, filePath) {
2971
3920
  classesUnrecognized.push(...unrecognized);
2972
3921
  return match;
2973
3922
  }
2974
- const szExpr = generateSzExpression(szObject);
3923
+ const szVal = generateSzHtmlValue(szObject, braces);
2975
3924
  changed = true;
2976
3925
  classNamesTransformed++;
2977
3926
  if (unrecognized.length > 0) {
2978
3927
  classesUnrecognized.push(...unrecognized);
2979
- return `className=${quote}${unrecognized.join(" ")}${quote} sz=${szExpr}`;
3928
+ return `class=${quote}${unrecognized.join(" ")}${quote} sz="${szVal}"`;
2980
3929
  }
2981
- return `sz=${szExpr}`;
3930
+ return `sz="${szVal}"`;
2982
3931
  }
2983
3932
  return {
2984
- code: output2,
3933
+ code: output,
2985
3934
  changed,
2986
3935
  warnings,
2987
- stats: { classNamesTransformed, classNamesSkipped, classesUnrecognized }
3936
+ stats: { classNamesTransformed, classNamesSkipped, classesUnrecognized },
3937
+ potentiallyUnusedImports: []
2988
3938
  };
2989
3939
  }
2990
3940
 
2991
3941
  // src/commands/migrate.ts
3942
+ function createLogFile(cwd) {
3943
+ const now = /* @__PURE__ */ new Date();
3944
+ const ts = now.toISOString().slice(0, 19).replace("T", "_").replace(/:/g, "-");
3945
+ const logDir = path5.join(cwd, ".csszyx", "logs");
3946
+ fs5.mkdirSync(logDir, { recursive: true });
3947
+ const filePath = path5.join(logDir, `migrate-${ts}.log`);
3948
+ const lines = [`csszyx migrate \u2014 ${now.toISOString()}`, ""];
3949
+ return {
3950
+ filePath,
3951
+ writeLine: (line) => lines.push(line),
3952
+ flush: () => fs5.writeFileSync(filePath, lines.join("\n") + "\n", "utf-8")
3953
+ };
3954
+ }
3955
+ function isGitignored(cwd, pattern) {
3956
+ try {
3957
+ const content = fs5.readFileSync(path5.join(cwd, ".gitignore"), "utf-8");
3958
+ return content.split("\n").some((l) => {
3959
+ const t2 = l.trim();
3960
+ return t2 === pattern || t2 === pattern + "/" || t2 === "/" + pattern;
3961
+ });
3962
+ } catch {
3963
+ return false;
3964
+ }
3965
+ }
3966
+ async function askYesNo(question) {
3967
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
3968
+ return new Promise((resolve4) => {
3969
+ rl.question(question, (answer) => {
3970
+ rl.close();
3971
+ resolve4(answer.trim().toLowerCase() === "y");
3972
+ });
3973
+ });
3974
+ }
2992
3975
  async function migrate(options = {}) {
2993
3976
  const cwd = options.cwd || process.cwd();
2994
- const dryRun = options.dryRun || false;
3977
+ let dryRun = options.dryRun || false;
2995
3978
  const ignorePatterns = options.ignore || [];
3979
+ const audit2 = options.audit || false;
3980
+ const resolveTodosPath = options.resolveTodos;
3981
+ let customMap, files;
3982
+ if (audit2) {
3983
+ dryRun = true;
3984
+ }
3985
+ let injectTodos = options.injectTodos || false;
3986
+ if (resolveTodosPath && !injectTodos) {
3987
+ injectTodos = true;
3988
+ }
2996
3989
  printHeader("csszyx Migration Tool");
2997
- if (dryRun) {
3990
+ if (process.stdout.isTTY && !injectTodos && !audit2 && !resolveTodosPath) {
3991
+ const answer = await askYesNo(
3992
+ "Add {/* @sz-todo */} comments above elements with unrecognized classes? [y/N] "
3993
+ );
3994
+ if (answer) {
3995
+ injectTodos = true;
3996
+ }
3997
+ }
3998
+ if (resolveTodosPath) {
3999
+ try {
4000
+ const absolutePath = path5.resolve(cwd, resolveTodosPath);
4001
+ const content = fs5.readFileSync(absolutePath, "utf-8");
4002
+ customMap = JSON.parse(content);
4003
+ printInfo(`Loaded resolution map from ${resolveTodosPath}`);
4004
+ } catch {
4005
+ printWarn(`Could not load resolve map from ${resolveTodosPath}. Ensure the file exists and is valid JSON.`);
4006
+ return;
4007
+ }
4008
+ }
4009
+ if (audit2) {
4010
+ printInfo("Audit mode \u2014 scanning for unrecognized classes to generate a mapping file...");
4011
+ } else if (dryRun) {
2998
4012
  printInfo("Dry run mode \u2014 no files will be modified");
2999
4013
  }
3000
- const patterns = options.pattern ? [options.pattern] : ["**/*.{jsx,tsx}"];
4014
+ const log = createLogFile(cwd);
4015
+ log.writeLine(`Mode: ${audit2 ? "audit" : dryRun ? "dry-run" : "migrate"}${resolveTodosPath ? ` (resolve-todos: ${resolveTodosPath})` : ""}`);
4016
+ log.writeLine(`injectTodos: ${injectTodos}`);
4017
+ log.writeLine("");
4018
+ if (!isGitignored(cwd, ".csszyx")) {
4019
+ printWarn("Tip: add .csszyx/ to your .gitignore to exclude migration logs from version control.");
4020
+ }
4021
+ const patterns = options.pattern ? [options.pattern] : ["**/*.{jsx,tsx,html}"];
3001
4022
  const ignore = [
3002
4023
  "**/node_modules/**",
3003
4024
  "**/dist/**",
@@ -3007,10 +4028,19 @@ async function migrate(options = {}) {
3007
4028
  ...ignorePatterns
3008
4029
  ];
3009
4030
  const s = spinner.start("Scanning for files...");
3010
- const files = await fg(patterns, { cwd, ignore, absolute: true });
4031
+ try {
4032
+ files = await fg(patterns, { cwd, ignore, absolute: true });
4033
+ } catch (err) {
4034
+ s.fail("File scan failed");
4035
+ printWarn(`Could not scan files: ${err instanceof Error ? err.message : String(err)}`);
4036
+ log.flush();
4037
+ return;
4038
+ }
3011
4039
  s.succeed(`Found ${files.length} files`);
3012
4040
  if (files.length === 0) {
3013
- printWarn("No JSX/TSX files found");
4041
+ printWarn(options.pattern ? `No files found matching pattern: ${options.pattern}` : "No JSX/TSX/HTML files found");
4042
+ log.writeLine("No files found.");
4043
+ log.flush();
3014
4044
  return;
3015
4045
  }
3016
4046
  let totalTransformed = 0;
@@ -3018,25 +4048,52 @@ async function migrate(options = {}) {
3018
4048
  let totalFiles = 0;
3019
4049
  const allUnrecognized = [];
3020
4050
  const allWarnings = [];
4051
+ const unusedImportFiles = [];
3021
4052
  const s2 = spinner.start("Migrating...");
3022
4053
  for (const filePath of files) {
3023
4054
  const source = fs5.readFileSync(filePath, "utf-8");
3024
- if (!source.includes("className=")) {
4055
+ const isHtml = filePath.endsWith(".html");
4056
+ const hasRelevantAttr = isHtml ? source.includes("class=") : source.includes("className=");
4057
+ if (!hasRelevantAttr) {
3025
4058
  continue;
3026
4059
  }
3027
- const result = transformSourceSimple(source, filePath);
4060
+ let processSource = source;
4061
+ if (resolveTodosPath && !isHtml) {
4062
+ processSource = processSource.replace(/\{\/\*\s*@sz-todo:\s*(.*?)\s*\*\/\}\n?/g, "");
4063
+ }
4064
+ const result = isHtml ? transformHtmlSourceSimple(processSource, filePath, {
4065
+ braces: options.braces,
4066
+ injectFouc: options.injectFouc,
4067
+ injectRuntime: options.injectRuntime,
4068
+ cdnUrl: options.cdnUrl,
4069
+ localPath: options.localPath
4070
+ }) : transformSource(processSource, filePath, { injectTodos, customMap });
4071
+ allWarnings.push(...result.warnings);
3028
4072
  if (result.changed) {
3029
4073
  totalFiles++;
3030
4074
  totalTransformed += result.stats.classNamesTransformed;
3031
4075
  totalSkipped += result.stats.classNamesSkipped;
3032
4076
  allUnrecognized.push(...result.stats.classesUnrecognized);
3033
- allWarnings.push(...result.warnings);
4077
+ if (result.potentiallyUnusedImports.length > 0) {
4078
+ const rel2 = path5.relative(cwd, filePath);
4079
+ unusedImportFiles.push({ file: rel2, imports: result.potentiallyUnusedImports });
4080
+ }
3034
4081
  if (!dryRun) {
3035
- fs5.writeFileSync(filePath, result.code, "utf-8");
4082
+ try {
4083
+ fs5.writeFileSync(filePath, result.code, "utf-8");
4084
+ } catch (err) {
4085
+ const rel2 = path5.relative(cwd, filePath);
4086
+ printWarn(`Could not write ${rel2}: ${err instanceof Error ? err.message : String(err)}`);
4087
+ log.writeLine(` Write error: ${rel2}`);
4088
+ continue;
4089
+ }
3036
4090
  }
3037
4091
  const rel = path5.relative(cwd, filePath);
3038
4092
  if (dryRun) {
3039
4093
  printInfo(` ${rel}: ${result.stats.classNamesTransformed} className(s) \u2192 sz`);
4094
+ log.writeLine(` ${rel}: ${result.stats.classNamesTransformed} className(s) \u2192 sz`);
4095
+ } else {
4096
+ log.writeLine(` ${rel}: ${result.stats.classNamesTransformed} className(s) \u2192 sz`);
3040
4097
  }
3041
4098
  }
3042
4099
  }
@@ -3044,12 +4101,16 @@ async function migrate(options = {}) {
3044
4101
  console.info();
3045
4102
  printSuccess(`Files modified: ${totalFiles}`);
3046
4103
  printSuccess(`classNames converted: ${totalTransformed}`);
4104
+ log.writeLine(`Files modified: ${totalFiles}`);
4105
+ log.writeLine(`classNames converted: ${totalTransformed}`);
3047
4106
  if (totalSkipped > 0) {
3048
4107
  printWarn(`classNames skipped (dynamic): ${totalSkipped}`);
4108
+ log.writeLine(`classNames skipped (dynamic): ${totalSkipped}`);
3049
4109
  }
3050
4110
  if (allUnrecognized.length > 0) {
3051
4111
  const unique = [...new Set(allUnrecognized)];
3052
4112
  printWarn(`Unrecognized classes (${unique.length}): ${unique.slice(0, 10).join(", ")}${unique.length > 10 ? "..." : ""}`);
4113
+ log.writeLine(`Unrecognized classes (${unique.length}): ${unique.join(", ")}`);
3053
4114
  }
3054
4115
  if (allWarnings.length > 0) {
3055
4116
  console.info();
@@ -3059,6 +4120,57 @@ async function migrate(options = {}) {
3059
4120
  if (allWarnings.length > 5) {
3060
4121
  printWarn(`... and ${allWarnings.length - 5} more warnings`);
3061
4122
  }
4123
+ log.writeLine("");
4124
+ log.writeLine("Warnings:");
4125
+ for (const w of allWarnings) {
4126
+ log.writeLine(` ${w}`);
4127
+ }
4128
+ }
4129
+ if (audit2) {
4130
+ const todoPath = path5.join(cwd, ".csszyx-todo.json");
4131
+ const unique = [...new Set(allUnrecognized)];
4132
+ console.info();
4133
+ if (unique.length === 0) {
4134
+ printSuccess("Audit complete. 100% of your classes are perfectly recognized by csszyx!");
4135
+ log.writeLine("Audit: 100% recognized.");
4136
+ } else {
4137
+ const todoObj = {};
4138
+ for (const u of unique) {
4139
+ todoObj[u] = "sz:todo";
4140
+ }
4141
+ try {
4142
+ fs5.writeFileSync(todoPath, JSON.stringify(todoObj, null, 2));
4143
+ } catch (err) {
4144
+ printWarn(`Could not write ${path5.relative(cwd, todoPath)}: ${err instanceof Error ? err.message : String(err)}`);
4145
+ log.flush();
4146
+ return;
4147
+ }
4148
+ printSuccess(`Audit complete. Exported ${unique.length} unrecognized classes to ${path5.relative(cwd, todoPath)}.`);
4149
+ printInfo("Edit this file to map custom classes, then run: npx @csszyx/cli migrate --resolve-todos .csszyx-todo.json");
4150
+ log.writeLine(`Audit: ${unique.length} unrecognized classes written to ${path5.relative(cwd, todoPath)}`);
4151
+ }
4152
+ }
4153
+ if (resolveTodosPath) {
4154
+ const unique = [...new Set(allUnrecognized)];
4155
+ if (unique.length > 0) {
4156
+ console.info();
4157
+ printWarn(`Still unresolved after this pass (${unique.length}): ${unique.slice(0, 10).join(", ")}${unique.length > 10 ? "..." : ""}`);
4158
+ printInfo("Re-run --audit to generate a fresh snapshot when ready.");
4159
+ log.writeLine(`Still unresolved (${unique.length}): ${unique.join(", ")}`);
4160
+ }
4161
+ }
4162
+ if (unusedImportFiles.length > 0) {
4163
+ console.info();
4164
+ printWarn("Potentially unused imports (run ESLint to clean up):");
4165
+ for (const { file, imports } of unusedImportFiles) {
4166
+ printInfo(` ${file}: ${imports.map((i) => `import { ${i} }`).join(", ")}`);
4167
+ log.writeLine(` Unused import in ${file}: ${imports.join(", ")}`);
4168
+ }
4169
+ }
4170
+ try {
4171
+ log.flush();
4172
+ printInfo(`Migration log saved to ${path5.relative(cwd, log.filePath)}`);
4173
+ } catch {
3062
4174
  }
3063
4175
  }
3064
4176
 
@@ -3072,9 +4184,8 @@ cli.command("init", "Setup csszyx in your project").option("--framework <name>",
3072
4184
  cwd: options.cwd
3073
4185
  });
3074
4186
  });
3075
- cli.command("doctor", "Diagnose mangling issues").option("--fix", "Auto-fix common issues").option("--verbose", "Show detailed output").option("--cwd <dir>", "Current working directory").action(async (options) => {
4187
+ cli.command("doctor", "Diagnose mangling issues").option("--verbose", "Show detailed output").option("--cwd <dir>", "Current working directory").action(async (options) => {
3076
4188
  await doctor({
3077
- fix: options.fix,
3078
4189
  verbose: options.verbose,
3079
4190
  cwd: options.cwd
3080
4191
  });
@@ -3098,12 +4209,20 @@ cli.command(
3098
4209
  silent: options.silent
3099
4210
  });
3100
4211
  });
3101
- 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").action(async (dir, options) => {
4212
+ 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("--local-path <path>", "Local script path for --inject-runtime local (default: csszyx-runtime.js)").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) => {
3102
4213
  await migrate({
3103
4214
  dryRun: options.dryRun,
3104
4215
  ignore: options.ignore ? options.ignore.split(",") : void 0,
3105
4216
  pattern: options.pattern,
3106
- cwd: dir || options.cwd
4217
+ cwd: dir || options.cwd,
4218
+ braces: options.braces,
4219
+ injectFouc: options.fouc !== false,
4220
+ injectRuntime: options.injectRuntime === "local" ? "local" : options.injectRuntime === "cdn" ? "cdn" : false,
4221
+ cdnUrl: options.cdnUrl,
4222
+ localPath: options.localPath,
4223
+ audit: options.audit,
4224
+ injectTodos: options.injectTodos,
4225
+ resolveTodos: options.resolveTodos
3107
4226
  });
3108
4227
  });
3109
4228
  cli.command("").action(() => {
@@ -3113,6 +4232,7 @@ cli.help();
3113
4232
  cli.version(VERSION);
3114
4233
  cli.parse();
3115
4234
  export {
4235
+ classNameToSzObject,
3116
4236
  extractScreenKeys,
3117
4237
  extractSpacingKeys,
3118
4238
  findConfigFile,
@@ -3120,6 +4240,7 @@ export {
3120
4240
  generateAndWriteTypes,
3121
4241
  generateTypeDeclarations,
3122
4242
  generateTypes,
4243
+ transformSource as migrateSource,
3123
4244
  scanTailwindConfig,
3124
4245
  writeDeclarationFile
3125
4246
  };