@kbach/react 1.0.0-beta.5 → 1.0.0-beta.6

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.
@@ -21,200 +21,16 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var vite_plugin_exports = {};
22
22
  __export(vite_plugin_exports, {
23
23
  CSS_SYNC_DEBOUNCE_MS: () => CSS_SYNC_DEBOUNCE_MS,
24
- kbach: () => kbach
24
+ DEFAULT_SCAN_DIRS: () => DEFAULT_SCAN_DIRS,
25
+ kbach: () => kbach,
26
+ loadConfigFile: () => loadConfigFile,
27
+ resolveEffectiveTheme: () => resolveEffectiveTheme
25
28
  });
26
29
  module.exports = __toCommonJS(vite_plugin_exports);
27
- var import_fs2 = require("fs");
28
- var import_path3 = require("path");
30
+ var import_fs3 = require("fs");
31
+ var import_path4 = require("path");
29
32
  var import_module = require("module");
30
33
 
31
- // src/vite-plugin/wasmNode.ts
32
- var import_node = require("@kbach/core-engine/node");
33
- function generateCssForToken(token, themeJson) {
34
- return JSON.parse((0, import_node.generate_css)(token, themeJson));
35
- }
36
-
37
- // src/vite-plugin/scan.ts
38
- var import_fs = require("fs");
39
- var import_path = require("path");
40
- function stripComments(code) {
41
- let out = "";
42
- let i = 0;
43
- const n = code.length;
44
- while (i < n) {
45
- const two = code[i] + (code[i + 1] ?? "");
46
- if (two === "//") {
47
- i += 2;
48
- while (i < n && code[i] !== "\n") i++;
49
- continue;
50
- }
51
- if (two === "/*") {
52
- i += 2;
53
- while (i < n && code[i] + (code[i + 1] ?? "") !== "*/") i++;
54
- i += 2;
55
- continue;
56
- }
57
- const ch = code[i];
58
- if (ch === '"' || ch === "'") {
59
- out += ch;
60
- i++;
61
- while (i < n && code[i] !== ch) {
62
- if (code[i] === "\\") {
63
- out += code[i] + (code[i + 1] ?? "");
64
- i += 2;
65
- continue;
66
- }
67
- out += code[i];
68
- i++;
69
- }
70
- out += code[i] ?? "";
71
- i++;
72
- continue;
73
- }
74
- if (ch === "`") {
75
- out += ch;
76
- i++;
77
- let interpolationDepth = 0;
78
- while (i < n) {
79
- if (interpolationDepth === 0 && code[i] === "`") {
80
- out += code[i];
81
- i++;
82
- break;
83
- }
84
- if (interpolationDepth === 0 && code[i] === "\\") {
85
- out += code[i] + (code[i + 1] ?? "");
86
- i += 2;
87
- continue;
88
- }
89
- if (interpolationDepth === 0 && code[i] + (code[i + 1] ?? "") === "${") {
90
- out += "${";
91
- i += 2;
92
- interpolationDepth = 1;
93
- continue;
94
- }
95
- if (interpolationDepth > 0) {
96
- if (code[i] === "{") interpolationDepth++;
97
- else if (code[i] === "}") {
98
- interpolationDepth--;
99
- if (interpolationDepth === 0) {
100
- out += "}";
101
- i++;
102
- continue;
103
- }
104
- }
105
- }
106
- out += code[i];
107
- i++;
108
- }
109
- continue;
110
- }
111
- out += ch;
112
- i++;
113
- }
114
- return out;
115
- }
116
- function splitClassTokens(str) {
117
- return str.split(/\s+/).filter(Boolean);
118
- }
119
- var NOT_A_CLASS_NAME_RE = /[${}]|-$|^[?:&|!"'=<>]+$/;
120
- function pushTokens(str, into) {
121
- for (const tok of splitClassTokens(str)) {
122
- if (tok && !NOT_A_CLASS_NAME_RE.test(tok)) into.add(tok);
123
- }
124
- }
125
- function pushTemplateLiteralBody(body, into) {
126
- const interpolationRe = /\$\{(?:[^{}]|\{[^{}]*\})*\}/g;
127
- let im;
128
- while ((im = interpolationRe.exec(body)) !== null) {
129
- pushClassLikeStrings(im[0], into);
130
- }
131
- pushTokens(body.replace(/\$\{(?:[^{}]|\{[^{}]*\})*\}/g, () => "$"), into);
132
- }
133
- function pushClassLikeStrings(text, into) {
134
- const quotedStringRe = /(["'`])((?:(?!\1).)*)\1/g;
135
- let qm;
136
- while ((qm = quotedStringRe.exec(text)) !== null) {
137
- const [, quote, content] = qm;
138
- if (quote === "`") pushTemplateLiteralBody(content, into);
139
- else pushTokens(content, into);
140
- }
141
- }
142
- var JSX_TAG_RE = /<([a-z][a-zA-Z0-9]*)[\s/>]/g;
143
- function scanUsedTags(code) {
144
- const cleaned = stripComments(code);
145
- const tags = /* @__PURE__ */ new Set();
146
- let m;
147
- JSX_TAG_RE.lastIndex = 0;
148
- while ((m = JSX_TAG_RE.exec(cleaned)) !== null) tags.add(m[1]);
149
- return tags;
150
- }
151
- function extractClassStrings(rawCode) {
152
- const code = stripComments(rawCode);
153
- const found = /* @__PURE__ */ new Set();
154
- const simpleRe = /(?:className|kb)=(["'])((?:(?!\1).)*)\1/g;
155
- let m;
156
- while ((m = simpleRe.exec(code)) !== null) pushTokens(m[2], found);
157
- const jsxExprRe = /(?:className|kb)=\{/g;
158
- while ((m = jsxExprRe.exec(code)) !== null) {
159
- let depth = 1;
160
- let i = m.index + m[0].length;
161
- let block = "";
162
- while (i < code.length && depth > 0) {
163
- const ch = code[i];
164
- if (ch === "{") depth++;
165
- else if (ch === "}") {
166
- if (--depth === 0) break;
167
- }
168
- block += ch;
169
- i++;
170
- }
171
- jsxExprRe.lastIndex = i + 1;
172
- pushClassLikeStrings(block, found);
173
- }
174
- const classComposerCallRe = /(?:clsx|cn|classnames|cx|kb)\(/g;
175
- while ((m = classComposerCallRe.exec(code)) !== null) {
176
- let depth = 1;
177
- let i = m.index + m[0].length;
178
- let block = "";
179
- while (i < code.length && depth > 0) {
180
- const ch = code[i];
181
- if (ch === "(") depth++;
182
- else if (ch === ")") {
183
- if (--depth === 0) break;
184
- }
185
- block += ch;
186
- i++;
187
- }
188
- classComposerCallRe.lastIndex = i + 1;
189
- pushClassLikeStrings(block, found);
190
- }
191
- const templateRe = /`([^`]{1,2000})`/g;
192
- while ((m = templateRe.exec(code)) !== null) pushTemplateLiteralBody(m[1], found);
193
- return [...found];
194
- }
195
- var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", "dist", "build", "out", ".next", ".output", ".git", ".svn", ".cache", "coverage"]);
196
- var MAX_SCAN_DEPTH = 10;
197
- var DEFAULT_FILE_MATCH = /\.(tsx?|jsx?)$/;
198
- function scanDir(dir, onFile, depth = 0, fileMatch = DEFAULT_FILE_MATCH) {
199
- if (depth > MAX_SCAN_DEPTH) return;
200
- let entries;
201
- try {
202
- entries = (0, import_fs.readdirSync)(dir);
203
- } catch {
204
- return;
205
- }
206
- for (const entry of entries) {
207
- if (entry.startsWith(".") || SKIP_DIRS.has(entry)) continue;
208
- const full = (0, import_path.join)(dir, entry);
209
- try {
210
- const st = (0, import_fs.statSync)(full);
211
- if (st.isDirectory()) scanDir(full, onFile, depth + 1, fileMatch);
212
- else if (fileMatch.test(entry)) onFile(full, (0, import_fs.readFileSync)(full, "utf-8"));
213
- } catch {
214
- }
215
- }
216
- }
217
-
218
34
  // src/vite-plugin/cssVars.ts
219
35
  function hexToRgbTriplet(hex) {
220
36
  const h = hex.replace("#", "");
@@ -475,10 +291,10 @@ function formatKbachCSS(tokenRules, theme, usedTags) {
475
291
  lines.push("", prettifyCSS(text));
476
292
  return lines.join("\n");
477
293
  }
478
- function writeKbachToFile(filePath, css, readFile2, writeFile2) {
294
+ function writeKbachToFile(filePath, css, readFile, writeFile) {
479
295
  let existing = "";
480
296
  try {
481
- existing = readFile2(filePath);
297
+ existing = readFile(filePath);
482
298
  } catch {
483
299
  }
484
300
  const block = `${KBACH_START}
@@ -490,66 +306,10 @@ ${KBACH_END}`;
490
306
 
491
307
  ${block}` : block;
492
308
  if (next === existing) return false;
493
- writeFile2(filePath, next);
309
+ writeFile(filePath, next);
494
310
  return true;
495
311
  }
496
312
 
497
- // src/vite-plugin/unknownClassWarnings.ts
498
- var import_path2 = require("path");
499
- var CSS_CLASS_SELECTOR_RE = /\.(-?[a-zA-Z_][a-zA-Z0-9_-]*)/g;
500
- var CSS_FILE_RE = /\.(css|scss|sass|less)$/;
501
- var useColor = !!process.stdout?.isTTY && !process.env.NO_COLOR;
502
- var paint = (code, s) => useColor ? `\x1B[${code}m${s}\x1B[0m` : s;
503
- var green = (s) => paint("32", s);
504
- var blue = (s) => paint("34", s);
505
- var white = (s) => paint("97", s);
506
- var gray = (s) => paint("90", s);
507
- var bold = (s) => paint("1", s);
508
- var ARROW = () => green("\u2192");
509
- var TAG = () => bold(green("[kbach]"));
510
- var highlight = (s) => bold(white(s));
511
- function findLineCol(code, needle) {
512
- const index = code.indexOf(needle);
513
- if (index === -1) return null;
514
- let line = 1;
515
- let lastNewline = -1;
516
- for (let i = 0; i < index; i++) {
517
- if (code.charCodeAt(i) === 10) {
518
- line++;
519
- lastNewline = i;
520
- }
521
- }
522
- return { line, column: index - lastNewline };
523
- }
524
- function scanCssFileInto(code, into) {
525
- const re = new RegExp(CSS_CLASS_SELECTOR_RE.source, "g");
526
- let m;
527
- while ((m = re.exec(code)) !== null) into.add(m[1]);
528
- }
529
- function scanProjectCssSelectors(root, includeDirs) {
530
- const projectCssClasses = /* @__PURE__ */ new Set();
531
- for (const dir of includeDirs) {
532
- scanDir((0, import_path2.join)(root, dir), (_filePath, code) => scanCssFileInto(code, projectCssClasses), 0, CSS_FILE_RE);
533
- }
534
- return projectCssClasses;
535
- }
536
- function stripModifierPrefix(token) {
537
- return token.replace(/^(?:[a-zA-Z0-9_/-]+:)+/, "");
538
- }
539
- var MARKER_CLASSES = /* @__PURE__ */ new Set(["group", "peer"]);
540
- function warnIfUnknownClass(token, filePath, code, isResolvable, projectCssClasses, warnedTokens) {
541
- if (isResolvable || warnedTokens.has(token)) return;
542
- const base = stripModifierPrefix(token);
543
- if (!base || projectCssClasses.has(base) || MARKER_CLASSES.has(base)) return;
544
- warnedTokens.add(token);
545
- const pos = findLineCol(code, token);
546
- const location = pos ? `${blue(filePath)}${green(`:${pos.line}:${pos.column}`)}` : blue(filePath);
547
- console.warn(
548
- `${ARROW()} ${TAG()} ${gray("Unknown class")} ${highlight(token)} ${gray("\u2014 no Kbach utility or project CSS rule matches it. Typo?")}
549
- ${bold("at")} ${location}`
550
- );
551
- }
552
-
553
313
  // src/vite-plugin/classNameHints.ts
554
314
  var STRUCTURAL_TOKENS = [
555
315
  // layout.rs
@@ -1669,36 +1429,261 @@ function resolveKbachConfig(config) {
1669
1429
  return theme;
1670
1430
  }
1671
1431
 
1672
- // src/vite-plugin/index.ts
1673
- var DEFAULT_SCAN_DIRS = ["src", "app", "pages", "components"];
1674
- var CSS_SYNC_DEBOUNCE_MS = 50;
1675
- function normPath(p) {
1676
- const fwd = p.replace(/\\/g, "/");
1677
- return process.platform === "win32" ? fwd.toLowerCase() : fwd;
1678
- }
1679
- var SAFELIST_KEY = "\0kbach-safelist";
1680
- function readFile(path) {
1681
- return (0, import_fs2.readFileSync)(path, "utf-8");
1682
- }
1683
- function writeFile(path, content) {
1684
- (0, import_fs2.writeFileSync)(path, content, "utf-8");
1685
- }
1686
- function writeFileEnsuringDir(path, content) {
1687
- (0, import_fs2.mkdirSync)((0, import_path3.dirname)(path), { recursive: true });
1688
- (0, import_fs2.writeFileSync)(path, content, "utf-8");
1432
+ // src/staticCss/engine.ts
1433
+ var import_fs2 = require("fs");
1434
+ var import_path3 = require("path");
1435
+
1436
+ // src/vite-plugin/wasmNode.ts
1437
+ var import_node = require("@kbach/core-engine/node");
1438
+ function generateCssForToken(token, themeJson) {
1439
+ return JSON.parse((0, import_node.generate_css)(token, themeJson));
1689
1440
  }
1690
- function loadConfigFile(root) {
1691
- const path = (0, import_path3.join)(root, "kbach.config.js");
1692
- if (!(0, import_fs2.existsSync)(path)) return void 0;
1693
- const mod = (0, import_module.createRequire)(root)(path);
1694
- return mod.default ?? mod;
1441
+
1442
+ // src/vite-plugin/scan.ts
1443
+ var import_fs = require("fs");
1444
+ var import_path = require("path");
1445
+ function stripComments(code) {
1446
+ let out = "";
1447
+ let i = 0;
1448
+ const n = code.length;
1449
+ while (i < n) {
1450
+ const two = code[i] + (code[i + 1] ?? "");
1451
+ if (two === "//") {
1452
+ i += 2;
1453
+ while (i < n && code[i] !== "\n") i++;
1454
+ continue;
1455
+ }
1456
+ if (two === "/*") {
1457
+ i += 2;
1458
+ while (i < n && code[i] + (code[i + 1] ?? "") !== "*/") i++;
1459
+ i += 2;
1460
+ continue;
1461
+ }
1462
+ const ch = code[i];
1463
+ if (ch === '"' || ch === "'") {
1464
+ out += ch;
1465
+ i++;
1466
+ while (i < n && code[i] !== ch) {
1467
+ if (code[i] === "\\") {
1468
+ out += code[i] + (code[i + 1] ?? "");
1469
+ i += 2;
1470
+ continue;
1471
+ }
1472
+ out += code[i];
1473
+ i++;
1474
+ }
1475
+ out += code[i] ?? "";
1476
+ i++;
1477
+ continue;
1478
+ }
1479
+ if (ch === "`") {
1480
+ out += ch;
1481
+ i++;
1482
+ let interpolationDepth = 0;
1483
+ while (i < n) {
1484
+ if (interpolationDepth === 0 && code[i] === "`") {
1485
+ out += code[i];
1486
+ i++;
1487
+ break;
1488
+ }
1489
+ if (interpolationDepth === 0 && code[i] === "\\") {
1490
+ out += code[i] + (code[i + 1] ?? "");
1491
+ i += 2;
1492
+ continue;
1493
+ }
1494
+ if (interpolationDepth === 0 && code[i] + (code[i + 1] ?? "") === "${") {
1495
+ out += "${";
1496
+ i += 2;
1497
+ interpolationDepth = 1;
1498
+ continue;
1499
+ }
1500
+ if (interpolationDepth > 0) {
1501
+ if (code[i] === "{") interpolationDepth++;
1502
+ else if (code[i] === "}") {
1503
+ interpolationDepth--;
1504
+ if (interpolationDepth === 0) {
1505
+ out += "}";
1506
+ i++;
1507
+ continue;
1508
+ }
1509
+ }
1510
+ }
1511
+ out += code[i];
1512
+ i++;
1513
+ }
1514
+ continue;
1515
+ }
1516
+ out += ch;
1517
+ i++;
1518
+ }
1519
+ return out;
1695
1520
  }
1696
- function kbach(options = {}) {
1697
- let theme = options.theme ?? (options.config ? resolveKbachConfig(options.config) : defaultTheme);
1698
- let themeJson = JSON.stringify(theme);
1699
- const includeDirs = options.include ?? DEFAULT_SCAN_DIRS;
1700
- const safelist = options.safelist ?? [];
1701
- let root = process.cwd();
1521
+ function splitClassTokens(str) {
1522
+ return str.split(/\s+/).filter(Boolean);
1523
+ }
1524
+ var NOT_A_CLASS_NAME_RE = /[${}]|-$|^[?:&|!"'=<>]+$/;
1525
+ function pushTokens(str, into) {
1526
+ for (const tok of splitClassTokens(str)) {
1527
+ if (tok && !NOT_A_CLASS_NAME_RE.test(tok)) into.add(tok);
1528
+ }
1529
+ }
1530
+ function pushTemplateLiteralBody(body, into) {
1531
+ const interpolationRe = /\$\{(?:[^{}]|\{[^{}]*\})*\}/g;
1532
+ let im;
1533
+ while ((im = interpolationRe.exec(body)) !== null) {
1534
+ pushClassLikeStrings(im[0], into);
1535
+ }
1536
+ pushTokens(body.replace(/\$\{(?:[^{}]|\{[^{}]*\})*\}/g, () => "$"), into);
1537
+ }
1538
+ function pushClassLikeStrings(text, into) {
1539
+ const quotedStringRe = /(["'`])((?:(?!\1).)*)\1/g;
1540
+ let qm;
1541
+ while ((qm = quotedStringRe.exec(text)) !== null) {
1542
+ const [, quote, content] = qm;
1543
+ if (quote === "`") pushTemplateLiteralBody(content, into);
1544
+ else pushTokens(content, into);
1545
+ }
1546
+ }
1547
+ var JSX_TAG_RE = /<([a-z][a-zA-Z0-9]*)[\s/>]/g;
1548
+ function scanUsedTags(code) {
1549
+ const cleaned = stripComments(code);
1550
+ const tags = /* @__PURE__ */ new Set();
1551
+ let m;
1552
+ JSX_TAG_RE.lastIndex = 0;
1553
+ while ((m = JSX_TAG_RE.exec(cleaned)) !== null) tags.add(m[1]);
1554
+ return tags;
1555
+ }
1556
+ function extractClassStrings(rawCode) {
1557
+ const code = stripComments(rawCode);
1558
+ const found = /* @__PURE__ */ new Set();
1559
+ const simpleRe = /(?:className|kb)=(["'])((?:(?!\1).)*)\1/g;
1560
+ let m;
1561
+ while ((m = simpleRe.exec(code)) !== null) pushTokens(m[2], found);
1562
+ const jsxExprRe = /(?:className|kb)=\{/g;
1563
+ while ((m = jsxExprRe.exec(code)) !== null) {
1564
+ let depth = 1;
1565
+ let i = m.index + m[0].length;
1566
+ let block = "";
1567
+ while (i < code.length && depth > 0) {
1568
+ const ch = code[i];
1569
+ if (ch === "{") depth++;
1570
+ else if (ch === "}") {
1571
+ if (--depth === 0) break;
1572
+ }
1573
+ block += ch;
1574
+ i++;
1575
+ }
1576
+ jsxExprRe.lastIndex = i + 1;
1577
+ pushClassLikeStrings(block, found);
1578
+ }
1579
+ const classComposerCallRe = /(?:clsx|cn|classnames|cx|kb)\(/g;
1580
+ while ((m = classComposerCallRe.exec(code)) !== null) {
1581
+ let depth = 1;
1582
+ let i = m.index + m[0].length;
1583
+ let block = "";
1584
+ while (i < code.length && depth > 0) {
1585
+ const ch = code[i];
1586
+ if (ch === "(") depth++;
1587
+ else if (ch === ")") {
1588
+ if (--depth === 0) break;
1589
+ }
1590
+ block += ch;
1591
+ i++;
1592
+ }
1593
+ classComposerCallRe.lastIndex = i + 1;
1594
+ pushClassLikeStrings(block, found);
1595
+ }
1596
+ const templateRe = /`([^`]{1,2000})`/g;
1597
+ while ((m = templateRe.exec(code)) !== null) pushTemplateLiteralBody(m[1], found);
1598
+ return [...found];
1599
+ }
1600
+ var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", "dist", "build", "out", ".next", ".output", ".git", ".svn", ".cache", "coverage"]);
1601
+ var MAX_SCAN_DEPTH = 10;
1602
+ var DEFAULT_FILE_MATCH = /\.(tsx?|jsx?)$/;
1603
+ function scanDir(dir, onFile, depth = 0, fileMatch = DEFAULT_FILE_MATCH) {
1604
+ if (depth > MAX_SCAN_DEPTH) return;
1605
+ let entries;
1606
+ try {
1607
+ entries = (0, import_fs.readdirSync)(dir);
1608
+ } catch {
1609
+ return;
1610
+ }
1611
+ for (const entry of entries) {
1612
+ if (entry.startsWith(".") || SKIP_DIRS.has(entry)) continue;
1613
+ const full = (0, import_path.join)(dir, entry);
1614
+ try {
1615
+ const st = (0, import_fs.statSync)(full);
1616
+ if (st.isDirectory()) scanDir(full, onFile, depth + 1, fileMatch);
1617
+ else if (fileMatch.test(entry)) onFile(full, (0, import_fs.readFileSync)(full, "utf-8"));
1618
+ } catch {
1619
+ }
1620
+ }
1621
+ }
1622
+
1623
+ // src/vite-plugin/unknownClassWarnings.ts
1624
+ var import_path2 = require("path");
1625
+ var CSS_CLASS_SELECTOR_RE = /\.(-?[a-zA-Z_][a-zA-Z0-9_-]*)/g;
1626
+ var CSS_FILE_RE = /\.(css|scss|sass|less)$/;
1627
+ var useColor = !!process.stdout?.isTTY && !process.env.NO_COLOR;
1628
+ var paint = (code, s) => useColor ? `\x1B[${code}m${s}\x1B[0m` : s;
1629
+ var green = (s) => paint("32", s);
1630
+ var blue = (s) => paint("34", s);
1631
+ var white = (s) => paint("97", s);
1632
+ var gray = (s) => paint("90", s);
1633
+ var bold = (s) => paint("1", s);
1634
+ var ARROW = () => green("\u2192");
1635
+ var TAG = () => bold(green("[kbach]"));
1636
+ var highlight = (s) => bold(white(s));
1637
+ function findLineCol(code, needle) {
1638
+ const index = code.indexOf(needle);
1639
+ if (index === -1) return null;
1640
+ let line = 1;
1641
+ let lastNewline = -1;
1642
+ for (let i = 0; i < index; i++) {
1643
+ if (code.charCodeAt(i) === 10) {
1644
+ line++;
1645
+ lastNewline = i;
1646
+ }
1647
+ }
1648
+ return { line, column: index - lastNewline };
1649
+ }
1650
+ function scanCssFileInto(code, into) {
1651
+ const re = new RegExp(CSS_CLASS_SELECTOR_RE.source, "g");
1652
+ let m;
1653
+ while ((m = re.exec(code)) !== null) into.add(m[1]);
1654
+ }
1655
+ function scanProjectCssSelectors(root, includeDirs) {
1656
+ const projectCssClasses = /* @__PURE__ */ new Set();
1657
+ for (const dir of includeDirs) {
1658
+ scanDir((0, import_path2.join)(root, dir), (_filePath, code) => scanCssFileInto(code, projectCssClasses), 0, CSS_FILE_RE);
1659
+ }
1660
+ return projectCssClasses;
1661
+ }
1662
+ function stripModifierPrefix(token) {
1663
+ return token.replace(/^(?:[a-zA-Z0-9_/-]+:)+/, "");
1664
+ }
1665
+ var MARKER_CLASSES = /* @__PURE__ */ new Set(["group", "peer"]);
1666
+ function warnIfUnknownClass(token, filePath, code, isResolvable, projectCssClasses, warnedTokens) {
1667
+ if (isResolvable || warnedTokens.has(token)) return;
1668
+ const base = stripModifierPrefix(token);
1669
+ if (!base || projectCssClasses.has(base) || MARKER_CLASSES.has(base)) return;
1670
+ warnedTokens.add(token);
1671
+ const pos = findLineCol(code, token);
1672
+ const location = pos ? `${blue(filePath)}${green(`:${pos.line}:${pos.column}`)}` : blue(filePath);
1673
+ console.warn(
1674
+ `${ARROW()} ${TAG()} ${gray("Unknown class")} ${highlight(token)} ${gray("\u2014 no Kbach utility or project CSS rule matches it. Typo?")}
1675
+ ${bold("at")} ${location}`
1676
+ );
1677
+ }
1678
+
1679
+ // src/staticCss/engine.ts
1680
+ function normPath(p) {
1681
+ const fwd = p.replace(/\\/g, "/");
1682
+ return process.platform === "win32" ? fwd.toLowerCase() : fwd;
1683
+ }
1684
+ var SAFELIST_KEY = "\0kbach-safelist";
1685
+ function createKbachStaticCssEngine(options) {
1686
+ const { root, theme, themeJson, includeDirs, safelist } = options;
1702
1687
  const fileTokens = /* @__PURE__ */ new Map();
1703
1688
  const tokenRules = /* @__PURE__ */ new Map();
1704
1689
  const fileTags = /* @__PURE__ */ new Map();
@@ -1722,6 +1707,10 @@ function kbach(options = {}) {
1722
1707
  fileTokens.set(normPath(filePath), tokens);
1723
1708
  fileTags.set(normPath(filePath), scanUsedTags(code));
1724
1709
  }
1710
+ function removeFile(filePath) {
1711
+ fileTokens.delete(normPath(filePath));
1712
+ fileTags.delete(normPath(filePath));
1713
+ }
1725
1714
  function processSafelist() {
1726
1715
  if (safelist.length === 0) return;
1727
1716
  const tokens = new Set(safelist);
@@ -1745,24 +1734,62 @@ function kbach(options = {}) {
1745
1734
  const rules = tokenRules.get(tok);
1746
1735
  if (rules) view.set(tok, rules);
1747
1736
  }
1737
+ for (const tok of tokenRules.keys()) {
1738
+ if (!active.has(tok)) tokenRules.delete(tok);
1739
+ }
1748
1740
  return formatKbachCSS(view, theme, activeTags());
1749
1741
  }
1742
+ function initialScan() {
1743
+ for (const dir of includeDirs) {
1744
+ scanDir((0, import_path3.join)(root, dir), processFile);
1745
+ }
1746
+ }
1747
+ function scanProjectCssSelectorsOnce() {
1748
+ projectCssClasses = scanProjectCssSelectors(root, includeDirs);
1749
+ }
1750
+ return { processFile, removeFile, initialScan, processSafelist, generateCSS, scanProjectCssSelectorsOnce };
1751
+ }
1752
+ function readSourceFile(path) {
1753
+ return (0, import_fs2.readFileSync)(path, "utf-8");
1754
+ }
1755
+
1756
+ // src/vite-plugin/index.ts
1757
+ var DEFAULT_SCAN_DIRS = ["src", "app", "pages", "components"];
1758
+ var CSS_SYNC_DEBOUNCE_MS = 50;
1759
+ function writeFileEnsuringDir(path, content) {
1760
+ (0, import_fs3.mkdirSync)((0, import_path4.dirname)(path), { recursive: true });
1761
+ (0, import_fs3.writeFileSync)(path, content, "utf-8");
1762
+ }
1763
+ function loadConfigFile(root) {
1764
+ const path = (0, import_path4.join)(root, "kbach.config.js");
1765
+ if (!(0, import_fs3.existsSync)(path)) return void 0;
1766
+ const mod = (0, import_module.createRequire)(root)(path);
1767
+ return mod.default ?? mod;
1768
+ }
1769
+ function resolveEffectiveTheme(root, options) {
1770
+ if (options.theme) return options.theme;
1771
+ if (options.config) return resolveKbachConfig(options.config);
1772
+ const discovered = loadConfigFile(root);
1773
+ return discovered ? resolveKbachConfig(discovered) : defaultTheme;
1774
+ }
1775
+ function kbach(options = {}) {
1776
+ const includeDirs = options.include ?? DEFAULT_SCAN_DIRS;
1777
+ const safelist = options.safelist ?? [];
1778
+ let root = process.cwd();
1779
+ let theme = defaultTheme;
1780
+ let engine = createKbachStaticCssEngine({ root, theme, themeJson: JSON.stringify(theme), includeDirs, safelist });
1750
1781
  function mainCSSFile() {
1751
- return (0, import_path3.join)(root, "src", "kbach.css");
1782
+ return (0, import_path4.join)(root, "src", "kbach.css");
1752
1783
  }
1753
1784
  function classNameHintsFile() {
1754
- return (0, import_path3.join)(root, ".kbach", "kbach-classnames.d.ts");
1785
+ return (0, import_path4.join)(root, ".kbach", "kbach-classnames.d.ts");
1755
1786
  }
1756
1787
  function writeClassNameHints() {
1757
1788
  const dts = buildClassNameHintsDts(buildClassTokens(theme));
1758
1789
  writeFileEnsuringDir(classNameHintsFile(), dts);
1759
1790
  }
1760
1791
  function syncMainCSSFile(server) {
1761
- const active = activeTokens();
1762
- for (const tok of tokenRules.keys()) {
1763
- if (!active.has(tok)) tokenRules.delete(tok);
1764
- }
1765
- const changed = writeKbachToFile(mainCSSFile(), generateCSS(), readFile, writeFile);
1792
+ const changed = writeKbachToFile(mainCSSFile(), engine.generateCSS(), readSourceFile, (p, c) => (0, import_fs3.writeFileSync)(p, c, "utf-8"));
1766
1793
  if (changed && server) server.watcher.emit("change", mainCSSFile());
1767
1794
  }
1768
1795
  let syncTimer = null;
@@ -1773,29 +1800,19 @@ function kbach(options = {}) {
1773
1800
  syncMainCSSFile(server);
1774
1801
  }, CSS_SYNC_DEBOUNCE_MS);
1775
1802
  }
1776
- function initialScan() {
1777
- for (const dir of includeDirs) {
1778
- scanDir((0, import_path3.join)(root, dir), processFile);
1779
- }
1780
- }
1781
1803
  return {
1782
1804
  name: "kbach",
1783
1805
  enforce: "pre",
1784
1806
  configResolved(resolved) {
1785
1807
  root = resolved.root;
1786
- if (!options.theme && !options.config) {
1787
- const discovered = loadConfigFile(root);
1788
- if (discovered) {
1789
- theme = resolveKbachConfig(discovered);
1790
- themeJson = JSON.stringify(theme);
1791
- }
1792
- }
1808
+ theme = resolveEffectiveTheme(root, options);
1809
+ engine = createKbachStaticCssEngine({ root, theme, themeJson: JSON.stringify(theme), includeDirs, safelist });
1793
1810
  },
1794
1811
  buildStart() {
1795
- projectCssClasses = scanProjectCssSelectors(root, includeDirs);
1796
- initialScan();
1797
- processSafelist();
1798
- writeKbachToFile(mainCSSFile(), generateCSS(), readFile, writeFile);
1812
+ engine.scanProjectCssSelectorsOnce();
1813
+ engine.initialScan();
1814
+ engine.processSafelist();
1815
+ writeKbachToFile(mainCSSFile(), engine.generateCSS(), readSourceFile, (p, c) => (0, import_fs3.writeFileSync)(p, c, "utf-8"));
1799
1816
  writeClassNameHints();
1800
1817
  },
1801
1818
  // handleHotUpdate covers edits; add/unlink (create/delete) land on the
@@ -1806,14 +1823,12 @@ function kbach(options = {}) {
1806
1823
  if (file.includes("node_modules")) return;
1807
1824
  if (!/\.(tsx?|jsx?)$/.test(file)) return;
1808
1825
  if (isUnlink) {
1809
- fileTokens.delete(normPath(file));
1810
- fileTags.delete(normPath(file));
1826
+ engine.removeFile(file);
1811
1827
  } else {
1812
1828
  try {
1813
- processFile(file, readFile(file));
1829
+ engine.processFile(file, readSourceFile(file));
1814
1830
  } catch {
1815
- fileTokens.delete(normPath(file));
1816
- fileTags.delete(normPath(file));
1831
+ engine.removeFile(file);
1817
1832
  }
1818
1833
  }
1819
1834
  scheduleSync(server);
@@ -1825,10 +1840,9 @@ function kbach(options = {}) {
1825
1840
  if (file.includes("node_modules")) return;
1826
1841
  if (!/\.(tsx?|jsx?)$/.test(file)) return;
1827
1842
  try {
1828
- processFile(file, readFile(file));
1843
+ engine.processFile(file, readSourceFile(file));
1829
1844
  } catch {
1830
- fileTokens.delete(normPath(file));
1831
- fileTags.delete(normPath(file));
1845
+ engine.removeFile(file);
1832
1846
  }
1833
1847
  scheduleSync(server);
1834
1848
  }
@@ -1837,5 +1851,8 @@ function kbach(options = {}) {
1837
1851
  // Annotate the CommonJS export names for ESM import in node:
1838
1852
  0 && (module.exports = {
1839
1853
  CSS_SYNC_DEBOUNCE_MS,
1840
- kbach
1854
+ DEFAULT_SCAN_DIRS,
1855
+ kbach,
1856
+ loadConfigFile,
1857
+ resolveEffectiveTheme
1841
1858
  });